Android Interface Definition Language (AIDL)

AIDL (Android Interface Definition Language) mirip dengan IDL lain yang mungkin sudah pernah Anda gunakan. AIDL memungkinkan Anda untuk mendefinisikan antarmuka pemrograman yang disepakati oleh klien dan layanan untuk berkomunikasi satu sama lain dengan menggunakan komunikasi antarproses (Interprocess Communication - IPC). Di Android, satu proses biasanya tidak bisa mengakses memori proses lain. Sehingga untuk berkomunikasi, proses-proses itu harus mengurai objek menjadi primitif yang bisa dipahami sistem operasi dan mengarahkan objek melintasi batasan untuk Anda. Menulis kode yang melakukan pengarahan tersebut adalah membosankan, sehingga Android melakukannya untuk Anda dengan AIDL.

Catatan: Menggunakan AIDL hanya perlu jika Anda mengizinkan klien dari aplikasi berbeda untuk mengakses layanan untuk IPC Anda dan ingin menangani multithreading dalam layanan Anda. Jika Anda tidak perlu melakukan IPC bersamaan secara lintas aplikasi berbeda, Anda harus membuat antarmuka dengan mengimplementasikan Binder atau, jika Anda ingin melakukan IPC, tetapi tidak perlu menangani multithreading, terapkan antarmuka Anda dengan menggunakan Messenger. Walau demikian, pastikan Anda memahami Layanan Terikat sebelum mengimplementasikan AIDL.

Sebelum Anda mulai mendesain antarmuka AIDL, ketahuilah bahwa panggilan ke antarmuka AIDL merupakan panggilan fungsi langsung. Anda tidak boleh berasumsi tentang thread tempat terjadinya panggilan. Apa yang terjadi tidak sama, bergantung pada apakah panggilan tersebut dari thread dalam proses lokal atau proses jauh. Khususnya:

  • Panggilan yang dilakukan dari proses lokal dieksekusi dalam thread yang sama yang melakukan panggilan. Jika ini thread UI utama Anda, thread tersebut akan terus mengeksekusi dalam antarmuka AIDL. Jika itu thread lain, thread itulah yang mengeksekusi kode Anda dalam layanan ini. Karena itu, jika hanya thread lokal yang mengakses layanan tersebut, Anda bisa mengontrol sepenuhnya thread mana yang mengeksekusi di dalamnya (namun jika kasusnya seperti itu, Anda tidak perlu menggunakan AIDL sama sekali, melainkan harus membuat antarmuka dengan mengimplementasikan Binder).
  • Panggilan dari proses jauh dikirimkan dari pool thread yang dikelola oleh platform dalam proses Anda sendiri. Anda harus siap untuk panggilan masuk dari thread yang tidak dikenal, dengan beberapa panggilan yang terjadi bersamaan. Dengan kata lain, implementasi antarmuka AIDL harus sepenuhnya thread-safe. Panggilan dibuat dari satu thread pada objek yang jauh tiba secara berurutan di tujuan penerima.
  • Kata kunci oneway memodifikasi perilaku panggilan jauh. Jika digunakan, panggilan jauh tidak memblokir; hanya mengirimkan data transaksi dan segera mengembalikan. Implementasi antarmuka tersebut akhirnya menerima ini sebagai panggilan biasa dari pool thread Binder seperti panggilan jauh biasa. Jika oneway digunakan bersama panggilan lokal, tidak ada pengaruhnya dan panggilan tersebut tetap sinkron.

Mendefinisikan antarmuka AIDL

Anda harus mendefinisikan antarmuka AIDL dalam file .aidl menggunakan sintaks bahasa pemrograman Java, kemudian menyimpannya dalam kode sumber (dalam direktori src/) baik pada aplikasi yang menjadi host layanan maupun aplikasi lain yang tertaut dengan layanan tersebut.

Jika Anda membuat setiap aplikasi yang berisi file .aidl, fitur Android SDK akan menghasilkan antarmuka IBinder berdasarkan file .aidl dan menyimpannya dalam direktori gen/ project. Layanan harus mengimplementasikan antarmuka IBinder dengan semestinya. Aplikasi klien kemudian bisa menautkan ke layanan tersebut dan memanggil metode dari IBinder untuk menjalankan IPC.

