Lenguaje de definición de la interfaz de Android (AIDL)

El Lenguaje de definición de la interfaz de Android (AIDL) es similar a otros IDL: te permite definir la interfaz de programación que tanto con el que el cliente y el servicio acuerdan comunicarse entre sí mediante comunicación entre procesos (IPC).

En Android, normalmente un proceso no puede acceder memoria de otro proceso. Para hablar, deben descomponer sus objetos en primitivas un sistema operativo completo puede comprender y ordenar los objetos fuera de los límites por ti. El código para hacer que el ordenamiento sea tedioso de escribir, así que Android lo maneja por ti con AIDL.

Nota: El AIDL es necesario solo si permites que los clientes diferentes acceden a tu servicio para IPC y quieres controlar los subprocesos múltiples en tu servicio. Si no necesitas establecer IPC simultáneas en diferentes aplicaciones, cree su interfaz implementando una Binder Si quieres realizar una IPC, pero no necesitas controlar varios subprocesos, implementar tu interfaz con un Messenger. Aun así, asegúrate de comprender los servicios vinculados antes de para implementar un AIDL.

Antes de comenzar a diseñar tu interfaz del AIDL, ten en cuenta que las llamadas a una interfaz del AIDL se llamadas directas a funciones. No hagas suposiciones sobre el subproceso en el que se produce la llamada. de que ocurra. Lo que sucede es diferente dependiendo de si la llamada proviene de un subproceso en el un proceso local o un proceso remoto:

  • Las llamadas realizadas desde el proceso local se ejecutan en el mismo subproceso que realiza la llamada. Si Este es el subproceso de IU principal, que continúa ejecutándose en la interfaz del AIDL. Si es es decir, el que ejecuta el código en el servicio. Por lo tanto, si solo es local acceso al servicio, puedes controlar completamente qué subprocesos se ejecutan en él. Sin embargo, Si ese es el caso, no uses AIDL en absoluto. en su lugar, crea el interfaz implementando una Binder
  • Las llamadas desde un proceso remoto se despachan desde un grupo de subprocesos que la plataforma mantiene en su interior. tu propio proceso. Prepárate para llamadas entrantes desde subprocesos desconocidos, con llamadas múltiples sucede al mismo tiempo. En otras palabras, una implementación de una interfaz de AIDL debe ser son completamente seguros para los subprocesos. Llamadas realizadas desde un subproceso en el mismo objeto remoto llegue en orden del extremo del receptor.
  • La palabra clave oneway modifica el comportamiento de las llamadas remotas. Cuando se usan, una llamada remota no no se bloquea. Envía los datos de la transacción y devuelve resultados de inmediato. La implementación de la interfaz finalmente recibe esto como una llamada normal del grupo de subprocesos Binder como una llamada remota normal. Si se usa oneway con una llamada local, no hay impacto, y la llamada sigue siendo síncrona.

Cómo definir una interfaz de AIDL

Define tu interfaz de AIDL en un archivo .aidl con Java del lenguaje de programación y guárdala en el código fuente, en el directorio src/ de ambos la aplicación que aloja el servicio y cualquier otra aplicación que se vincule con el servicio.

Cuando compilas cada aplicación que contiene el archivo .aidl, las herramientas del SDK de Android Genera una interfaz IBinder basada en el archivo .aidl y guárdala en el directorio gen/ del proyecto. El servicio debe implementar IBinder. interfaz de usuario según corresponda. Las aplicaciones cliente pueden enlazarse con el servicio y llamar a los métodos desde el IBinder para realizar IPC.

Para crear un servicio delimitado mediante el AIDL, sigue estos pasos, que se describen en las siguientes secciones:

  1. Crea el archivo .aidl

    Este archivo define la interfaz de programación con firmas de métodos.

  2. Cómo implementar la interfaz

    Las herramientas del SDK de Android generan una interfaz con el lenguaje de programación Java basada en tu .aidl. Esta interfaz tiene una clase abstracta interna llamada Stub que extiende Binder e implementa métodos de tu interfaz de AIDL. Debes extender el Stub e implementar los métodos

  3. Expón la interfaz a los clientes

    Implementa un Service y anula onBind() para mostrar tu implementación de Stub. .

