L'AIDL (Android Interface Definition Language) è simile ad altre IDL: consente di definire l'interfaccia di programmazione che il cliente e il servizio concordano per comunicare tra loro tramite comunicazione tra processi (IPC, Inter-Process Communication).
Su Android, un processo normalmente non consente di accedere di un altro processo. Per parlare, devono scomporre i loro oggetti in primitive che sia in grado di comprendere ed eseguire il marshalling degli oggetti oltre il confine per te. Il codice da utilizzare il marshalling è noioso da scrivere, quindi Android se ne occupa per te con AIDL.
Nota:AIDL è necessario solo se consenti ai client di
applicazioni diverse accedono al tuo servizio per IPC e vuoi gestire il multi-threading
completamente gestito di Google Cloud. Se non devi eseguire IPC in contemporanea su
applicazioni diverse, per creare la tua interfaccia implementando un'interfaccia
Binder
.
Se vuoi eseguire l'IPC ma non hai bisogno di gestire il multithreading,
implementare l'interfaccia utilizzando un Messenger
.
In ogni caso, assicurati di aver compreso i servizi associati prima
nell'implementazione di un AIDL.
Prima di iniziare a progettare l'interfaccia AIDL, tieni presente che le chiamate a un'interfaccia AIDL le chiamate di funzione dirette. Non fare ipotesi sul thread in cui viene effettuata la chiamata . Ciò che accade è diverso a seconda che la chiamata provenga o meno da un thread nel processo locale o remoto:
- Le chiamate effettuate dal processo locale vengono eseguite nello stesso thread che sta effettuando la chiamata. Se
questo è il thread principale della UI, che continua a essere eseguito nell'interfaccia AIDL. Se è
che esegue il codice nel servizio. Pertanto, se solo le risorse locali
i thread accedono al servizio, puoi controllare completamente quali thread vengono eseguiti al suo interno. Ma
In tal caso, non usare l'AIDL; puoi creare il cluster
a riga di comando implementando un'interfaccia
Binder
. - Le chiamate da un processo remoto vengono inviate da un pool di thread che la piattaforma gestisce al suo interno il tuo processo. Preparati alle chiamate in arrivo da thread sconosciuti, con più chiamate contemporaneamente. In altre parole, l'implementazione di un'interfaccia AIDL deve completamente sicuri per i thread. Chiamate effettuate da un thread sullo stesso oggetto remoto arrivi in ordine dal lato destinatario.
- La parola chiave
oneway
modifica il comportamento delle chiamate remote. Quando viene utilizzata, una chiamata remota non bloccare. Invia i dati delle transazioni e ritorna immediatamente. Alla fine, l'implementazione dell'interfaccia riceve questa chiamata come una normale chiamata dal pool di threadBinder
come una normale chiamata remota. Se viene usatooneway
per una chiamata locale, non c'è alcun impatto e la chiamata è ancora sincrona.
Definizione di un'interfaccia AIDL
Definisci la tua interfaccia AIDL in un file .aidl
utilizzando Java
la sintassi del linguaggio di programmazione, quindi salvala nel codice sorgente, nella directory src/
,
l'applicazione che ospita il servizio e qualsiasi altra applicazione associata al servizio.
Quando crei ogni applicazione che contiene il file .aidl
, gli strumenti dell'SDK Android
generare un'interfaccia IBinder
basata sul file .aidl
e salvarla
alla directory gen/
del progetto. Il servizio deve implementare l'IBinder
a riga di comando in base alle esigenze. Le applicazioni client possono quindi associarsi al servizio e ai metodi di chiamata
IBinder
per eseguire l'IPC.
Per creare un servizio limitato utilizzando AIDL, segui questi passaggi, descritti nelle sezioni seguenti:
- Crea il file
.aidl
Questo file definisce l'interfaccia di programmazione con le firme dei metodi.
- Implementa l'interfaccia
Gli strumenti SDK per Android generano un'interfaccia nel linguaggio di programmazione Java basata sul
.aidl
. Questa interfaccia ha una classe astratta interna denominataStub
che si estendeBinder
e implementa metodi dalla tua interfaccia AIDL. Devi estendereStub
e implementa i metodi. - Esponi l'interfaccia ai client
Implementa un
Service
ed esegui l'override dionBind()
per restituire l'implementazione diStub
.
Attenzione:eventuali modifiche apportate all'interfaccia AIDL dopo
la tua prima release deve rimanere compatibile con le versioni precedenti per evitare di danneggiare altre applicazioni
che utilizzano il tuo servizio. Questo perché il file .aidl
deve essere copiato in altre applicazioni
in modo che possano accedere all'interfaccia del servizio, devi mantenere il supporto per
a riga di comando.
Crea il file .aidl
AIDL utilizza una sintassi semplice che consente di dichiarare un'interfaccia con uno o più metodi che prendono i parametri e restituiscono i valori. I parametri e i valori restituiti possono essere di qualsiasi tipo, anche Interfacce create con AIDL.
Devi creare il file .aidl
utilizzando il linguaggio di programmazione Java. Ogni .aidl
deve definire una singola interfaccia e richiede solo la dichiarazione e il metodo dell'interfaccia
firme.
Per impostazione predefinita, AIDL supporta i seguenti tipi di dati:
- Tutti i tipi primitivi nel linguaggio di programmazione Java (ad esempio
int
,long
,char
,boolean
e così via) - Array di tipi primitivi, come
int[]
String
CharSequence
List
Tutti gli elementi in
List
devono essere di uno dei tipi di dati supportati in questo o una delle altre interfacce o pacchettizzabili generati dall'AIDL che dichiari. RList
può essere utilizzato facoltativamente come classe di tipo con parametri, ad esempioList<String>
. L'effettiva classe di cemento ricevuta dall'altra parte è sempreArrayList
, anche se per usare l'interfacciaList
.Map
Tutti gli elementi in
Map
devono essere di uno dei tipi di dati supportati in questo o una delle altre interfacce o pacchettizzabili generati dall'AIDL che dichiari. Mappe dei tipi parametrizzate, ad esempio quelle del moduloMap<String,Integer>
non sono supportati. L'effettiva classe concreta che l'altra parte riceve sempre unHashMap
, anche se il metodo viene generato per utilizzare l'interfacciaMap
. Valuta l'usoBundle
in alternativa aMap
.
Devi includere un'istruzione import
per ogni tipo aggiuntivo non elencato in precedenza,
anche se sono definite nello stesso pacchetto dell'interfaccia.
Quando definisci l'interfaccia del servizio, tieni presente che:
- I metodi possono prendere zero o più parametri e restituire un valore o un valore nullo.
- Tutti i parametri non primitivi richiedono un tag direzionale che indica la direzione in cui vengono inseriti i dati:
in
,out
oinout
(vedi l'esempio sotto).Primitivi,
String
,IBinder
e AIDL generati sonoin
per impostazione predefinita, ma non possono essere altrimenti.Attenzione:limita la direzione a ciò che è effettivamente necessaria, perché i parametri di marshalling sono costosi.
- Tutti i commenti al codice inclusi nel file
.aidl
sono inclusi nel generatoIBinder
a riga di comando, tranne i commenti precedenti all'importazione e all'applicazione istruzioni. - Le costanti stringhe e int possono essere definite nell'interfaccia AIDL, ad esempio
const int VERSION = 1;
. - Le chiamate al metodo vengono inviate
transact()
, che di solito si basa su un indice di un metodo nell'interfaccia. Poiché questo rende difficile il controllo delle versioni, può assegnare manualmente il codice della transazione a un metodo:void method() = 10;
. - Gli argomenti nulli e i tipi restituiti devono essere annotati utilizzando
@nullable
.
Ecco un file .aidl
di esempio:
// 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); }
Salva il file .aidl
nella directory src/
del progetto. Quando
creare la tua applicazione, gli strumenti dell'SDK generano il file di interfaccia IBinder
directory gen/
del progetto. Il nome del file generato corrisponde al nome del file .aidl
, ma
con un'estensione .java
. Ad esempio, IRemoteService.aidl
restituisce IRemoteService.java
.
Se utilizzi Android Studio, la build incrementale genera la classe binder quasi immediatamente.
Se non utilizzi Android Studio, lo strumento Gradle genera la classe Raccoglitore la prossima volta che
per creare la tua applicazione. Crea il tuo progetto con gradle assembleDebug
oppure gradle assembleRelease
non appena finisci di scrivere il file .aidl
,
in modo che il tuo codice possa collegarsi alla classe generata.
Implementare l'interfaccia
Quando crei la tua applicazione, gli strumenti dell'SDK Android generano un file di interfaccia .java
denominato in base al tuo file .aidl
. L'interfaccia generata include una sottoclasse denominata Stub
ovvero un'implementazione astratta dell'interfaccia principale, come YourInterface.Stub
, e dichiara tutti i metodi del file .aidl
.
Nota: anche Stub
definisce alcuni metodi helper, in particolare asInterface()
, che richiede IBinder
, di solito quello passato al metodo di callback onServiceConnected()
di un client, e
restituisce un'istanza dell'interfaccia stub. Per maggiori dettagli su come effettuare questa trasmissione, consulta la sezione Chiamata a un IPC
.
Per implementare l'interfaccia generata da .aidl
, estendi l'elemento Binder
generato
dell'interfaccia utente, ad esempio YourInterface.Stub
, e implementare i metodi
ereditato dal file .aidl
.
Ecco un esempio di implementazione di un'interfaccia denominata IRemoteService
, definita dalla precedente
Esempio di IRemoteService.aidl
, utilizzando un'istanza anonima:
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. } };
Ora binder
è un'istanza della classe Stub
(un Binder
),
che definisce l'interfaccia IPC del servizio. Nel passaggio successivo, l'istanza sarà esposta
affinché possano interagire con il servizio.
Tieni presente alcune regole durante l'implementazione dell'interfaccia AIDL:
- Non è garantito che le chiamate in arrivo vengano eseguite sul thread principale, quindi devi pensare sul multi-threading fin dall'inizio e di creare correttamente il tuo servizio per garantire l'utilizzo dei thread.
- Per impostazione predefinita, le chiamate IPC sono sincrone. Se sai che il servizio richiede più di millisecondi per completare una richiesta, non chiamarla dal thread principale dell'attività. L'applicazione potrebbe essere bloccata, quindi su Android viene visualizzato il messaggio "L'applicazione non risponde" . Chiamarlo da un thread separato nel client.
- Solo i tipi di eccezioni elencati nella documentazione di riferimento per
Parcel.writeException()
vengono rimandati al chiamante.
Esposizione dell'interfaccia ai clienti
Dopo aver implementato l'interfaccia per il tuo servizio, devi esporla
in modo che possano associarsi. Per esporre l'interfaccia
per il tuo servizio, estendi Service
e implementa onBind()
per restituire un'istanza della tua classe che implementa
il Stub
generato, come discusso nella sezione precedente. Ecco un esempio
servizio che espone l'interfaccia di esempio IRemoteService
ai client.
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. } }; }
Ora, quando un client, ad esempio un'attività, chiama bindService()
per connettersi a questo servizio, il callback onServiceConnected()
del client riceve il messaggio
Istanza binder
restituita dall'onBind()
del servizio
.
Il client deve inoltre avere accesso alla classe dell'interfaccia. Quindi se il client e il servizio sono in
applicazioni separate, l'applicazione del client deve avere una copia del file .aidl
nella sua directory src/
, che genera il android.os.Binder
che fornisce al client l'accesso ai metodi AIDL.
Quando il client riceve IBinder
nel callback onServiceConnected()
, deve chiamare
YourServiceInterface.Stub.asInterface(service)
per trasmettere il
parametro al tipo 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; } };
Per altro codice di esempio, consulta
RemoteService.java
corso in
apiDemos.
Trasferimento di oggetti su IPC
In Android 10 (livello API 29 o versioni successive), puoi definire
Parcelable
oggetti direttamente in
AIDL Sono supportati anche i tipi supportati come argomenti dell'interfaccia AIDL e altri elementi Parcelable
supportati qui. In questo modo si evita il lavoro aggiuntivo necessario per scrivere manualmente il codice di marshalling e
. Tuttavia, questo crea anche uno struct semplice. Se le funzioni di accesso personalizzate o
un'altra funzionalità sono
vuoi, implementa invece Parcelable
.
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; }
L'esempio di codice precedente genera automaticamente una classe Java con campi interi left
,
top
, right
e bottom
. Tutto il codice di marshalling pertinente è
implementato automaticamente e l'oggetto può essere utilizzato direttamente senza dover aggiungere
implementazione.
Puoi anche inviare una classe personalizzata da un processo a un altro attraverso un'interfaccia IPC. Tuttavia,
assicurati che il codice del corso sia disponibile nell'altro lato del canale IPC e
il tuo corso deve supportare l'interfaccia Parcelable
. Supporto
Parcelable
è importante
perché consente al sistema Android di scomporre oggetti in primitive di cui è possibile eseguire il marshalling
tra i vari processi.
Per creare una classe personalizzata che supporti Parcelable
, procedi nel seguente modo
seguenti:
- Fai in modo che il tuo corso implementi l'interfaccia
Parcelable
. - Implementare
writeToParcel
, che richiede attuale dell'oggetto e lo scrive in unParcel
. - Aggiungi alla tua classe un campo statico denominato
CREATOR
, ovvero un oggetto che implementa l'interfaccia diParcelable.Creator
. - Infine, crea un file
.aidl
che dichiari la classe "parcelable", come mostrato di seguitoRect.aidl
file.Se utilizzi un processo di compilazione personalizzato, non aggiungere il file
.aidl
al tuo creare. Analogamente a un file di intestazione in linguaggio C, questo file.aidl
non viene compilato.
AIDL utilizza questi metodi e campi nel codice che genera per eseguire il marshall e annullare il marshall degli oggetti.
Ad esempio, ecco un file Rect.aidl
per creare una classe Rect
"parcelable":
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
Ed ecco un esempio di come la classe Rect
implementa la classe
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; } }
Il trasferimento nella classe Rect
è semplice. Dai un'occhiata all'altro
su Parcel
per vedere gli altri tipi di valori che puoi scrivere
a Parcel
.
Avviso: ricorda le implicazioni sulla sicurezza della ricezione
i dati provenienti da altri processi. In questo caso, Rect
legge quattro numeri da Parcel
, ma spetta a te assicurarti che rientrino nell'intervallo accettabile di
per ciò che il chiamante sta cercando di fare. Per ulteriori informazioni su come proteggere la tua applicazione dal malware, vedi Suggerimenti per la sicurezza.
Metodi con argomenti Bundle contenenti Parcelables
Se un metodo accetta un oggettoBundle
che dovrebbe contenere
parcelables, assicurati di impostare il classloader di Bundle
tramite
chiamata a Bundle.setClassLoader(ClassLoader)
prima di tentare di leggere
da Bundle
. In caso contrario, riscontri ClassNotFoundException
anche se il valore "parcelable" è definito correttamente nella tua applicazione.
Considera ad esempio il seguente file .aidl
di esempio:
// 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); }Come mostrato nella seguente implementazione, il
ClassLoader
è
esplicitamente impostato in Bundle
prima di leggere 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. } };
Chiamata a un metodo IPC
Per chiamare un'interfaccia remota definita con AIDL, procedi nel seguente modo in la tua classe di chiamata:
- Includi il file
.aidl
nella directory del progettosrc/
. - Dichiara un'istanza dell'interfaccia
IBinder
generata in base al AIDL - Implementa
ServiceConnection
. - Chiama
Context.bindService()
, passando per l'implementazione diServiceConnection
. - Nella tua implementazione di
onServiceConnected()
, ricevi unIBinder
denominataservice
. Chiama DaYourInterfaceName.Stub.asInterface((IBinder)service)
a trasmette il parametro restituito al tipoYourInterface
. - Richiama i metodi che hai definito nella tua interfaccia. Blocca sempre
DeadObjectException
eccezioni, che vengono generate quando le interruzioni di connessione. Inoltre, vengono intercettate le eccezioniSecurityException
, che vengono generate quando i due processi coinvolti nella chiamata al metodo IPC hanno definizioni AIDL in conflitto. - Per disconnetterti, chiama
Context.unbindService()
con l'istanza della tua interfaccia.
Tieni presente quanto segue quando chiami un servizio IPC:
- Gli oggetti vengono conteggiati nei processi.
- Puoi inviare oggetti anonimi come argomenti del metodo.
Per ulteriori informazioni sull'associazione a un servizio, leggi la panoramica dei servizi associati.
Ecco un codice di esempio che mostra la chiamata di un servizio creato da AIDL, preso dall'esempio di servizio remoto nel progetto 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); } } } }