Untuk membuat layanan terikat dengan menggunakan AIDL, ikuti langkah-langkah ini:

  1. Buat file .aidl

    File ini mendefinisikan antarmuka pemrograman dengan tanda tangan metode.

  2. Implementasikan antarmuka

    Fitur Android SDK menghasilkan antarmuka dalam bahasa pemrograman Java, berdasarkan file .aidl Anda. Antarmuka ini memiliki kelas abstrak internal bernama Stub yang memperluas Binder dan mengimplementasikan metode dari antarmuka AIDL Anda. Anda harus memperluas kelas Stub dan mengimplementasikan metode tersebut.

  3. Ekspos antarmuka ke klien

    Implementasikan Service dan timpa onBind() untuk mengembalikan implementasi kelas Stub Anda.

Perhatian: Setiap perubahan yang Anda buat pada antarmuka AIDL setelah rilis pertama harus tetap kompatibel ke belakang agar tidak merusak aplikasi lain yang menggunakan layanan Anda. Maksudnya, karena file .aidl Anda harus disalin ke aplikasi lain agar bisa mengakses antarmuka layanan, maka Anda harus mempertahankan dukungan antarmuka asli.

1. Buat file .aidl

AIDL menggunakan sintaks sederhana yang memungkinkan Anda mendeklarasikan antarmuka dengan satu atau beberapa metode yang bisa mengambil parameter dan mengembalikan nilai. Parameter dan nilai yang dikembalikan bisa bertipe apa saja, bahkan antarmuka lain yang dihasilkan AIDL.

Anda harus membuat file .aidl menggunakan bahasa pemrograman Java. Setiap file .aidl harus mendefinisikan satu antarmuka dan hanya memerlukan deklarasi antarmuka dan tanda tangan metode.

Secara default, AIDL mendukung tipe data berikut:

  • Semua tipe primitif dalam bahasa pemrograman Java (misalnya int, long, char, boolean, dan seterusnya)
  • String
  • CharSequence
  • List

    Semua elemen dalam List harus berupa salah satu tipe data yang didukung dalam daftar ini atau salah satu antarmuka lain yang dihasilkan AIDL atau parcelable yang telah Anda deklarasikan. Suatu List dapat secara opsional digunakan sebagai kelas "generik" (misalnya, List<String>). Kelas konkret yang diterima pihak lain selalu ArrayList, meskipun metode itu dibuat untuk menggunakan antarmuka List.

  • Map

    Semua elemen dalam Map harus berupa salah satu tipe data yang didukung dalam daftar ini atau salah satu antarmuka lain yang dihasilkan AIDL atau parcelable yang telah Anda deklarasikan. Peta generik, (misalnya peta berbentuk Map<String,Integer>) tidak didukung. Kelas konkret yang diterima pihak lain selalu HashMap, meskipun metode itu dibuat untuk menggunakan antarmuka Map.

Anda harus menyertakan pernyataan import untuk setiap tipe tambahan yang tidak ada dalam daftar di atas, bahkan jika telah didefinisikan dalam satu paket dengan antarmuka Anda.

Saat mendefinisikan antarmuka layanan Anda, ketahuilah bahwa:

  • Metode bisa mengambil nol atau beberapa parameter, dan mengembalikan nilai atau void.
  • Semua parameter non-primitif memerlukan tag pengarah yang menunjukkan arah tujuan data. Baik in, out, maupun inout (lihat contoh di bawah ini).

    Primitif secara default adalah in, dan tidak bisa sebaliknya.

    Perhatian: Anda harus membatasi arah mana yang benar-benar diperlukan, karena pengarahan parameter itu mahal.

  • Semua komentar kode yang disertakan dalam file .aidl dimasukkan dalam antarmuka IBinder yang dihasilkan (kecuali untuk komentar sebelum pernyataan impor dan paket).
  • String dan konstanta int bisa ditetapkan dalam antarmuka AIDL. Misalnya: const int VERSION = 1;.
  • Panggilan metode dikirim oleh kode transaksi, yang biasanya berdasarkan indeks metode di antarmuka. Karena hal ini membuat penentuan versi sulit, Anda bisa secara manual menetapkan kode transaksi ke metode: void method() = 10;.
  • Beri anotasi argumen nullable atau kembali mengetik menggunakan @nullable.

Inilah contoh file .aidl:

// IRemoteService.aidl
package com.example.android

// Declare any non-default types here with import statements
/** Example service interface */
internal interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    val pid:Int

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    fun basicTypes(anInt:Int, aLong:Long, aBoolean:Boolean, aFloat:Float,
                 aDouble:Double, aString:String)
}

Cukup simpan file .aidl dalam direktori src/ project dan saat Anda membuat aplikasi, fitur SDK akan menghasilkan file antarmuka IBinder dalam direktori gen/ project Anda. Nama file yang dihasilkan sama dengan nama file .aidl, tetapi dengan ekstensi .java (misalnya, IRemoteService.aidl menghasilkan IRemoteService.java).

