Android Interface Definition Language (AIDL)

Android Interface Definition Language (AIDL) mirip dengan IDL: memungkinkan Anda untuk menentukan antarmuka pemrograman yang disepakati oleh klien dan layanan untuk berkomunikasi satu sama lain menggunakan komunikasi antarproses (IPC).

Di Android, satu proses biasanya tidak dapat mengakses memori proses lain. Agar dapat berkomunikasi, mereka perlu mengurai objek mereka menjadi primitif yang sistem operasi dapat memahami dan mengarahkan objek-objek yang melintasi batasan tersebut untuk Anda. Kode untuk melakukan marshalling itu membosankan, jadi Android akan menanganinya untuk Anda dengan AIDL.

Catatan: AIDL hanya diperlukan jika Anda mengizinkan klien dari aplikasi yang berbeda mengakses layanan Anda untuk IPC dan Anda ingin menangani multithreading di layanan. Jika Anda tidak perlu melakukan IPC serentak di aplikasi yang berbeda, buat antarmuka Anda dengan menerapkan Binder. Jika Anda ingin melakukan IPC tetapi tidak perlu menangani multithreading, implementasikan antarmuka Anda menggunakan Messenger. Terlepas dari itu, pastikan Anda memahami layanan terikat sebelum menerapkan AIDL.

Sebelum Anda mulai merancang antarmuka AIDL, ketahuilah bahwa panggilan ke antarmuka AIDL panggilan fungsi secara langsung. Jangan berasumsi tentang thread tempat panggilan apa yang terjadi. Yang terjadi berbeda bergantung pada apakah panggilan berasal dari thread di proses lokal atau proses jarak jauh:

  • Panggilan yang dilakukan dari proses lokal dieksekusi dalam thread yang sama yang melakukan panggilan. Jika ini adalah thread UI utama Anda, thread tersebut terus mengeksekusi di antarmuka AIDL. Jika ya thread lain, yaitu thread yang mengeksekusi kode Anda dalam layanan ini. Jadi, jika hanya lokal {i>thread<i} mengakses layanan, Anda bisa sepenuhnya mengontrol thread mana yang dieksekusi di dalamnya. Tapi jika demikian, jangan gunakan AIDL sama sekali; sebagai gantinya, buat antarmuka pengguna dengan menerapkan Binder.
  • Panggilan dari proses jarak jauh dikirimkan dari kumpulan thread yang dikelola oleh platform di dalamnya proses Anda sendiri. Bersiap untuk panggilan masuk dari thread yang tidak dikenal, dengan beberapa panggilan yang bersamaan. Dengan kata lain, implementasi antarmuka AIDL harus sepenuhnya aman untuk thread. Panggilan yang dilakukan dari satu thread pada objek jarak jauh yang sama tiba dalam urutan di sisi penerima.
  • Kata kunci oneway mengubah perilaku panggilan jarak jauh. Ketika digunakan, panggilan jarak jauh melakukan jangan diblokir. Ini mengirimkan data transaksi dan segera kembali. Implementasi antarmuka pada akhirnya menerima ini sebagai panggilan biasa dari kumpulan thread Binder seperti panggilan jarak jauh biasa. Jika oneway digunakan dengan panggilan lokal, tidak ada dampak, dan panggilan tetap sinkron.

Mendefinisikan antarmuka AIDL

Menentukan antarmuka AIDL dalam file .aidl menggunakan Java sintaks bahasa pemrograman, lalu menyimpannya dalam kode sumber, di direktori src/, aplikasi yang menghosting layanan dan aplikasi lain yang mengikat ke layanan.

Saat Anda membangun setiap aplikasi yang berisi file .aidl, Android SDK Tools membuat antarmuka IBinder berdasarkan file .aidl dan menyimpannya di direktori gen/ project. Layanan harus mengimplementasikan IBinder antarmuka yang sesuai. Aplikasi klien kemudian bisa mengikat ke layanan dan memanggil metode dari IBinder untuk melakukan IPC.

Untuk membuat layanan terikat menggunakan AIDL, ikuti langkah-langkah berikut, yang dijelaskan di bagian berikut ini:

  1. Membuat file .aidl

    File ini mendefinisikan antarmuka pemrograman dengan tanda tangan metode.

  2. Mengimplementasikan antarmuka

    Android SDK Tools membuat antarmuka dalam bahasa pemrograman Java berdasarkan File .aidl. Antarmuka ini memiliki class abstrak bagian dalam bernama Stub yang memperluas Binder dan mengimplementasikan metode dari antarmuka AIDL Anda. Anda harus memperpanjang Stub dan mengimplementasikan metode tersebut.

  3. Mengekspos antarmuka ke klien

    Implementasikan Service dan ganti onBind() untuk menampilkan implementasi Stub Anda .