Precaución: Cualquier cambio que realices en la interfaz del AIDL después de tu primera versión debe ser retrocompatible para evitar romper otras aplicaciones que usan tu servicio. Esto se debe a que tu archivo .aidl debe copiarse a otras aplicaciones para que puedan acceder a la interfaz de tu servicio, debes mantener la compatibilidad con la versión interfaz de usuario.

Crear el archivo .aidl

El AIDL usa una sintaxis simple que te permite declarar una interfaz con uno o más métodos que pueden tomar parámetros y devolver valores. Los parámetros y los valores que se devuelven pueden ser de cualquier tipo, incluso otros Interfaces generadas con AIDL.

Debes construir el archivo .aidl con el lenguaje de programación Java. Cada .aidl debe definir una única interfaz y solo requiere la declaración y el método de la interfaz las firmas.

De manera predeterminada, el AIDL admite los siguientes tipos de datos:

  • Todos los tipos primitivos del lenguaje de programación Java (como int, long, char, boolean, etc.)
  • Arrays de tipos primitivos, como int[]
  • String
  • CharSequence
  • List

    Todos los elementos de List deben ser uno de los tipos de datos admitidos en este o una de las otras interfaces generadas con AIDL o objetos parcelables que declares. R De manera opcional, List se puede usar como una clase de tipo parametrizado, como List<String> La clase real concreta que el otro lado recibe siempre es un ArrayList, aunque el para usar la interfaz List.

  • Map

    Todos los elementos de Map deben ser uno de los tipos de datos admitidos en este o una de las otras interfaces generadas con AIDL o objetos parcelables que declares. Mapas de tipos con parametrización, como las del formato Map<String,Integer>, no son compatibles. La clase concreta real que la otra parte recibe siempre es un HashMap, aunque el método se genera para usar la interfaz Map. Considera usar un Bundle como alternativa a Map

Debes incluir una sentencia import para cada tipo adicional que no se haya mencionado anteriormente. incluso si están definidos en el mismo paquete que tu interfaz.

Cuando definas la interfaz de tu servicio, ten en cuenta lo siguiente:

  • Los métodos pueden tomar cero o más parámetros y pueden mostrar un valor o nulo.
  • Todos los parámetros que no sean primitivos requieren una etiqueta direccional que indique de qué manera se dirigen los datos: in, out o inout (consulta el siguiente ejemplo).

    Primitivas, String, IBinder y archivos generados por AIDL interfaces son in de forma predeterminada y no pueden ser de otra manera.

    Precaución: Limita la dirección a lo que es realmente porque el ordenamiento de parámetros es costoso.

  • Todos los comentarios de código incluidos en el archivo .aidl se incluyen en el generado el IBinder de la API, excepto los comentarios antes de las instrucciones de importación y paquete declaraciones.
  • Las constantes int y string se pueden definir en la interfaz del AIDL, como const int VERSION = 1;.
  • Las llamadas a los métodos se envían a través de un transact() código, que por lo general se basa en un índice de métodos en la interfaz. Porque esta dificulta el control de versiones, Puedes asignar el código de transacción a un método de forma manual: void method() = 10;.
  • Los argumentos anulables y los tipos de datos que se muestran deben anotarse con @nullable.

Este es un archivo .aidl de ejemplo:

// 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);
}

Guarda el archivo .aidl en el directorio src/ de tu proyecto. Cuando compila tu aplicación, las herramientas del SDK generan el archivo de interfaz IBinder en tu directorio gen/ del proyecto. El nombre del archivo generado coincide con el del archivo .aidl, pero con una extensión .java. Por ejemplo, IRemoteService.aidl da como resultado IRemoteService.java.

Si usas Android Studio, la generación incremental genera la clase binder casi inmediatamente. Si no usas Android Studio, la herramienta Gradle generará la clase binder la próxima vez que compilar tu aplicación. Compila tu proyecto con gradle assembleDebug o gradle assembleRelease apenas termines de escribir el archivo .aidl, para que tu código se pueda vincular con la clase generada.