Jika Anda menggunakan Android Studio, build bertahap akan menghasilkan kelas binder yang nyaris seketika. Jika Anda tidak menggunakan Android Studio, maka fitur Gradle akan menghasilkan kelas binder bila nanti Anda membuat aplikasi—Anda harus membuat project dengan gradle assembleDebug (atau gradle assembleRelease) begitu Anda selesai menulis file .aidl, sehingga kode Anda bisa ditautkan dengan kelas yang dibuat.

2. Implementasikan antarmuka

Jika Anda membuat aplikasi, fitur Android SDK akan menghasilkan file antarmuka .java yang dinamai menurut file .aidl Anda. Antarmuka yang dihasilkan termasuk subkelas bernama Stub yang merupakan implementasi abstrak antarmuka induknya (misalnya, YourInterface.Stub) dan mendeklarasikan semua metode dari file .aidl.

Catatan: Stub juga mendefinisikan beberapa metode helper, terutama asInterface(), yang mengambil IBinder (biasanya yang diteruskan ke metode callback onServiceConnected() klien) dan mengembalikan instance antarmuka stub. Lihat bagian Memanggil Metode IPC untuk detail selengkapnya tentang cara membuat transmisi ini.

Untuk mengimplementasikan antarmuka yang dihasilkan dari .aidl, perluas antarmuka Binder yang dihasilkan (misalnya, YourInterface.Stub) dan implementasikan metode yang diwarisi dari file .aidl.

Inilah contoh implementasi antarmuka yang disebut IRemoteService (didefinisikan oleh contoh IRemoteService.aidl, di atas) dengan menggunakan instance anonim:

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing
    }
}

Java

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

Kini binder merupakan instance kelas Stub (sebuah Binder), yang mendefinisikan antarmuka PPK untuk layanan. Pada langkah berikutnya, instance ini diekspos ke klien sehingga mereka bisa berinteraksi dengan layanan.

Ada beberapa aturan yang perlu diketahui tentang kapan mengimplementasikan antarmuka AIDL Anda:

  • Panggilan masuk tidak dijamin akan dijalankan pada thread utama, jadi Anda perlu mempertimbangkan multithreading dari awal dan membangun layanan agar thread-safe.
  • Secara default, panggilan PPK serempak. Jika Anda tahu bahwa layanan memerlukan lebih dari beberapa milidetik untuk menyelesaikan permintaan, Anda jangan memanggilnya dari aktivitas thread utama, karena aplikasi bisa mogok (Android mungkin menampilkan dialog "Application is Not Responding")—biasanya Anda harus memanggilnya dari thread terpisah di klien.
  • Tidak ada pengecualian yang Anda lemparkan akan dikirim balik ke pemanggil.

3. Ekspos antarmuka ke klien

Setelah berhasil mengimplementasikan antarmuka untuk layanan, Anda perlu mengeksposnya ke klien agar bisa diikat. Untuk mengekspos antarmuka layanan Anda, perluas Service dan implementasikan onBind() untuk mengembalikan instance kelas yang mengimplementasikan Stub yang dihasilkan (seperti yang dibahas di bagian sebelumnya). Inilah contoh layanan yang mengekspos antarmuka contoh IRemoteService ke klien.

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing
        }
    }
}

Java

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

Kini, saat klien memanggil (misalnya aktivitas) memanggil bindService() untuk menghubungkan ke layanan ini, callback onServiceConnected() klien akan menerima instance binder yang dikembalikan oleh metode onBind() layanan.

Klien juga harus memiliki akses ke kelas antarmuka, sehingga jika klien dan layanan berada dalam aplikasi terpisah, maka aplikasi klien harus memiliki salinan file .aidl dalam direktori src/-nya (yang menghasilkan antarmuka android.os.Binder—yang memberi klien akses ke metode AIDL).

Saat klien menerima IBinder dalam callback onServiceConnected(), maka klien tersebut harus memanggil YourServiceInterface.Stub.asInterface(service) untuk mentransmisikan parameter yang dikembalikan ke tipe YourServiceInterface. Sebagai contoh:

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

Java

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

Untuk kode contoh selengkapnya, lihat kelas RemoteService.java dalam ApiDemos.

Meneruskan Objek melalui IPC