Perhatian: Setiap perubahan yang Anda lakukan pada antarmuka AIDL setelah rilis pertama Anda harus tetap kompatibel dengan versi sebelumnya agar tidak merusak aplikasi lain yang menggunakan layanan Anda. Artinya, karena file .aidl Anda harus disalin ke aplikasi lain agar mereka dapat mengakses antarmuka layanan, Anda harus mempertahankan dukungan untuk dalam antarmuka berbasis web yang sederhana.

Buat file .aidl

AIDL menggunakan sintaks sederhana yang memungkinkan Anda mendeklarasikan antarmuka dengan satu atau mengambil parameter dan mengembalikan nilai. Parameter dan nilai yang dikembalikan dapat berupa jenis apa pun, bahkan Antarmuka yang dihasilkan AIDL.

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

Secara default, AIDL mendukung tipe data berikut:

  • Semua jenis primitif dalam bahasa pemrograman Java (seperti int, long, char, boolean, dan sebagainya)
  • Array jenis primitif, seperti int[]
  • String
  • CharSequence
  • List

    Semua elemen dalam List harus berupa salah satu jenis data yang didukung dalam atau salah satu antarmuka lain yang dihasilkan AIDL atau parcelable yang Anda deklarasikan. J Secara opsional, List dapat digunakan sebagai class jenis berparameter, seperti List<String>. Class konkret sebenarnya yang diterima pihak lain selalu ArrayList, meskipun akan dibuat untuk menggunakan antarmuka List.

  • Map

    Semua elemen dalam Map harus berupa salah satu jenis data yang didukung dalam atau salah satu antarmuka lain yang dihasilkan AIDL atau parcelable yang Anda deklarasikan. Peta jenis berparameter, seperti pada formulir Map<String,Integer>, tidak didukung. Kelas konkret yang sebenarnya yang digunakan sisi lain terima selalu berupa HashMap, meskipun metode tersebut dibuat untuk menggunakan antarmuka Map. Pertimbangkan untuk menggunakan Bundle sebagai alternatif Map.

Anda harus menyertakan pernyataan import untuk setiap jenis tambahan yang tidak tercantum sebelumnya, meskipun mereka didefinisikan dalam paket yang sama dengan antarmuka Anda.

Saat mendefinisikan antarmuka layanan Anda, ketahuilah bahwa:

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

    Primitif, String, IBinder, dan buatan AIDL antarmuka adalah in secara default dan tidak dapat sebaliknya.

    Perhatian: Batasi arah yang benar-benar diperlukan, karena pengarahan parameter itu mahal.

  • Semua komentar kode yang disertakan dalam file .aidl disertakan dalam menghasilkan IBinder antarmuka kecuali komentar sebelum impor dan paket pernyataan pribadi Anda.
  • Konstanta string dan int dapat ditentukan dalam antarmuka AIDL, seperti const int VERSION = 1;.
  • Panggilan metode dikirim oleh transact() kode, yang biasanya didasarkan pada indeks metode di antarmuka. Karena ini membuat pembuatan versi menjadi sulit, Anda dapat menetapkan kode transaksi secara manual ke metode: void method() = 10;.
  • Argumen yang nullable dan jenis nilai yang ditampilkan harus dianotasi menggunakan @nullable.

Berikut adalah contoh file .aidl:

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

// Declare any non-default types here with import statements.

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

Simpan file .aidl di direktori src/ project Anda. Jika Anda mem-build aplikasi Anda, SDK Tools akan menghasilkan file antarmuka IBinder di pada direktori gen/ project. Nama file yang dihasilkan cocok 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, alat Gradle akan menghasilkan class binder pada saat berikutnya Anda membangun aplikasi Anda. Membuat project Anda dengan gradle assembleDebug atau gradle assembleRelease segera setelah Anda selesai menulis file .aidl, sehingga kode Anda dapat ditautkan ke class yang dihasilkan.

Mengimplementasikan antarmuka

Saat Anda membangun aplikasi, Android SDK Tools akan menghasilkan file antarmuka .java yang dinamai berdasarkan file .aidl Anda. Antarmuka yang dihasilkan menyertakan subclass bernama Stub yang merupakan implementasi abstrak antarmuka induknya, seperti YourInterface.Stub, dan mendeklarasikan semua metode dari file .aidl.

