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 el cliente y el servicio acuerdan para comunicarse entre sí mediante la comunicación entre procesos (IPC).
En Android, por lo general, un proceso no puede acceder a la memoria de otro. Para comunicarse, deben descomponer sus objetos en primitivas que el sistema operativo pueda comprender y organizar los objetos a través de ese límite por ti. El código para realizar esa vinculación es tedioso de escribir, por lo que Android lo controla por ti con AIDL.
Nota: AIDL solo es necesario si permites que clientes de diferentes aplicaciones accedan a tu servicio para IPC y deseas controlar la multitarea en tu servicio. Si no necesitas realizar IPC simultáneos en diferentes aplicaciones, crea tu interfaz implementando un Binder
.
Si quieres realizar IPC, pero no necesitas controlar la multitarea, implementa tu interfaz con un Messenger
.
Independientemente de esto, asegúrate de comprender los servicios vinculados antes de implementar un AIDL.
Antes de comenzar a diseñar tu interfaz de AIDL, ten en cuenta que las llamadas a una interfaz de AIDL son llamadas a función directas. No hagas suposiciones sobre el subproceso en el que se produce la llamada. Lo que sucede es diferente según si la llamada proviene de un subproceso en el proceso local o en un proceso remoto:
- Las llamadas realizadas desde el proceso local se ejecutan en el mismo subproceso que realiza la llamada. Si este es tu subproceso de IU principal, ese subproceso se seguirá ejecutando en la interfaz de AIDL. Si es otro subproceso, ese es el que ejecuta tu código en el servicio. Por lo tanto, si solo los subprocesos
locales acceden al servicio, puedes controlar por completo qué subprocesos se ejecutan en él. Sin embargo, si ese es el caso, no uses AIDL en absoluto. En su lugar, crea la interfaz implementando un
Binder
. - Las llamadas de un proceso remoto se despachan desde un grupo de subprocesos que la plataforma mantiene dentro de tu propio proceso. Prepárate para recibir llamadas entrantes de subprocesos desconocidos, con varias llamadas que se produzcan al mismo tiempo. En otras palabras, una implementación de una interfaz AIDL debe ser completamente segura para los subprocesos. Las llamadas realizadas desde un subproceso en el mismo objeto remoto llegan en orden al extremo del receptor.
- La palabra clave
oneway
modifica el comportamiento de las llamadas remotas. Cuando se usa, una llamada remota no se bloquea. Envía los datos de la transacción y se muestra de inmediato. La implementación de la interfaz recibe esto como una llamada normal del grupo de subprocesosBinder
como una llamada remota normal. Si se usaoneway
con una llamada local, no hay impacto y la llamada sigue siendo síncrona.
Cómo definir una interfaz de AIDL
Define tu interfaz AIDL en un archivo .aidl
con la sintaxis del lenguaje de programación Java y, luego, guárdalo en el código fuente, en el directorio src/
, de la aplicación que aloja el servicio y de cualquier otra aplicación que se vincule al servicio.
Cuando compilas cada aplicación que contiene el archivo .aidl
, las herramientas del SDK de Android generan una interfaz IBinder
basada en el archivo .aidl
y la guardan en el directorio gen/
del proyecto. El servicio debe implementar la interfaz IBinder
según corresponda. Luego, las aplicaciones cliente pueden vincularse al servicio y llamar a métodos desde IBinder
para realizar IPC.
Para crear un servicio limitado con AIDL, sigue estos pasos, que se describen en las siguientes secciones:
- Cómo crear el archivo
.aidl
Este archivo define la interfaz de programación con firmas de métodos.
- Implementa la interfaz
Las herramientas del SDK de Android generan una interfaz en el lenguaje de programación Java según tu archivo
.aidl
. Esta interfaz tiene una clase abstracta interna llamadaStub
que extiendeBinder
e implementa métodos de tu interfaz AIDL. Debes extender la claseStub
e implementar los métodos. - Expón la interfaz a los clientes
Implementa un
Service
y anulaonBind()
para mostrar la implementación de la claseStub
.
Precaución: Cualquier cambio que realices en la interfaz de AIDL después de tu primera versión debe ser retrocompatible para evitar que se dañen otras aplicaciones que usen tu servicio. Es decir, como tu archivo .aidl
se debe copiar en otras aplicaciones para que puedan acceder a la interfaz de tu servicio, debes mantener la compatibilidad con la interfaz original.
Crear el archivo .aidl
AIDL usa una sintaxis simple que te permite declarar una interfaz con uno o más métodos que pueden tomar parámetros y mostrar valores. Los parámetros y los valores que se muestran pueden ser de cualquier tipo, incluso de otras interfaces generadas por AIDL.
Debes crear el archivo .aidl
con el lenguaje de programación Java. Cada archivo .aidl
debe definir una sola interfaz y solo requiere la declaración de la interfaz y las firmas de método.
De manera predeterminada, el AIDL admite los siguientes tipos de datos:
- Todos los tipos primitivos, con la excepción de
short
, en el lenguaje de programación Java (comoint
,long
,char
,boolean
, etcétera) - Arreglos de cualquier tipo, como
int[]
oMyParcelable[]
String
CharSequence
List
Todos los elementos de
List
deben ser uno de los tipos de datos admitidos en esta lista o una de las otras interfaces o elementos parcelables generados por AIDL que declares. De manera opcional, unList
se puede usar como una clase de tipo parametrizado, comoList<String>
. La clase concreta real que recibe el otro lado siempre es unaArrayList
, aunque el método se genera para usar la interfazList
.Map
Todos los elementos de
Map
deben ser uno de los tipos de datos admitidos en esta lista o una de las otras interfaces o elementos parcelables generados por AIDL que declares. No se admiten los mapas de tipos parametrizados, como los del formularioMap<String,Integer>
. La clase concreta real que recibe el otro lado siempre es unaHashMap
, aunque el método se genera para usar la interfazMap
. Considera usar unBundle
como alternativa aMap
.
Debes incluir una sentencia import
para cada tipo adicional que no se haya indicado anteriormente, incluso si se definen 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 un valor nulo.
- Todos los parámetros no primitivos requieren una etiqueta de dirección que indique hacia qué lado van los datos:
in
,out
oinout
(consulta el ejemplo a continuación).Las interfaces primitivas,
String
,IBinder
y generadas por AIDL sonin
de forma predeterminada y no pueden ser de otra manera.Precaución: Limita la dirección a lo que realmente se necesita, ya que el marshalling de parámetros es costoso.
- Todos los comentarios de código incluidos en el archivo
.aidl
se incluyen en la interfazIBinder
generada, excepto los comentarios que se encuentran antes de las instrucciones de importación y paquete. - Las constantes de cadena y de int se pueden definir en la interfaz de AIDL, como
const int VERSION = 1;
. - Las llamadas a métodos se despachan con un código
transact()
, que normalmente se basa en un índice de método en la interfaz. Debido a que esto dificulta el control de versiones, puedes asignar manualmente el código de transacción a un método:void method() = 10;
. - Los argumentos anulables y los tipos de datos que se muestran deben anotarse con
@nullable
.
A continuación, se muestra 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 compilas tu aplicación, las herramientas del SDK generan el archivo de interfaz IBinder
en el directorio gen/
de tu proyecto. El nombre del archivo generado coincide con el nombre del archivo .aidl
, pero con una extensión .java
. Por ejemplo, IRemoteService.aidl
genera 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 de Binder la próxima vez que compiles tu aplicación. Compila tu proyecto con gradle assembleDebug
o gradle assembleRelease
apenas termines de escribir el archivo .aidl
para que tu código pueda vincularse 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
que se nombra según tu 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 IBinder
, por lo general, el que se pasa al método de devolución de llamada onServiceConnected()
de un cliente, y muestra una instancia de la interfaz de stub. Para obtener más detalles sobre cómo realizar esta transmisión, consulta la sección Cómo llamar a un método de IPC.
Para implementar la interfaz generada desde .aidl
, extiende la interfaz Binder
generada, como YourInterface.Stub
, y, luego, implementa los métodos heredados del archivo .aidl
.
A continuación, se muestra un ejemplo de implementación de una interfaz llamada IRemoteService
, definida por el ejemplo anterior de IRemoteService.aidl
, que usa 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
(un Binder
), que define la interfaz de IPC para el servicio. En el siguiente paso, esta instancia se expone a los 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 en la multitarea desde el principio y compilar tu servicio de forma correcta para que sea seguro para subprocesos.
- De forma predeterminada, las llamadas a IPC son síncronas. Si sabes que el servicio tarda más de unos pocos milisegundos en completar una solicitud, no lo llames desde el subproceso principal de la actividad. Es posible que la aplicación deje de funcionar, lo que hará que Android muestre un diálogo de "La aplicación no responde". Llámala desde un subproceso independiente en el cliente.
- Solo los tipos de excepción que se enumeran en la documentación de referencia de
Parcel.writeException()
se envían al llamador.
Exponer la interfaz a los clientes
Una vez que hayas implementado la interfaz de tu servicio, debes exponerla a los clientes para que puedan vincularse a ella. Para exponer la interfaz de 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. Este es un ejemplo de servicio que expone la interfaz de ejemplo 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 la instancia binder
que muestra el método onBind()
del servicio.
El cliente también debe tener acceso a la clase de interfaz. Por lo tanto, 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 la interfaz android.os.Binder
y le proporciona al cliente acceso a los métodos AIDL.
Cuando el cliente recibe el IBinder
en la devolución de llamada onServiceConnected()
, debe llamar a YourServiceInterface.Stub.asInterface(service)
para transmitir el parámetro que se muestra al tipo YourServiceInterface
:
Kotlin
var iRemoteService: IRemoteService? = null val mConnection = object : ServiceConnection { // Called when the connection with the service is established. override fun onServiceConnected(className: ComponentName, service: IBinder) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service) } // Called when the connection with the service disconnects unexpectedly. override fun onServiceDisconnected(className: ComponentName) { Log.e(TAG, "Service has unexpectedly disconnected") iRemoteService = null } }
Java
IRemoteService iRemoteService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established. public void onServiceConnected(ComponentName className, IBinder service) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly. public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "Service has unexpectedly disconnected"); iRemoteService = null; } };
Para acceder a otros ejemplos de código, consulta la clase
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 AIDL. Aquí también se admiten los tipos que se admiten como argumentos de interfaz de AIDL y otros elementos parcelables. Esto evita el trabajo adicional de escribir manualmente el código de marshalling y una clase personalizada. Sin embargo, esto también crea una estructura sin procesar. Si se desean accesores personalizados o alguna otra funcionalidad, implementa 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; }
La muestra de código anterior genera automáticamente una clase Java con campos enteros left
, top
, right
y bottom
. Todo el código de marshalling relevante se implementa automáticamente, y el objeto se puede usar directamente sin tener que agregar ninguna implementación.
También puedes enviar una clase personalizada de un proceso a otro a través de una interfaz de IPC. Sin embargo, asegúrate de que el código de tu clase esté disponible para el otro lado del canal de IPC y de que tu clase sea compatible con la interfaz Parcelable
. La compatibilidad con Parcelable
es importante porque permite que el sistema Android decomponga objetos en primitivas que se pueden organizar en varios procesos.
Para crear una clase personalizada que admita Parcelable
, haz lo siguiente:
- Haz que tu clase implemente la interfaz
Parcelable
. - Implementa
writeToParcel
, que toma el estado actual del objeto y lo escribe en unParcel
. - Agrega un campo estático llamado
CREATOR
a tu clase que sea un objeto que implemente la interfazParcelable.Creator
. - Por último, crea un archivo
.aidl
que declare tu clase parcelable, como se muestra en el siguiente archivoRect.aidl
.Si usas un proceso de compilación personalizado, no agregues el archivo
.aidl
a tu compilación. Al igual que un archivo de encabezado en el lenguaje C, este archivo.aidl
no se compila.
AIDL usa estos métodos y campos en el código que genera para agrupar y desagrupar tus objetos.
Por ejemplo, este es un archivo Rect.aidl
para crear una clase Rect
que se puede particionar:
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 el 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 marshalling en la clase Rect
es sencillo. Consulta los otros métodos de Parcel
para ver los otros tipos de valores que puedes escribir en un Parcel
.
Advertencia: Recuerda las implicaciones de seguridad de recibir datos de otros procesos. En este caso, Rect
lee cuatro números de Parcel
, pero depende de ti asegurarte de que estén dentro del rango aceptable de valores para lo que el llamador intenta hacer. Para obtener más información sobre cómo proteger tu aplicación del software malicioso, consulta Sugerencias de seguridad.
Métodos con argumentos Bundle que contienen Parcelables
Si un método acepta un objetoBundle
que se espera que contenga elementos 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, se produce un error ClassNotFoundException
, aunque el elemento 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); }
ClassLoader
se configura 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:
- Incluye el archivo
.aidl
en el directoriosrc/
del proyecto. - Declara una instancia de la interfaz
IBinder
, que se genera en función del AIDL. - Implementa
ServiceConnection
. - Llama a
Context.bindService()
y pasa tu implementación deServiceConnection
. - En tu implementación de
onServiceConnected()
, recibes una instancia deIBinder
, llamadaservice
. Llama aYourInterfaceName.Stub.asInterface((IBinder)service)
para transmitir el parámetro que se muestra al tipoYourInterface
. - Llama a los métodos que definiste en la interfaz. Siempre captura las excepciones
DeadObjectException
, que se producen cuando se corta la conexión. Además, atrapa las excepcionesSecurityException
, que se generan cuando los dos procesos involucrados en la llamada al método IPC tienen definiciones de AIDL en conflicto. - Para desconectarte, llama a
Context.unbindService()
con la instancia de tu interfaz.
Ten en cuenta los siguientes 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étodo.
Para obtener más información sobre la vinculación a un servicio, consulta la descripción general de los servicios vinculados.
Este es un ejemplo de código que demuestra cómo llamar a un servicio creado por AIDL, tomado del ejemplo de 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—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); } } } }