Langage de définition d'interface Android (AIDL)

Le langage AIDL (Android Interface Definition Language) est semblable à d'autres IDL: il vous permet de définir l'interface de programmation que le client et le service se mettent d'accord afin de communiquer entre eux en utilisant la communication inter-processus (IPC).

Sur Android, un processus ne peut normalement pas accéder la mémoire d'un autre processus. Pour pouvoir communiquer, ils doivent décomposer leurs objets en primitives dont les système d'exploitation peut comprendre et organiser les objets au-delà de cette limite pour vous. Le code permettant ce marshaling est fastidieux à écrire. Android s'en charge pour vous avec AIDL.

Remarque:AIDL n'est nécessaire que si vous permettez aux clients différentes applications accèdent à votre service pour l'IPC et vous souhaitez gérer le multithreading dans votre Google Cloud. Si vous n'avez pas besoin d'effectuer une IPC simultanée applications différentes, 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 pour implémenter AIDL.

Avant de commencer à concevoir votre interface AIDL, sachez que les appels vers une interface AIDL sont les appels de fonction directs. Ne faites pas d'hypothèses sur le thread dans lequel l'appel se produit. Le processus diffère selon que l'appel provient ou non d'un fil de discussion dans un processus local ou distant:

  • Les appels effectués à partir du processus local s'exécutent dans le même thread que celui qui effectue l'appel. Si il s'agit de votre thread UI principal. Ce thread continue de s'exécuter dans l'interface AIDL. Si c'est le cas un autre thread, qui est celui qui exécute votre code dans le service. Ainsi, si la communication d'exécution des threads qui accèdent au service, vous pouvez contrôler entièrement les threads qui s'y exécutent. Toutefois, Si c'est le cas, n'utilisez pas AIDL ; à la place, créez de l'interface en implémentant un Binder.
  • Les appels provenant d'un processus distant sont distribués à partir d'un pool de threads géré par la plate-forme. votre propre processus. Être prêt à recevoir plusieurs appels provenant de fils de discussion inconnus se produisant en même temps. En d'autres termes, l'implémentation d'une interface AIDL doit être entièrement thread-safe. 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'elle est utilisée, un appel distant pas de bloquer. Il envoie les données de transaction et renvoie immédiatement le résultat. L'implémentation de l'interface le reçoit à terme en tant qu'appel standard du pool de threads Binder en tant qu'appel distant normal. Si oneway 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 l'API Java la syntaxe du langage de programmation, puis enregistrez-la dans le code source, dans le répertoire src/, l'application qui héberge le service et toute autre application qui se lie au service.

Lorsque vous compilez chaque application contenant le fichier .aidl, Android SDK Tools générer une interface IBinder basée sur le fichier .aidl et l'enregistrer dans le répertoire gen/ du projet. Le service doit implémenter le IBinder de l'interface utilisateur, le cas échéant. Les applications clientes peuvent ensuite s'associer au service et appeler les méthodes IBinder pour effectuer l'IPC.

Pour créer un service limité à l'aide d'AIDL, procédez comme suit : dans les sections suivantes:

  1. Créer le fichier .aidl

    Ce fichier définit l'interface de programmation avec des signatures de méthode.

  2. Implémenter l'interface

    Android SDK Tools génère une interface en langage de programmation Java en fonction de votre .aidl. Cette interface comporte une classe abstraite interne nommée Stub qui étend Binder et implémente des méthodes à partir de votre interface AIDL. Vous devez étendre la Stub et implémenter les méthodes.

  3. Exposer l'interface aux clients

    Implémentez un Service et ignorez onBind() pour renvoyer votre implémentation de Stub. .

Attention:Toute modification apportée à votre interface AIDL après votre première version doit rester rétrocompatible pour éviter de perturber les autres applications ; qui utilisent votre service. En effet, votre fichier .aidl doit être copié dans d'autres applications. afin qu'ils puissent accéder à l'interface de votre service, vous devez assurer la compatibilité de commande.

Créer le fichier .aidl

AIDL utilise une syntaxe simple qui vous permet de déclarer une interface à l'aide d'une ou de plusieurs méthodes pouvant acceptent des paramètres et renvoient des valeurs. Les paramètres et les valeurs de retour peuvent être de n'importe quel type, Interfaces générées par AIDL

Vous devez créer le fichier .aidl à l'aide du langage de programmation Java. Chaque .aidl doit définir une interface unique, et ne nécessite que la déclaration de l'interface et la méthode signatures.