Catatan: Stub juga menentukan beberapa metode bantuan, terutama asInterface(), yang membutuhkan IBinder, biasanya yang diteruskan ke metode callback onServiceConnected() klien, dan akan mengembalikan instance antarmuka stub. Untuk detail selengkapnya tentang cara melakukan transmisi ini, lihat bagian Memanggil IPC metode.

Untuk menerapkan antarmuka yang dihasilkan dari .aidl, perluas Binder yang dihasilkan antarmuka pengguna, seperti YourInterface.Stub, dan mengimplementasikan metode diwarisi dari file .aidl.

Berikut adalah contoh implementasi antarmuka bernama IRemoteService, yang ditentukan oleh Contoh IRemoteService.aidl, 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.
    }
};

Sekarang binder adalah instance class Stub (Binder), yang menentukan antarmuka IPC untuk layanan. Pada langkah berikutnya, instance ini diekspos klien sehingga mereka dapat berinteraksi dengan layanan tersebut.

Perhatikan beberapa aturan saat menerapkan antarmuka AIDL Anda:

  • Panggilan masuk tidak dijamin akan dijalankan di thread utama, jadi Anda perlu mempertimbangkan multithreading sejak awal dan membangun layanan Anda dengan benar agar aman untuk thread.
  • Secara default, panggilan IPC bersifat sinkron. Jika Anda tahu bahwa layanan membutuhkan waktu lebih dari milidetik untuk menyelesaikan permintaan, jangan memanggilnya dari thread utama aktivitas. Aplikasi mungkin macet, sehingga Android menampilkan "Aplikasi Tidak Merespons" dialog. Memanggil dari thread terpisah di klien.
  • Hanya jenis pengecualian yang tercantum di dokumentasi referensi untuk Parcel.writeException() dikirim kembali ke pemanggil.

Ekspos antarmuka ke klien

Setelah Anda mengimplementasikan antarmuka untuk layanan Anda, Anda perlu mengeksposnya ke klien sehingga mereka dapat mengikatnya. Untuk mengekspos antarmuka untuk layanan Anda, perluas Service dan implementasikan onBind() untuk menampilkan instance class yang mengimplementasikan Stub yang dihasilkan, seperti yang dibahas di bagian sebelumnya. Berikut ini contohnya 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.
        }
    };
}

Sekarang, saat klien, seperti aktivitas, memanggil bindService() untuk terhubung ke layanan ini, callback onServiceConnected() klien akan menerima metode Instance binder yang ditampilkan oleh onBind() layanan .

Klien juga harus memiliki akses ke class antarmuka. Jadi, jika klien dan layanan berada dalam aplikasi terpisah, aplikasi klien harus memiliki salinan file .aidl dalam direktori src/-nya, yang menghasilkan android.os.Binder klien, yang menyediakan akses ke metode AIDL.

Saat klien menerima IBinder dalam callback onServiceConnected(), callback harus memanggil YourServiceInterface.Stub.asInterface(service) untuk mentransmisikan item yang ditampilkan ke jenis YourServiceInterface:

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 preceding example 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 preceding example 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 Class RemoteService.java di ApiDemos.

Meneruskan Objek melalui IPC

Di Android 10 (API level 29 atau yang lebih tinggi), Anda dapat menentukan Parcelable objek langsung di AIDL. Jenis yang didukung sebagai argumen antarmuka AIDL dan parcelable lainnya juga didukung di sini. Hal ini menghindari pekerjaan tambahan untuk menulis kode marshalling secara manual dan . Namun, ini juga menciptakan struct sederhana. Jika pengakses kustom atau fungsi lainnya diinginkan, implementasikan Parcelable sebagai gantinya.

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect {
    int left;
    int top;
    int right;
    int bottom;
}

Contoh kode sebelumnya otomatis membuat class Java dengan kolom bilangan bulat left, top, right, dan bottom. Semua kode marshalling yang relevan diimplementasikan secara otomatis, dan objek tersebut dapat digunakan secara langsung tanpa harus menambahkan terlepas dari implementasi layanan.

Anda juga dapat mengirim class kustom dari satu proses ke proses lainnya melalui antarmuka IPC. Namun, pastikan kode untuk kelas Anda tersedia di sisi lain saluran IPC dan class Anda harus mendukung antarmuka Parcelable. Didukung Parcelable penting karena memungkinkan sistem Android mengurai objek menjadi primitif yang dapat diarahkan lintas proses.