Cómo implementar la interfaz

Cuando compilas tu aplicación, las herramientas del SDK de Android generan un archivo de interfaz .java. nombrado según el archivo .aidl. La interfaz generada incluye una subclase llamada Stub. que es una implementación abstracta de su interfaz superior, como YourInterface.Stub, y declara todos los métodos del archivo .aidl.

Nota: Stub también Define algunos métodos auxiliares, en particular asInterface(), que toma un objeto IBinder, generalmente el que se pasa al método de devolución de llamada onServiceConnected() del cliente. muestra una instancia de la interfaz de stub. Para obtener más información sobre cómo realizar esta transmisión, consulta la sección Cómo llamar a una IPC método.

Para implementar la interfaz generada del .aidl, extiende el Binder generado. como YourInterface.Stub y, luego, implementarás los métodos se hereda del archivo .aidl.

A continuación, se muestra un ejemplo de la implementación de una interfaz denominada IRemoteService, definida por el procedimiento anterior Ejemplo de IRemoteService.aidl con una instancia anónima:

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.
    }
};

Ahora, binder es una instancia de la clase Stub (una Binder), que define la interfaz de IPC del servicio. En el siguiente paso, se expone esta instancia clientes para que puedan interactuar con el servicio.

Ten en cuenta algunas reglas cuando implementes tu interfaz de AIDL:

  • No se garantiza que las llamadas entrantes se ejecuten en el subproceso principal, por lo que debes pensar sobre el multiprocesamiento desde el principio y compila correctamente tu servicio para que sea seguro para subprocesos.
  • De forma predeterminada, las llamadas de IPC son síncronas. Si sabes que el servicio requiere más que unos pocos milisegundos para completar una solicitud, no la llames desde el subproceso principal de la actividad. Es posible que cuelgue la aplicación, lo que hará que Android muestre el mensaje "La aplicación no responde". . Llámalo desde un subproceso separado en el cliente.
  • Solo los tipos de excepción enumerados en la documentación de referencia para Parcel.writeException() se envían de vuelta al emisor.

Exponer la interfaz a los clientes

Una vez que implementaste la interfaz para tu servicio, necesitas exponerla al clientes para poder vincularse a él. Cómo exponer la interfaz para tu servicio, extiende Service e implementa onBind() para mostrar una instancia de tu clase que implemente el Stub generado, como se explicó en la sección anterior. Aquí hay un ejemplo que expone la interfaz de ejemplo de IRemoteService a los clientes.

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.
        }
    };
}

Ahora, cuando un cliente, como una actividad, llama a bindService() para conectarse a este servicio, la devolución de llamada onServiceConnected() del cliente recibe el Instancia de binder que muestra onBind() del servicio .

El cliente también debe tener acceso a la clase de interfaz. Si el cliente y el servicio están en aplicaciones separadas, la aplicación del cliente debe tener una copia del archivo .aidl en su directorio src/, que genera el android.os.Binder y proporciona al cliente acceso a los métodos del AIDL.

Cuando el cliente recibe el IBinder en la devolución de llamada onServiceConnected(), debe llamar YourServiceInterface.Stub.asInterface(service) para transmitir parámetro para el 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;
    }
};

Para ver más códigos de muestra, consulta la RemoteService.java en ApiDemos.

Cómo pasar objetos por IPC

En Android 10 (nivel de API 29 o versiones posteriores), puedes definir Objetos Parcelable directamente en o AIDL. Los tipos que son compatibles como argumentos de la interfaz del AIDL y otros objetos parcelables también son se admiten aquí. Esto evita el trabajo adicional de escribir manualmente código de ordenamiento y una . Sin embargo, esto también crea un struct simple. Si se admiten los descriptores de acceso personalizados o alguna otra funcionalidad deseado, implementa Parcelable en su lugar.

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;
}

En la muestra de código anterior, se genera automáticamente una clase de Java con campos de números enteros left, top, right y bottom. Todo el código de ordenamiento relevante se implementa automáticamente, y el objeto se puede usar directamente sin tener que agregar para implementarlos.