Jika Anda memiliki kelas yang ingin dikirim dari satu proses ke proses lain melalui antarmuka IPC, Anda bisa melakukannya. Akan tetapi, Anda harus memastikan bahwa kode kelas Anda tersedia untuk saluran IPC dan kelas Anda harus mendukung antarmuka Parcelable. Mendukung antarmuka Parcelable adalah penting karena memungkinkan sistem Android untuk mengurai objek menjadi primitif yang bisa diarahkan ke berbagai proses.

Untuk membuat kelas yang mendukung protokol Parcelable, Anda harus melakukan yang berikut ini:

  1. Buat agar kelas Anda mengimplementasikan antarmuka Parcelable.
  2. Implementasikan writeToParcel, yang akan mengambil status objek saat ini dan menuliskannya ke Parcel.
  3. Tambahkan kolom statis yang disebut CREATOR ke kelas Anda sebagai objek yang mengimplementasikan antarmuka Parcelable.Creator.
  4. Terakhir, buat file .aidl yang mendeklarasikan kelas parcelable Anda (seperti yang ditampilkan untuk file Rect.aidl, di bawah ini).

    Jika Anda menggunakan proses build khusus jangan tambahkan file .aidl ke build Anda. Sama dengan file header dalam bahasa C, file .aidl ini tidak dikompilasi.

AIDL menggunakan metode dan kolom ini dalam kode yang dihasilkannya untuk mengarahkan dan tidak mengarahkan objek Anda.

Misalnya, inilah file Rect.aidl untuk membuat kelas Rect yang parcelable:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

Berikut ini contoh cara kelas Rect mengimplementasikan protokol Parcelable.

Kotlin

import android.os.Parcel
import android.os.Parcelable

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect> {
            return Array(size) { Rect() }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

Java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

Pengarahan dalam kelas Rect cukup sederhana. Perhatikan metode lain di Parcel untuk melihat jenis nilai lainnya yang bisa Anda tuliskan ke Parcel.

Peringatan: Jangan lupa implikasi keamanan akibat menerima data dari proses lain. Dalam hal ini, Rect akan membaca empat angka dari Parcel, namun itu terserah Anda untuk memastikan angka tersebut ada dalam kisaran nilai yang bisa diterima untuk apa pun yang coba dilakukan pemanggil. Lihat Keamanan dan Izin untuk informasi selengkapnya tentang cara menjaga aplikasi tetap aman dari malware.

Metode dengan argumen Bundel yang berisi Parcelable

Jika aidl antarmuka Anda menyertakan metode yang menerima Bundel sebagai argumen yang diharapkan berisi parcelable, pastikan Anda menyetel classloader Paket dengan memanggil Bundle.setClassLoader(ClassLoader) sebelum mencoba membaca dari Bundel. Jika tidak, Anda akan menemukan ClassNotFoundException meskipun parcelable ditetapkan dengan benar dalam aplikasi Anda. Misalnya,

Jika Anda memiliki file .aidl:

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect" */
    void saveRect(in Bundle bundle);
}
Seperti yang bisa dilihat dari implementasi di bawah ini, ClassLoader sedang disetel secara eksplisit dalam Bundle sebelum membaca Rect

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

Memanggil metode IPC

Berikut ini langkah-langkah yang harus diikuti oleh kelas pemanggil untuk memanggil antarmuka jauh yang didefinisikan dengan AIDL:

  1. Sertakan file .aidl dalam direktori src/ project.
  2. Deklarasikan instance antarmuka IBinder (yang dihasilkan berdasarkan AIDL).
  3. Implementasikan ServiceConnection.
  4. Panggil Context.bindService(), meneruskan implementasi ServiceConnection Anda.
  5. Dalam implementasi onServiceConnected(), Anda akan menerima instance IBinder (yang disebut service). Panggil YourInterfaceName.Stub.asInterface((IBinder)service) untuk mentransmisikan parameter yang dikembalikan menjadi tipe YourInterface.
  6. Panggil metode yang Anda definisikan pada antarmuka Anda. Anda harus selalu menjebak pengecualian DeadObjectException, yang dilontarkan jika koneksi terputus. Anda juga harus menjebak pengecualian SecurityException, yang dilontarkan jika dua proses yang terlibat dalam panggilan metode IPC memiliki definisi AIDL yang bertentangan.
  7. Untuk memutus koneksi, panggil Context.unbindService() dengan instance antarmuka Anda.

Beberapa komentar tentang pemanggilan layanan IPC:

  • Objek adalah acuan yang dihitung lintas proses.
  • Anda bisa mengirimkan objek anonim sebagai argumen metode.

Untuk informasi selengkapnya tentang menautkan ke layanan, baca dokumen Layanan Tertaut.

