Le langage AIDL (Android Interface Definition Language) est semblable aux autres IDL : il vous permet de définir l'interface de programmation que le client et le service conviennent pour communiquer entre eux à l'aide de la communication inter-processus (IPC).
Sur Android, un processus ne peut normalement pas accéder à la mémoire d'un autre processus. Pour communiquer, ils doivent décomposer leurs objets en primitives que le système d'exploitation peut comprendre et gérer pour vous les objets au-delà de cette limite. Le code permettant d'effectuer ce marshaling est fastidieux à écrire. Android s'en charge pour vous avec AIDL.
Remarque:AIDL n'est nécessaire que si vous autorisez des clients de différentes applications à accéder à votre service pour l'IPC et que vous souhaitez gérer le multithreading dans votre service. Si vous n'avez pas besoin d'effectuer une IPC simultanée entre différentes applications, créez votre interface en implémentant un Binder
.
Si vous souhaitez effectuer l'IPC, mais que vous n'avez pas besoin de gérer le multithreading, implémentez votre interface à l'aide d'un Messenger
.
Quoi qu'il en soit, assurez-vous de bien comprendre les services liés avant d'implémenter un AIDL.
Avant de commencer à concevoir votre interface AIDL, sachez que les appels à une interface AIDL sont des appels de fonction directs. Ne faites pas d'hypothèses sur le thread dans lequel l'appel a lieu. Le résultat diffère selon que l'appel provient d'un thread du processus local ou d'un processus distant:
- Les appels effectués à partir du processus local s'exécutent dans le même thread qui effectue l'appel. S'il s'agit de votre thread UI principal, il continue de s'exécuter dans l'interface AIDL. S'il s'agit d'un autre thread, c'est celui qui exécute votre code dans le service. Ainsi, si seuls les threads locaux accèdent au service, vous pouvez contrôler entièrement les threads qui s'y exécutent. Toutefois, si tel est le cas, n'utilisez pas du tout AIDL. À la place, créez l'interface en implémentant un
Binder
. - Les appels provenant d'un processus distant sont distribués à partir d'un pool de threads que la plate-forme gère dans votre propre processus. Préparez-vous aux appels entrants provenant de threads inconnus, avec plusieurs appels ayant lieu en même temps. En d'autres termes, l'implémentation d'une interface AIDL doit être entièrement sécurisée. Les appels effectués à partir d'un thread sur le même objet distant arrivent dans l'ordre côté récepteur.
- Le mot clé
oneway
modifie le comportement des appels distants. Lorsqu'il est utilisé, un appel distant ne se bloque pas. Il envoie les données de la transaction et renvoie immédiatement un résultat. L'implémentation de l'interface finit par recevoir cet appel standard du pool de threadsBinder
en tant qu'appel distant normal. Sioneway
est utilisé avec un appel local, il n'y a aucun impact et l'appel est toujours synchrone.
Définir une interface AIDL
Définissez votre interface AIDL dans un fichier .aidl
à l'aide de la syntaxe du langage de programmation Java, puis enregistrez-le dans le code source, dans le répertoire src/
, de l'application hébergeant le service et de toute autre application qui se lie au service.
Lorsque vous compilez chaque application contenant le fichier .aidl
, les SDK Tools pour Android génèrent une interface IBinder
basée sur le fichier .aidl
et l'enregistrent dans le répertoire gen/
du projet. Le service doit implémenter l'interface IBinder
de manière appropriée. Les applications clientes peuvent ensuite s'associer au service et appeler des méthodes à partir de IBinder
pour effectuer l'IPC.
Pour créer un service limité à l'aide d'AIDL, suivez les étapes décrites dans les sections suivantes:
- Créer le fichier
.aidl
Ce fichier définit l'interface de programmation avec des signatures de méthodes.
- Implémenter l'interface
Les outils Android SDK génèrent une interface dans le langage de programmation Java en fonction du fichier
.aidl
. Cette interface comporte une classe abstraite interne nomméeStub
qui étendBinder
et implémente les méthodes de votre interface AIDL. Vous devez étendre la classeStub
et implémenter les méthodes. - Exposer l'interface aux clients
Implémentez un
Service
et ignorezonBind()
pour renvoyer votre implémentation de la classeStub
.
Attention:Toutes les modifications que vous apportez à votre interface AIDL après votre première version doivent rester rétrocompatibles pour éviter de perturber les autres applications qui utilisent votre service. En effet, comme votre fichier .aidl
doit être copié dans d'autres applications pour qu'elles puissent accéder à l'interface de votre service, vous devez conserver la compatibilité de l'interface d'origine.
Créer le fichier .aidl
AIDL utilise une syntaxe simple qui vous permet de déclarer une interface avec une ou plusieurs méthodes pouvant accepter des paramètres et des valeurs de retour. Les paramètres et les valeurs de retour peuvent être de n'importe quel type, même d'autres interfaces générées par AIDL.
Vous devez construire le fichier .aidl
à l'aide du langage de programmation Java. Chaque fichier .aidl
doit définir une interface unique et ne nécessite que la déclaration d'interface et les signatures de méthode.
Par défaut, AIDL accepte les types de données suivants:
- Tous les types primitifs dans le langage de programmation Java (par exemple,
int
,long
,char
,boolean
, etc.) - Tableaux de types primitifs, tels que
int[]
String
CharSequence
List
Tous les éléments de
List
doivent correspondre à l'un des types de données acceptés dans cette liste, ou à l'une des autres interfaces ou parcelables générées par AIDL que vous déclarez. UnList
peut éventuellement être utilisé en tant que classe de type paramétrée, telle queList<String>
. La classe concrète réelle reçue par l'autre côté est toujours uneArrayList
, bien que la méthode soit générée pour utiliser l'interfaceList
.Map
Tous les éléments de
Map
doivent correspondre à l'un des types de données acceptés dans cette liste, ou à l'une des autres interfaces ou parcelables générées par AIDL que vous déclarez. Les mappages de types paramétrés, tels que ceux au formatMap<String,Integer>
, ne sont pas acceptés. La classe concrète réelle reçue par l'autre côté est toujours uneHashMap
, bien que la méthode soit générée pour utiliser l'interfaceMap
. Envisagez d'utiliser unBundle
à la place deMap
.
Vous devez inclure une instruction import
pour chaque type supplémentaire non répertorié précédemment, même s'ils sont définis dans le même package que votre interface.
Lorsque vous définissez votre interface de service, gardez à l'esprit les points suivants:
- Les méthodes peuvent accepter zéro ou plusieurs paramètres et renvoyer une valeur ou une valeur nulle.
- Tous les paramètres non primitifs nécessitent un tag directionnel indiquant la direction des données :
in
,out
ouinout
(voir l'exemple ci-dessous).Les primitives,
String
,IBinder
et les interfaces générées par AIDL sontin
par défaut et ne peuvent pas l'être autrement.Attention:Limitez la direction à ce qui est réellement nécessaire, car les paramètres de marshaling sont coûteux.
- Tous les commentaires de code inclus dans le fichier
.aidl
sont inclus dans l'interfaceIBinder
générée, à l'exception des commentaires précédant les instructions d'importation et de package. - Les constantes de type chaîne et entier peuvent être définies dans l'interface AIDL, par exemple
const int VERSION = 1;
. - Les appels de méthode sont distribués par un code
transact()
, qui est normalement basé sur un index de méthode dans l'interface. Comme cela complique la gestion des versions, vous pouvez attribuer manuellement le code de transaction à une méthode:void method() = 10;
. - Les arguments pouvant être de valeur nulle et les types renvoyés doivent être annotés à l'aide de
@nullable
.
Voici un exemple de fichier .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); }
Enregistrez votre fichier .aidl
dans le répertoire src/
de votre projet. Lorsque vous compilez votre application, les SDK Tools génèrent le fichier d'interface IBinder
dans le répertoire gen/
de votre projet. Le nom du fichier généré correspond au nom du fichier .aidl
, mais avec une extension .java
. Par exemple, IRemoteService.aidl
donne IRemoteService.java
.
Si vous utilisez Android Studio, la compilation incrémentielle génère la classe de liaison presque immédiatement.
Si vous n'utilisez pas Android Studio, l'outil Gradle génère la classe de liaison la prochaine fois que vous compilez votre application. Créez votre projet avec gradle assembleDebug
ou gradle assembleRelease
dès que vous avez terminé d'écrire le fichier .aidl
, afin que votre code puisse être associé à la classe générée.
Implémenter l'interface
Lorsque vous compilez votre application, les SDK Tools pour Android génèrent un fichier d'interface .java
nommé d'après votre fichier .aidl
. L'interface générée inclut une sous-classe nommée Stub
, qui est une implémentation abstraite de son interface parente, telle que YourInterface.Stub
, et déclare toutes les méthodes du fichier .aidl
.
Remarque:Stub
définit également quelques méthodes d'assistance, notamment asInterface()
, qui accepte un IBinder
, généralement celui transmis à la méthode de rappel onServiceConnected()
d'un client et renvoie une instance de l'interface bouchon. Pour en savoir plus sur la manière de réaliser cette conversion, consultez la section Appeler une méthode IPC.
Pour implémenter l'interface générée à partir de .aidl
, étendez l'interface Binder
générée, par exemple YourInterface.Stub
, et implémentez les méthodes héritées du fichier .aidl
.
Voici un exemple d'implémentation d'une interface appelée IRemoteService
, définie par l'exemple IRemoteService.aidl
précédent, à l'aide d'une instance anonyme:
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. } };
Désormais, binder
est une instance de la classe Stub
(une Binder
), qui définit l'interface d'IPC pour le service. À l'étape suivante, cette instance est exposée aux clients afin qu'ils puissent interagir avec le service.
Tenez compte de quelques règles lorsque vous implémentez votre interface AIDL:
- L'exécution des appels entrants sur le thread principal n'est pas garantie. Vous devez donc réfléchir dès le départ au multithreading et créer correctement votre service pour qu'il soit thread-safe.
- Par défaut, les appels d'IPC sont synchrones. Si vous savez que le service met plus de quelques millisecondes à traiter une requête, ne l'appelez pas à partir du thread principal de l'activité. Cela peut entraîner le blocage de l'application, ce qui peut entraîner l'affichage d'une boîte de dialogue "L'application ne répond pas". Appelez-le à partir d'un thread distinct dans le client.
- Seuls les types d'exceptions répertoriés dans la documentation de référence pour
Parcel.writeException()
sont renvoyés à l'appelant.
Exposer l'interface aux clients
Une fois que vous avez implémenté l'interface de votre service, vous devez l'exposer aux clients afin qu'ils puissent s'y associer. Pour exposer l'interface de votre service, étendez Service
et implémentez onBind()
pour renvoyer une instance de votre classe qui implémente le Stub
généré, comme indiqué dans la section précédente. Voici un exemple de service qui expose l'exemple d'interface IRemoteService
aux clients.
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. } }; }
Désormais, lorsqu'un client, comme une activité, appelle bindService()
pour se connecter à ce service, le rappel onServiceConnected()
du client reçoit l'instance binder
renvoyée par la méthode onBind()
du service.
Le client doit également avoir accès à la classe d'interface. Ainsi, si le client et le service se trouvent dans des applications distinctes, l'application du client doit disposer d'une copie du fichier .aidl
dans son répertoire src/
, ce qui génère l'interface android.os.Binder
et permet au client d'accéder aux méthodes AIDL.
Lorsque le client reçoit le IBinder
dans le rappel onServiceConnected()
, il doit appeler YourServiceInterface.Stub.asInterface(service)
pour caster le paramètre renvoyé en type 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; } };
Pour plus d'exemples de code, consultez la classe
RemoteService.java
dans
ApiDemos.
Transmettre des objets via l'IPC
Sous Android 10 (niveau d'API 29 ou supérieur), vous pouvez définir des objets Parcelable
directement dans AIDL. Les types acceptés en tant qu'arguments d'interface AIDL et d'autres éléments Parcelable le sont également ici. Cela évite d'avoir à écrire manuellement du code de marshaling et une classe personnalisée. Cependant, cela crée également un struct nu. Si vous souhaitez utiliser des accesseurs personnalisés ou d'autres fonctionnalités, implémentez plutôt 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'exemple de code précédent génère automatiquement une classe Java avec les champs d'entiers left
, top
, right
et bottom
. Tout le code de marshaling pertinent est implémenté automatiquement et l'objet peut être utilisé directement sans qu'il soit nécessaire d'ajouter une implémentation.
Vous pouvez également envoyer une classe personnalisée d'un processus à un autre via une interface IPC. Toutefois, assurez-vous que le code de votre classe est disponible de l'autre côté du canal IPC et que votre classe doit être compatible avec l'interface Parcelable
. La prise en charge de Parcelable
est importante, car elle permet au système Android de décomposer les objets en primitives qui peuvent être marshalées entre les processus.
Pour créer une classe personnalisée compatible avec Parcelable
, procédez comme suit:
- Demandez à votre classe d'implémenter l'interface
Parcelable
. - Implémentez
writeToParcel
, qui prend l'état actuel de l'objet et l'écrit dans unParcel
. - Ajoutez à votre classe un champ statique appelé
CREATOR
. Il s'agit d'un objet implémentant l'interfaceParcelable.Creator
. - Enfin, créez un fichier
.aidl
qui déclare votre classe Parcelable, comme illustré dans le fichierRect.aidl
suivant.Si vous utilisez un processus de compilation personnalisé, n'ajoutez pas le fichier
.aidl
à votre compilation. Semblable à un fichier d'en-tête en langage C, ce fichier.aidl
n'est pas compilé.
AIDL utilise ces méthodes et ces champs dans le code qu'il génère pour marshaler et désassembler les objets.
Par exemple, voici un fichier Rect.aidl
permettant de créer une classe Rect
parcelable:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
Voici un exemple de la manière dont la classe Rect
implémente le protocole 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; } }
Le marshaling dans la classe Rect
est simple. Examinez les autres méthodes sur Parcel
pour connaître les autres types de valeurs que vous pouvez écrire dans un Parcel
.
Avertissement:N'oubliez pas les implications sur la sécurité de la réception de données provenant d'autres processus. Dans ce cas, Rect
lit quatre chiffres à partir de Parcel
, mais il vous appartient de vous assurer qu'ils se situent dans la plage de valeurs acceptable pour l'opération que l'appelant tente d'effectuer. Pour en savoir plus sur la manière de protéger votre application contre les logiciels malveillants, consultez Conseils de sécurité.
Méthodes avec des arguments de bundle contenant des Parcelables
Si une méthode accepte un objetBundle
censé contenir des parcelables, assurez-vous de définir le ClassLoader de Bundle
en appelant Bundle.setClassLoader(ClassLoader)
avant d'essayer de lire le Bundle
. Sinon, vous rencontrerez ClassNotFoundException
même si l'élément Parcelable est correctement défini dans votre application.
Prenons l'exemple de fichier .aidl
suivant:
// 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); }Comme indiqué dans l'implémentation suivante,
ClassLoader
est explicitement défini dans Bundle
avant la lecture de 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. } };
Appeler une méthode IPC
Pour appeler une interface distante définie avec AIDL, procédez comme suit dans votre classe d'appel:
- Incluez le fichier
.aidl
dans le répertoiresrc/
du projet. - Déclarez une instance de l'interface
IBinder
, générée selon AIDL. - Implémentez
ServiceConnection
. - Appelez
Context.bindService()
en transmettant votre implémentation deServiceConnection
. - Dans votre implémentation de
onServiceConnected()
, vous recevez une instanceIBinder
, appeléeservice
. AppelezYourInterfaceName.Stub.asInterface((IBinder)service)
pour caster le paramètre renvoyé sur le typeYourInterface
. - Appelez les méthodes que vous avez définies dans votre interface. Toujours intercepter les exceptions
DeadObjectException
, qui sont générées lorsque la connexion est interrompue. De plus, les exceptionsSecurityException
sont générées lorsque les deux processus impliqués dans l'appel de méthode IPC ont des définitions AIDL contradictoires. - Pour vous déconnecter, appelez
Context.unbindService()
avec l'instance de votre interface.
Tenez compte des points suivants lorsque vous appelez un service d'IPC:
- Les objets sont des références comptées d'un processus à l'autre.
- Vous pouvez envoyer des objets anonymes en tant qu'arguments de méthode.
Pour en savoir plus sur la liaison à un service, consultez la présentation des services liés.
Vous trouverez ci-dessous un exemple de code illustrant l'appel d'un service créé par AIDL à partir de l'exemple de service distant du projet 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); } } } }