Par défaut, AIDL accepte les types de données suivants:

  • Tous les types primitifs dans le langage de programmation Java (tels que 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 ou l'une des autres interfaces ou éléments parcelables générés par AIDL que vous déclarez. A List peut éventuellement être utilisé comme classe de type paramétrée, par exemple List<String> La classe concrète réelle reçue par l'autre côté est toujours ArrayList, bien que le est générée pour utiliser l'interface List.

  • Map

    Tous les éléments de Map doivent correspondre à l'un des types de données acceptés dans cette ou l'une des autres interfaces ou éléments parcelables générés par AIDL que vous déclarez. Mappages de types paramétrés, tels que ceux sous la forme Map<String,Integer> ne sont pas acceptés. La classe concrète réelle que l'autre côté reçoit est toujours un HashMap, bien que la méthode soit générée pour utiliser l'interface Map. Envisagez d'utiliser un Bundle comme alternative à Map.

Vous devez inclure une instruction import pour chaque type supplémentaire non listé précédemment. même s'ils sont définis dans le même package que votre interface.

Lorsque vous définissez l'interface de votre service, tenez compte des points suivants:

  • Les méthodes peuvent accepter zéro, un ou plusieurs paramètres, et renvoyer une valeur ou un vide.
  • Tous les paramètres non primitifs nécessitent un tag directionnel indiquant la direction des données: in, out ou inout (voir l'exemple ci-dessous).

    Primitives, String, IBinder et générées par AIDL interfaces sont in par défaut et ne peuvent pas l'être autrement.

    Attention:Limitez la direction à ce qui est réellement car les paramètres de marshaling sont coûteux.

  • Tous les commentaires de code inclus dans le fichier .aidl sont inclus dans le a généré IBinder à l'exception des commentaires avant l'importation et le package .
  • Les constantes "string" et "int" peuvent être définies dans l'interface AIDL, par exemple const int VERSION = 1;.
  • Les appels de méthode sont envoyés transact() code, qui repose normalement sur un index de méthode de l'interface. Parce que cela complique la gestion des versions, peut 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 compiler votre application, les outils du SDK génèrent le fichier d'interface IBinder dans votre dans le répertoire gen/ du projet. Le nom du fichier généré correspond à celui 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 créer votre application. Compiler votre projet avec gradle assembleDebug ou gradle assembleRelease dès que vous avez fini 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, Android SDK Tools génère un fichier d'interface .java dont le nom est dérivé de 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 à partir du fichier .aidl.

Remarque:Stub également définit 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 réalisation de cette diffusion, consultez la section Appeler un IPC méthode.

Pour implémenter l'interface générée à partir de .aidl, étendez le Binder généré. interface standard comme YourInterface.Stub, puis 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 la méthode Exemple IRemoteService.aidl à 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.
    }
};

binder est maintenant une instance de la classe Stub (un Binder). qui définit l'interface d'IPC du service. À l'étape suivante, cette instance sera exposée clients afin qu'ils puissent interagir avec le service.

Tenez compte de quelques règles lorsque vous implémentez votre interface AIDL:

  • Il n'est pas garanti que les appels entrants s'exécutent sur le thread principal. Vous devez donc réfléchir sur le multithreading dès le départ et de 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 prend plus de de millisecondes pour terminer une requête, ne l'appelez pas à partir du thread principal de l'activité. Cela peut bloquer l'application, ce qui peut entraîner l'affichage du message "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 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 à clients afin qu'ils puissent s'y associer. Pour exposer l'interface pour 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 : 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, par exemple une activité, appelle bindService() pour se connecter à ce service, le rappel onServiceConnected() du client reçoit la Instance binder renvoyée par l'instance onBind() du service .

Le client doit également avoir accès à la classe d'interface. Si le client et le service sont dans applications distinctes, l'application du client doit disposer d'une copie du fichier .aidl dans son répertoire src/, ce qui génère le fichier android.os.Binder. permettant au client d'accéder aux méthodes AIDL.

Lorsque le client reçoit la valeur IBinder dans le rappel onServiceConnected(), il doit appeler YourServiceInterface.Stub.asInterface(service) pour caster le contenu renvoyé au 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 obtenir d'autres exemples de code, consultez <ph type="x-smartling-placeholder"></ph> RemoteService.java dans <ph type="x-smartling-placeholder"></ph> ApiDemos

Transmettre des objets via l'IPC