También puedes enviar una clase personalizada de un proceso a otro a través de una interfaz IPC. Sin embargo, asegúrate de que el código de la clase esté disponible en el otro lado del canal de IPC tu clase debe admitir la interfaz Parcelable. Asistencia Parcelable es importante ya que permite que el sistema Android descomponga objetos en primitivas que se pueden ordenar entre los procesos.

Para crear una clase personalizada que admita Parcelable, haz lo siguiente: lo siguiente:

  1. Haz que tu clase implemente la interfaz Parcelable.
  2. Implementa writeToParcel, que toma el estado actual del objeto y lo escribe en un Parcel.
  3. Agrega a tu clase un campo estático llamado CREATOR que es un objeto que implementa la interfaz Parcelable.Creator.
  4. Por último, crea un archivo .aidl que declare tu clase parcelable, como se muestra a continuación archivo Rect.aidl.

    Si usas un proceso de compilación personalizado, no agregues el archivo .aidl a tu compilar. Al igual que un archivo de encabezado en lenguaje C, este archivo .aidl no se compila.

El AIDL usa estos métodos y campos en el código que genera para ordenar y anular el orden los objetos.

Por ejemplo, aquí hay un archivo Rect.aidl para crear una clase Rect que es parcelable (parcelable):

package android.graphics;

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

Este es un ejemplo de cómo la clase Rect implementa la Protocolo 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;
    }
}

El ordenamiento en la clase Rect es sencillo. Mira el otro métodos en Parcel para ver los otros tipos de valores que puedes escribir a un Parcel.

Advertencia: Recuerda las implicaciones de seguridad de recibir datos de otros procesos. En este caso, Rect lee cuatro números del Parcel, pero depende de ti asegurarte de que estén dentro del rango aceptable de para lo que el llamador intente hacer. Para obtener más información para proteger tu aplicación de software malicioso, consulta Sugerencias de seguridad.

Métodos con argumentos Bundle que contienen Parcelables

Si un método acepta un objeto Bundle que se espera que contenga parcelables, asegúrate de configurar el cargador de clases de Bundle llamando a Bundle.setClassLoader(ClassLoader) antes de intentar leer desde Bundle. De lo contrario, te encontrarás con ClassNotFoundException, aunque el parcelable esté definido correctamente en tu aplicación.

Por ejemplo, considera el siguiente archivo .aidl de muestra:

// 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);
}
Como se muestra en la siguiente implementación, ClassLoader es Se establece de forma explícita en Bundle antes de leer 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.
    }
};

Cómo llamar a un método de IPC

Para llamar a una interfaz remota definida con AIDL, sigue estos pasos en tu clase de llamada:

  1. Incluye el archivo .aidl en el directorio src/ del proyecto.
  2. Declara una instancia de la interfaz IBinder, que se genera según el o AIDL.
  3. Implementa ServiceConnection.
  4. Llama a Context.bindService(), pasando tu implementación de ServiceConnection.
  5. En tu implementación de onServiceConnected(), recibes un IBinder llamada service. Llamada YourInterfaceName.Stub.asInterface((IBinder)service) a Transmite el parámetro que se muestra al tipo YourInterface.
  6. Llama a los métodos que definiste en la interfaz. Trampa siempre Excepciones de DeadObjectException, que se arrojan cuando se interrumpe la conexión. Además, captura excepciones de SecurityException, que se producen cuando los dos procesos involucrados en la llamada al método de IPC tienen definiciones contradictorias con el AIDL.
  7. Para desconectarte, llama a Context.unbindService() con la instancia de tu interfaz.

Ten en cuenta estos puntos cuando llames a un servicio de IPC:

  • Los objetos se cuentan como referencia entre procesos.
  • Puedes enviar objetos anónimos como argumentos de métodos.

Para obtener más información sobre la vinculación a un servicio, consulta la Descripción general de los servicios vinculados.

Aquí hay algunos códigos de muestra que demuestran cómo llamar a un servicio creado con el AIDL, tomado de la muestra del servicio remoto en el proyecto 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);
            }
        }
    }
}