Berikut ini beberapa kode contoh yang memperagakan pemanggilan layanan buatan AIDL, yang diambil dari contoh Remote Service dalam project ApiDemos.

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface we will be calling on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface we use on the service.  */
    internal var secondaryService: ISecondary? = null

    private lateinit var killButton: Button
    private lateinit var callbackText: TextView
    private lateinit var handler: InternalHandler

    private var isBound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null
            killButton.isEnabled = false
            callbackText.text = "Disconnected."

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT
            ).show()
        }
    }

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private val secondaryConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service)
            killButton.isEnabled = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            secondaryService = null
            killButton.isEnabled = false
        }
    }

    private val mBindListener = View.OnClickListener {
        // Establish a couple connections with the service, binding
        // by interface names.  This allows other applications to be
        // installed that replace the remote service by implementing
        // the same interface.
        val intent = Intent(this@Binding, RemoteService::class.java)
        intent.action = IRemoteService::class.java.name
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        intent.action = ISecondary::class.java.name
        bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE)
        isBound = true
        callbackText.text = "Binding."
    }

    private val unbindListener = View.OnClickListener {
        if (isBound) {
            // If we have received the service, and hence registered with
            // it, then now is the time to unregister.
            try {
                mService?.unregisterCallback(mCallback)
            } catch (e: RemoteException) {
                // There is nothing special we need to do if the service
                // has crashed.
            }

            // Detach our existing connection.
            unbindService(mConnection)
            unbindService(secondaryConnection)
            killButton.isEnabled = false
            isBound = false
            callbackText.text = "Unbinding."
        }
    }

    private val killListener = View.OnClickListener {
        // To kill the process hosting our service, we need to know its
        // PID.  Conveniently our service has a call that will return
        // to us that information.
        try {
            secondaryService?.pid?.also { pid ->
                // Note that, though this API allows us to request to
                // kill any process based on its PID, the kernel will
                // still impose standard restrictions on which PIDs you
                // are actually able to kill.  Typically this means only
                // the process running your application and any additional
                // processes created by that app as shown here; packages
                // sharing a common UID will also be able to kill each
                // other's processes.
                Process.killProcess(pid)
                callbackText.text = "Killed service process."
            }
        } catch (ex: RemoteException) {
            // Recover gracefully from the process hosting the
            // server dying.
            // Just for purposes of the sample, put up a notification.
            Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show()
        }
    }

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private val mCallback = object : IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        override fun valueChanged(value: Int) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0))
        }
    }

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button clicks.
        var button: Button = findViewById(R.id.bind)
        button.setOnClickListener(mBindListener)
        button = findViewById(R.id.unbind)
        button.setOnClickListener(unbindListener)
        killButton = findViewById(R.id.kill)
        killButton.setOnClickListener(killListener)
        killButton.isEnabled = false

        callbackText = findViewById(R.id.callback)
        callbackText.text = "Not attached."
        handler = InternalHandler(callbackText)
    }

    private class InternalHandler(
            textView: TextView,
            private val weakTextView: WeakReference<TextView> = WeakReference(textView)
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}"
                else -> super.handleMessage(msg)
            }
        }
    }
}

Java

public static class Binding extends Activity {
    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary secondaryService = null;

    Button killButton;
    TextView callbackText;

    private InternalHandler handler;
    private boolean isBound;

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(unbindListener);
        killButton = (Button)findViewById(R.id.kill);
        killButton.setOnClickListener(killListener);
        killButton.setEnabled(false);

        callbackText = (TextView)findViewById(R.id.callback);
        callbackText.setText("Not attached.");
        handler = new InternalHandler(callbackText);
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            killButton.setEnabled(true);
            callbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            killButton.setEnabled(false);
            callbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection secondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            secondaryService = ISecondary.Stub.asInterface(service);
            killButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            secondaryService = null;
            killButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE);
            isBound = true;
            callbackText.setText("Binding.");
        }
    };

    private OnClickListener unbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (isBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(secondaryConnection);
                killButton.setEnabled(false);
                isBound = false;
                callbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener killListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently our service has a call that will return
            // to us that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.getPid();
                    // Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here; packages
                    // sharing a common UID will also be able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    callbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // Just for purposes of the sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private static class InternalHandler extends Handler {
        private final WeakReference<TextView> weakTextView;

        InternalHandler(TextView textView) {
            weakTextView = new WeakReference<>(textView);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    TextView textView = weakTextView.get();
                    if (textView != null) {
                        textView.setText("Received from service: " + msg.arg1);
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }
}