Sous Android 10 (niveau d'API 29 ou supérieur), vous pouvez définir Parcelable objets directement dans AIDL. Les types pris en charge en tant qu'arguments d'interface AIDL et d'autres éléments parcelables sont également pris en charge ici. Cela évite d'avoir à écrire manuellement du code de marshaling et une requête . Cependant, cela crée également un struct simple. Si des accesseurs personnalisés ou d'autres fonctionnalités vous pouvez implémenter Parcelable à la place.

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 des champs d'entiers left, top, right et bottom. Tout le code de marshaling pertinent sont implémentés automatiquement. De plus, l'objet peut être utilisé directement sans avoir à ajouter la mise en œuvre.

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 Votre classe doit être compatible avec l'interface Parcelable. Soutien Parcelable est important car il permet au système Android de décomposer les objets en primitives pouvant être marshalées. entre les processus.

Pour créer une classe personnalisée compatible avec Parcelable, exécutez la suivantes:

  1. Faites en sorte que votre classe implémente l'interface Parcelable.
  2. Implémentez writeToParcel, qui prend le l'état actuel de l'objet et l'écrit dans un Parcel.
  3. Ajoutez à votre classe un champ statique appelé CREATOR, qui est un objet implémenté l'interface Parcelable.Creator.
  4. Enfin, créez un fichier .aidl qui déclare votre classe parcelable, comme illustré ci-dessous : Rect.aidl.

    Si vous utilisez un processus de compilation personnalisé, n'ajoutez pas le fichier .aidl à votre créer. 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 rassembler et désassembler vos 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 façon dont la classe Rect implémente la 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. Regardez l'autre sur Parcel pour voir les autres types de valeurs que vous pouvez écrire. à un Parcel.

Avertissement:Souvenez-vous des implications en termes de sécurité de la réception les données d'autres processus. Dans ce cas, Rect lit quatre chiffres de Parcel, mais vous devez vous assurer qu'ils se situent dans la plage acceptable de des valeurs pour ce que l'appelant tente de faire. Pour savoir comment protéger votre application contre les logiciels malveillants, consultez les conseils de sécurité.

Méthodes avec des arguments Bundle contenant des Parcelables

Si une méthode accepte un objet Bundle censé contenir parcelables, veillez à définir le ClassLoader de Bundle en appeler Bundle.setClassLoader(ClassLoader) avant de tenter de lire à partir de Bundle. Dans le cas contraire, vous rencontrerez ClassNotFoundException, même si le fragmentable 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 le montre l'implémentation suivante, ClassLoader est explicitement définies dans le Bundle avant de lire 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 d'IPC

Pour appeler une interface à distance définie avec AIDL, procédez comme suit dans votre classe d'appel:

  1. Incluez le fichier .aidl dans le répertoire src/ du projet.
  2. Déclarez une instance de l'interface IBinder, générée en fonction du AIDL.
  3. Implémentez ServiceConnection.
  4. Appeler Context.bindService(), en transmettant votre implémentation ServiceConnection.
  5. Dans votre implémentation de onServiceConnected(), vous recevez une IBinder appelée service. Appeler YourInterfaceName.Stub.asInterface((IBinder)service) jusqu'à Convertissez le paramètre renvoyé en type YourInterface.
  6. Appelez les méthodes que vous avez définies dans votre interface. Toujours piéger des exceptions DeadObjectException, qui sont générées lorsque la connexion est interrompue. En outre, les exceptions SecurityException interceptent, qui sont levées lorsque les deux processus impliqués dans l'appel de méthode IPC ont des définitions AIDL en conflit.
  7. Pour vous déconnecter, appelez Context.unbindService() avec l'instance de votre interface.

Lorsque vous appelez un service d'IPC, tenez compte des points suivants:

  • Les objets sont comptabilisés comme des références dans tous les processus.
  • 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.

Voici un exemple de code illustrant l'appel d'un service créé par AIDL, pris à partir de l'exemple de service distant dans le 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&mdash;that is, its process crashed.
            mService = null
            killButton.isEnabled = false
            callbackText.text = "Disconnected."

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

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

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

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

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

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

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

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

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

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

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

        setContentView(R.layout.remote_service_binding)

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

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

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

Java

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

    Button killButton;
    TextView callbackText;

    private InternalHandler handler;
    private boolean isBound;

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

        setContentView(R.layout.remote_service_binding);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    private static final int BUMP_MSG = 1;

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

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

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