Untuk membuat class kustom yang mendukung Parcelable, lakukan berikut ini:

  1. Buat class Anda menerapkan antarmuka Parcelable.
  2. Implementasikan writeToParcel, yang menggunakan status objek saat ini dan menulisnya ke Parcel.
  3. Tambahkan kolom statis bernama CREATOR ke class Anda yang merupakan objek yang mengimplementasikan antarmuka Parcelable.Creator.
  4. Terakhir, buat file .aidl yang mendeklarasikan class parcelable Anda, seperti yang ditunjukkan di bawah ini File Rect.aidl.

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

AIDL menggunakan metode dan kolom ini dalam kode yang dihasilkannya untuk melakukan marshall dan unmarshall objek Anda.

Misalnya, berikut adalah file Rect.aidl untuk membuat class Rect yang {i>parcelable<i}:

package android.graphics;

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

Berikut adalah contoh cara class 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) { null }
        }
    }

    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 class Rect sangat mudah. Lihat lainnya metode di Parcel untuk melihat jenis nilai lain yang dapat Anda tulis menjadi Parcel.

Peringatan: Ingatlah implikasi keamanan dari penerimaan data dari proses lain. Dalam hal ini, Rect membaca empat angka dari Parcel, tetapi Anda harus memastikan angka tersebut berada dalam rentang yang dapat diterima nilai untuk apa pun yang ingin dilakukan pemanggil. Untuk informasi selengkapnya tentang cara menjaga aplikasi tetap aman dari malware, lihat Tips keamanan.

Metode dengan argumen Bundel yang berisi Parcelable

Jika metode menerima objek Bundle yang diharapkan berisi parcelable, pastikan Anda menetapkan classloader Bundle dengan memanggil Bundle.setClassLoader(ClassLoader) sebelum mencoba membaca dari Bundle. Jika tidak, Anda akan mengalami ClassNotFoundException meskipun parcelable ditentukan dengan benar di aplikasi Anda.

Misalnya, perhatikan contoh file .aidl berikut:

// 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 ditunjukkan dalam implementasi berikut, ClassLoader adalah secara eksplisit ditetapkan 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

Untuk memanggil antarmuka jarak jauh yang didefinisikan dengan AIDL, lakukan langkah-langkah berikut kelas panggilan Anda:

  1. Sertakan file .aidl dalam direktori src/ project.
  2. Deklarasikan instance antarmuka IBinder, yang dihasilkan berdasarkan AIDL.
  3. Implementasikan ServiceConnection.
  4. Panggil Context.bindService(), yang meneruskan implementasi ServiceConnection.
  5. Dalam implementasi onServiceConnected(), Anda menerima IBinder yang disebut service. Telepon YourInterfaceName.Stub.asInterface((IBinder)service) hingga mentransmisikan parameter yang ditampilkan ke jenis YourInterface.
  6. Panggil metode yang Anda definisikan pada antarmuka Anda. Selalu jebakan Pengecualian DeadObjectException, yang ditampilkan saat koneksi terputus. Selain itu, pengecualian SecurityException jebakan, yang ditampilkan saat dua proses yang terlibat dalam panggilan metode IPC memiliki definisi AIDL yang bertentangan.
  7. Untuk memutuskan koneksi, panggil Context.unbindService() dengan instance antarmuka Anda.

Ingatlah poin-poin berikut saat memanggil layanan IPC:

  • Objek adalah acuan yang dihitung lintas proses.
  • Anda dapat mengirim objek anonim sebagai argumen metode.

Untuk mengetahui informasi selengkapnya tentang binding ke layanan, baca Ringkasan layanan terikat.

Berikut ini beberapa kode contoh yang menunjukkan 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 you call on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface you 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 is
            // 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 crashes before we can
                // 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 is
            // unexpectedly disconnected&mdash;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 lets other applications 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
                // crashes.
            }

            // 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 the service, we need to know its
        // PID.  Conveniently, the service has a call that returns
        // that information.
        try {
            secondaryService?.pid?.also { pid ->
                // Note that, though this API lets us request to
                // kill any process based on its PID, the kernel
                // still imposes standard restrictions on which PIDs you
                // can actually 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 are also 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.
            // For purposes of this 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 is
         * NOT 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 interact with it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button taps.
        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 are 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 interact with it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button taps.
        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 is
            // 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 crashes before we can 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 is
            // unexpectedly disconnected&mdash;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 lets other applications 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
                        // crashes.
                    }
                }

                // 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 returns
            // that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.getPid();
                    // Note that, though this API lets us request to
                    // kill any process based on its PID, the kernel
                    // still imposes standard restrictions on which PIDs you
                    // can actually 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 are also 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.
                    // For purposes of this 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 is
         * NOT 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);
            }
        }
    }
}