Android Interface Definition Language (AIDL) mirip dengan IDL lainnya: memungkinkan Anda 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. Untuk berkomunikasi, objek tersebut harus mengurai objek menjadi primitif yang dapat dipahami dan mengarahkan sistem operasi kepada objek melintasi batasan tersebut untuk Anda. Menulis kode untuk melakukan marshalling itu membosankan, jadi Android menanganinya untuk Anda dengan AIDL.
Catatan: AIDL hanya diperlukan jika Anda mengizinkan klien dari
aplikasi yang berbeda mengakses layanan untuk IPC dan Anda ingin menangani multithreading dalam
layanan. Jika Anda tidak perlu melakukan IPC serentak
di berbagai aplikasi, buat antarmuka dengan mengimplementasikan
Binder
.
Jika ingin melakukan IPC, tetapi tidak perlu menangani multithreading,
implementasikan antarmuka Anda menggunakan Messenger
.
Terlepas dari itu, pastikan Anda memahami layanan terikat sebelum
mengimplementasikan AIDL.
Sebelum mulai mendesain antarmuka AIDL, perhatikan bahwa panggilan ke antarmuka AIDL merupakan panggilan fungsi langsung. Jangan membuat asumsi tentang thread tempat panggilan terjadi. Hal yang terjadi tidak sama, bergantung pada apakah panggilan berasal dari thread dalam proses lokal atau proses jarak jauh:
- Panggilan yang dilakukan dari proses lokal dieksekusi di thread yang sama yang melakukan panggilan. Jika
ini adalah UI thread utama Anda, thread tersebut akan terus dieksekusi di antarmuka AIDL. Jika itu
thread lain, thread itulah yang mengeksekusi kode Anda dalam layanan ini. Oleh karena itu, jika hanya thread lokal
yang mengakses layanan, Anda dapat sepenuhnya mengontrol thread mana yang dieksekusi di dalamnya. Namun,
jika demikian, jangan gunakan AIDL sama sekali; sebagai gantinya, buat
antarmuka dengan mengimplementasikan
Binder
. - Panggilan dari proses jarak jauh dikirim dari pool thread yang dikelola oleh platform dalam proses Anda sendiri. Bersiaplah untuk panggilan masuk dari thread yang tidak dikenal, dengan beberapa panggilan yang terjadi bersamaan. Dengan kata lain, implementasi antarmuka AIDL harus benar-benar thread-safe. Panggilan yang dilakukan dari satu thread pada objek jarak jauh yang sama akan tiba secara berurutan di sisi penerima.
- Kata kunci
oneway
mengubah perilaku panggilan jarak jauh. Saat digunakan, panggilan jarak jauh tidak akan memblokir. Klien mengirimkan data transaksi dan segera kembali. Implementasi antarmuka tersebut pada akhirnya menerima ini sebagai panggilan biasa dari pool threadBinder
seperti panggilan jarak jauh biasa. Jikaoneway
digunakan dengan panggilan lokal, tidak akan ada dampak apa pun dan panggilan tersebut masih sinkron.
Mendefinisikan antarmuka AIDL
Tentukan antarmuka AIDL Anda dalam file .aidl
menggunakan sintaksis bahasa
pemrograman Java, lalu simpan dalam kode sumber, di direktori src/
, untuk
aplikasi yang menghosting layanan dan aplikasi lain yang mengikat ke layanan.
Jika Anda membangun setiap aplikasi yang berisi file .aidl
, Android SDK Tools
akan membuat antarmuka IBinder
berdasarkan file .aidl
dan menyimpannya dalam
direktori gen/
project. Layanan harus mengimplementasikan antarmuka
IBinder
sebagaimana mestinya. Aplikasi klien kemudian dapat mengikat ke layanan tersebut dan memanggil metode dari
IBinder
untuk menjalankan IPC.
Untuk membuat layanan terikat menggunakan AIDL, ikuti langkah-langkah berikut yang dijelaskan di bagian berikut:
- Membuat file
.aidl
File ini mendefinisikan antarmuka pemrograman dengan tanda tangan metode.
- Mengimplementasikan antarmuka
Android SDK Tools menghasilkan antarmuka dalam bahasa pemrograman Java berdasarkan file
.aidl
Anda. Antarmuka ini memiliki class abstrak dalam bernamaStub
yang memperluasBinder
dan mengimplementasikan metode dari antarmuka AIDL Anda. Anda harus memperluas classStub
dan mengimplementasikan metode tersebut. - Mengekspos antarmuka ke klien
Terapkan
Service
dan gantionBind()
untuk menampilkan implementasi classStub
.
Perhatian: Setiap perubahan yang Anda buat pada antarmuka AIDL setelah
rilis pertama harus tetap kompatibel dengan versi lama agar tidak merusak aplikasi lain
yang menggunakan layanan Anda. Artinya, karena file .aidl
Anda harus disalin ke aplikasi lain
agar dapat mengakses antarmuka layanan, Anda harus mempertahankan dukungan untuk antarmuka
asli.
Buat file .aidl
AIDL menggunakan sintaksis sederhana yang memungkinkan Anda mendeklarasikan antarmuka dengan satu atau beberapa metode yang dapat mengambil parameter dan menampilkan nilai. Parameter dan nilai yang ditampilkan bisa berjenis apa pun, bahkan antarmuka lain yang dihasilkan AIDL.
Anda harus membuat file .aidl
menggunakan bahasa pemrograman Java. Setiap file .aidl
harus menentukan satu antarmuka dan hanya memerlukan deklarasi antarmuka dan tanda tangan
metode.
Secara default, AIDL mendukung tipe data berikut:
- Semua jenis primitif dalam bahasa pemrograman Java (seperti
int
,long
,char
,boolean
, dan seterusnya) - Array jenis primitif, seperti
int[]
String
CharSequence
List
Semua elemen dalam
List
harus berupa salah satu jenis data yang didukung dalam daftar ini atau salah satu antarmuka lain yang dihasilkan AIDL atau parcelable yang Anda deklarasikan. Secara opsional,List
dapat digunakan sebagai class jenis berparameter, sepertiList<String>
. Class konkret sebenarnya yang diterima pihak lain selaluArrayList
, meskipun metode itu dibuat untuk menggunakan antarmukaList
.Map
Semua elemen dalam
Map
harus berupa salah satu jenis data yang didukung dalam daftar ini atau salah satu antarmuka lain yang dihasilkan AIDL atau parcelable yang Anda deklarasikan. Peta jenis berparameter, seperti yang berbentukMap<String,Integer>
, tidak didukung. Class konkret sebenarnya yang diterima pihak lain selaluHashMap
, meskipun metodenya dibuat untuk menggunakan antarmukaMap
. Sebaiknya gunakanBundle
sebagai alternatif untukMap
.
Anda harus menyertakan pernyataan import
untuk setiap jenis tambahan yang tidak tercantum sebelumnya,
meskipun ditentukan dalam paket yang sama dengan antarmuka Anda.
Saat mendefinisikan antarmuka layanan Anda, ketahuilah bahwa:
- Metode dapat mengambil nol parameter atau lebih dan dapat mengembalikan nilai atau void.
- Semua parameter non-primitif memerlukan tag arah yang menunjukkan arah tujuan data:
in
,out
, atauinout
(lihat contoh di bawah).Antarmuka primitif,
String
,IBinder
, dan AIDL akan bernilaiin
secara default dan tidak dapat sebaliknya.Perhatian: Batasi arah pada hal yang benar-benar diperlukan, karena pengarahan parameter membutuhkan biaya yang mahal.
- Semua komentar kode yang disertakan dalam file
.aidl
disertakan dalam antarmukaIBinder
yang dihasilkan, kecuali komentar sebelum pernyataan impor dan paket. - String dan konstanta int dapat ditentukan dalam antarmuka AIDL, seperti
const int VERSION = 1;
. - Panggilan metode dikirimkan oleh
kode
transact()
, yang biasanya didasarkan pada indeks metode di antarmuka. Karena hal ini mempersulit pembuatan versi, Anda dapat menetapkan kode transaksi secara manual ke metode:void method() = 10;
. - Argumen 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. Saat Anda
mem-build aplikasi, alat SDK akan menghasilkan file antarmuka IBinder
di
direktori gen/
project Anda. 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
mem-build aplikasi. Build 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 mem-build aplikasi, Android SDK Tools akan menghasilkan file antarmuka .java
yang dinamai berdasarkan file .aidl
Anda. Antarmuka yang dihasilkan mencakup 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 mengambil IBinder
, biasanya yang diteruskan ke metode callback onServiceConnected()
klien, dan
menampilkan instance antarmuka stub. Untuk detail selengkapnya tentang cara membuat transmisi ini, lihat bagian Memanggil metode
IPC.
Untuk mengimplementasikan antarmuka yang dihasilkan dari .aidl
, perluas antarmuka Binder
yang dihasilkan, seperti YourInterface.Stub
, dan terapkan metode
yang diwarisi dari file .aidl
.
Berikut adalah contoh implementasi antarmuka yang disebut IRemoteService
, yang ditentukan oleh contoh IRemoteService.aidl
sebelumnya, 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 dari class Stub
(Binder
),
yang menentukan antarmuka IPC untuk layanan. Pada langkah berikutnya, instance ini diekspos ke klien sehingga mereka dapat berinteraksi dengan layanan.
Perhatikan beberapa aturan saat mengimplementasikan antarmuka AIDL Anda:
- Panggilan masuk tidak dijamin akan dijalankan pada thread utama, jadi Anda harus memikirkan multithreading dari awal dan membangun layanan dengan benar agar aman untuk thread.
- Secara default, panggilan IPC bersifat sinkron. Jika Anda mengetahui bahwa layanan memerlukan waktu lebih dari beberapa milidetik untuk menyelesaikan permintaan, jangan memanggilnya dari thread utama aktivitas. Tindakan tersebut dapat menyebabkan aplikasi hang, sehingga Android menampilkan dialog "Aplikasi Tidak Merespons". Memanggil dari thread terpisah di klien.
- Hanya jenis pengecualian yang tercantum dalam dokumentasi referensi untuk
Parcel.writeException()
yang dikirim kembali ke pemanggil.
Ekspos antarmuka ke klien
Setelah mengimplementasikan antarmuka untuk layanan, Anda harus mengeksposnya ke
klien agar mereka dapat mengikatnya. Untuk menampilkan antarmuka
layanan Anda, perluas Service
dan implementasikan onBind()
untuk menampilkan instance class yang mengimplementasikan
Stub
yang dihasilkan, seperti yang dibahas di bagian sebelumnya. Berikut ini contoh
layanan yang mengekspos antarmuka contoh IRemoteService
kepada 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
instance binder
yang ditampilkan oleh metode onBind()
layanan.
Klien juga harus memiliki akses ke class antarmuka. Jadi, jika klien dan layanan berada dalam
aplikasi yang terpisah, aplikasi klien tersebut harus memiliki salinan file .aidl
di direktori src/
-nya, yang menghasilkan antarmuka
android.os.Binder
, yang memberi klien akses ke metode AIDL.
Saat klien menerima IBinder
dalam callback onServiceConnected()
, klien harus memanggil
YourServiceInterface.Stub.asInterface(service)
untuk mentransmisikan parameter 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 lainnya, lihat class
RemoteService.java
dalam
ApiDemos.
Meneruskan Objek melalui IPC
Di Android 10 (API level 29 atau yang lebih baru), Anda dapat menentukan
objek Parcelable
secara langsung di
AIDL. Jenis yang didukung sebagai argumen antarmuka AIDL dan parcelable lainnya juga
didukung di sini. Dengan demikian, Anda tidak perlu melakukan pekerjaan tambahan untuk menulis kode marshalling dan class kustom secara manual. Namun, tindakan ini juga akan membuat struktur kosong. 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 secara otomatis menghasilkan class Java dengan kolom bilangan bulat left
,
top
, right
, dan bottom
. Semua kode marshalling yang relevan
diimplementasikan secara otomatis, dan objek dapat digunakan langsung tanpa harus menambahkan
implementasi apa pun.
Anda juga bisa mengirim class kustom dari satu proses ke proses lainnya melalui antarmuka IPC. Namun,
pastikan kode untuk class Anda tersedia untuk saluran IPC dan
class Anda harus mendukung antarmuka Parcelable
. Mendukung
Parcelable
itu penting
karena memungkinkan sistem Android mengurai objek menjadi primitif yang dapat diarahkan
ke seluruh proses.
Untuk membuat class kustom yang mendukung Parcelable
, lakukan
hal berikut:
- Buat class Anda mengimplementasikan antarmuka
Parcelable
. - Implementasikan
writeToParcel
, yang mengambil status objek saat ini dan menuliskannya keParcel
. - Tambahkan kolom statis bernama
CREATOR
ke class Anda yang merupakan objek yang mengimplementasikan antarmukaParcelable.Creator
. - Terakhir, buat file
.aidl
yang mendeklarasikan class parcelable Anda, seperti yang ditunjukkan untuk fileRect.aidl
berikut.Jika Anda menggunakan proses build kustom, jangan tambahkan file
.aidl
ke build Anda. Mirip 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, berikut adalah file Rect.aidl
untuk membuat class Rect
yang
dapat dibagi-bagi:
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
menerapkan
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; } }
Marshalling di class Rect
sangat mudah dilakukan. Lihat metode lain di Parcel
untuk melihat jenis nilai lain yang dapat Anda tulis ke Parcel
.
Peringatan: Ingatlah implikasi keamanan jika menerima data dari proses lain. Dalam hal ini, Rect
akan membaca empat angka dari Parcel
, tetapi Anda harus memastikan angka tersebut berada dalam rentang nilai yang dapat diterima
untuk apa pun yang coba dilakukan pemanggil. Untuk informasi selengkapnya tentang cara menjaga keamanan aplikasi Anda dari malware, lihat Tips keamanan.
Metode dengan argumen Bundel yang berisi Parcelable
Jika sebuah metode menerima objekBundle
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 dalam aplikasi Anda.
Misalnya, pertimbangkan 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
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 ditentukan dengan AIDL, lakukan langkah-langkah berikut dalam class panggilan Anda:
- Sertakan file
.aidl
dalam direktorisrc/
project. - Deklarasikan instance antarmuka
IBinder
yang dihasilkan berdasarkan AIDL. - Implementasikan
ServiceConnection
. - Panggil
Context.bindService()
, yang meneruskan implementasiServiceConnection
Anda. - Dalam implementasi
onServiceConnected()
, Anda akan menerima instanceIBinder
, yang disebutservice
. PanggilYourInterfaceName.Stub.asInterface((IBinder)service)
untuk mentransmisikan parameter yang ditampilkan ke jenisYourInterface
. - Panggil metode yang Anda definisikan pada antarmuka Anda. Selalu perangkap
pengecualian
DeadObjectException
, yang ditampilkan saat koneksi terputus. Selain itu, jebakan pengecualianSecurityException
, yang ditampilkan saat dua proses yang terlibat dalam panggilan metode IPC memiliki definisi AIDL yang bertentangan. - Untuk memutuskan koneksi, panggil
Context.unbindService()
dengan instance antarmuka Anda.
Ingatlah poin-poin berikut saat memanggil layanan IPC:
- Objek adalah acuan yang dihitung pada 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 mendemonstrasikan pemanggilan layanan yang dibuat 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—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—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); } } } }