A Linguagem de definição de interface do Android (AIDL) é semelhante a outras IDLs: ela permite definir a interface de programação que o cliente e o serviço concordam para se comunicar usando comunicação interprocesso (IPC).
No Android, um processo normalmente não pode acessar a memória de outro. Para se comunicarem, eles precisam decompor os objetos em primitivos que o sistema operacional possa entender e organizar os objetos em todo esse limite para você. O código para fazer essa associação é tedioso de escrever. Por isso, o Android o processa para você com o AIDL.
Observação:o AIDL só é necessário se você permitir que clientes de
aplicativos diferentes acessem seu serviço para IPC e se quiser processar várias linhas de execução no
serviço. Se você não precisar executar o IPC simultâneo em
diferentes aplicativos, crie sua interface implementando um
Binder
.
Se você quiser executar o IPC, mas não precisa processar várias linhas de execução,
implemente a interface usando um Messenger
.
De qualquer forma, entenda os serviços vinculados antes de
implementar um AIDL.
Antes de começar a projetar sua interface AIDL, saiba que as chamadas para uma interface AIDL são chamadas de função direta. Não faça suposições sobre a linha de execução em que a chamada ocorre. O que acontece é diferente dependendo se a chamada é de uma linha de execução no processo local ou remoto:
- As chamadas feitas pelo processo local são executadas na mesma linha de execução que está fazendo a chamada. Se
essa for a linha de execução de interface principal, ela continuará sendo executada na interface AIDL. Se for
outra linha de execução, ela será a que executa o código no serviço. Assim, se apenas linhas de execução
locais estiverem acessando o serviço, você poderá controlar completamente quais linhas de execução estão sendo executadas nele. No entanto,
se esse for o caso, não use o AIDL. Em vez disso, crie a
interface implementando um
Binder
. - As chamadas de um processo remoto são enviadas de um pool de linhas de execução que a plataforma mantém no seu próprio processo. Esteja preparado para chamadas recebidas de linhas de execução desconhecidas, com várias chamadas ocorrendo ao mesmo tempo. Em outras palavras, uma implementação de uma interface AIDL precisa ser completamente threadsafe. As chamadas feitas de uma linha de execução no mesmo objeto remoto chegam na ordem no receptor.
- A palavra-chave
oneway
modifica o comportamento das chamadas remotas. Quando é usado, uma chamada remota não é bloqueada. Ele envia os dados da transação e retorna imediatamente. A implementação da interface recebe isso como uma chamada normal do pool de linhas de execuçãoBinder
como uma chamada remota normal. Seoneway
for usado com uma chamada local, não haverá impacto e a chamada ainda será síncrona.
Definição de uma interface AIDL
Defina a interface AIDL em um arquivo .aidl
usando a sintaxe da linguagem de
programação Java e salve-o no código-fonte, no diretório src/
, do
aplicativo que hospeda o serviço e de qualquer outro aplicativo vinculado ao serviço.
Quando você cria cada aplicativo que contém o arquivo .aidl
, as ferramentas do SDK do Android
geram uma interface IBinder
com base no arquivo .aidl
e a salvam no
diretório gen/
do projeto. O serviço precisa implementar a interface
IBinder
conforme apropriado. Os aplicativos cliente podem se vincular ao serviço e chamar métodos do
IBinder
para executar o IPC.
Para criar um serviço delimitado usando AIDL, siga estas etapas, descritas nas seções a seguir:
- Criar o arquivo
.aidl
Esse arquivo define a interface de programação com assinaturas de método.
- Implementar a interface
As ferramentas do SDK do Android geram uma interface na linguagem de programação Java com base no arquivo
.aidl
. Essa interface tem uma classe abstrata interna chamadaStub
, que estendeBinder
e implementa métodos da interface AIDL. É necessário ampliar a classeStub
e implementar os métodos. - Exposiça a interface para os clientes
Implemente um
Service
e substituaonBind()
para retornar a implementação da classeStub
.
Atenção:todas as mudanças feitas na interface AIDL após
a primeira versão precisam permanecer compatíveis com versões anteriores para evitar a interrupção de outros aplicativos
que usam seu serviço. Ou seja, como o arquivo .aidl
precisa ser copiado para outros aplicativos
para que eles possam acessar a interface do serviço, é necessário manter o suporte à interface
original.
Criação do arquivo .aidl
O AIDL usa uma sintaxe simples que permite declarar uma interface com um ou mais métodos que podem receber parâmetros e retornar valores. Os parâmetros e os valores de retorno podem ser de qualquer tipo, até mesmo outras interfaces geradas pelo AIDL.
Você precisa criar o arquivo .aidl
usando a linguagem de programação Java. Cada arquivo .aidl
precisa definir uma única interface e requer apenas a declaração de interface e as assinaturas
de método.
Por padrão, a AIDL suporta os seguintes tipos de dados:
- Todos os tipos primitivos, com exceção de
short
, na linguagem de programação Java (comoint
,long
,char
,boolean
e assim por diante) - Matrizes de qualquer tipo, como
int[]
ouMyParcelable[]
String
CharSequence
List
Todos os elementos no
List
precisam ser um dos tipos de dados aceitos nesta lista ou uma das outras interfaces ou parceláveis gerados por AIDL que você declarar. UmList
pode ser usado como uma classe de tipo parametrizada, comoList<String>
. A classe concreta real que o outro lado recebe é sempre umaArrayList
, embora o método seja gerado para usar a interfaceList
.Map
Todos os elementos no
Map
precisam ser um dos tipos de dados aceitos nesta lista ou uma das outras interfaces ou parceláveis gerados por AIDL que você declarar. Mapas de tipo parametrizados, como os do formulárioMap<String,Integer>
, não são aceitos. A classe concreta real que o outro lado recebe é sempre umHashMap
, embora o método seja gerado para usar a interfaceMap
. Considere usar umBundle
como alternativa aoMap
.
É necessário incluir uma instrução import
para cada tipo adicional não listado anteriormente,
mesmo que eles sejam definidos no mesmo pacote da interface.
Ao definir a interface de serviço, tenha ciência do seguinte:
- Os métodos podem ter zero ou mais parâmetros e retornar um valor ou void.
- Todos os parâmetros não primitivos exigem uma tag direcional indicando para onde os dados vão:
in
,out
ouinout
(confira o exemplo abaixo).As interfaces primitivas,
String
,IBinder
e geradas pelo AIDL sãoin
por padrão e não podem ser de outro tipo.Atenção:limite a direção ao que realmente é necessário, porque o marshalling de parâmetros é caro.
- Todos os comentários de código incluídos no arquivo
.aidl
são incluídos na interfaceIBinder
gerada, exceto comentários antes das instruções de importação e pacote. - As constantes de string e int podem ser definidas na interface da AIDL, como
const int VERSION = 1;
. - As chamadas de método são enviadas por um
código
transact()
, que normalmente é baseado em um índice de método na interface. Como isso dificulta o controle de versões, é possível atribuir manualmente o código da transação a um método:void method() = 10;
. - Os argumentos e tipos de retorno nulos precisam ser anotados usando
@nullable
.
Confira um exemplo de arquivo .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); }
Salve o arquivo .aidl
no diretório src/
do projeto. Ao
criar o aplicativo, as ferramentas do SDK geram o arquivo de interface IBinder
no
diretório gen/
do projeto. O nome do arquivo gerado corresponde ao nome do arquivo .aidl
, mas
com uma extensão .java
. Por exemplo, IRemoteService.aidl
resulta em IRemoteService.java
.
Se você usar o Android Studio, a compilação incremental gerará a classe binder quase imediatamente.
Se você não usa o Android Studio, a ferramenta Gradle gera a classe de vinculação na próxima vez que você
cria o aplicativo. Crie seu projeto com gradle assembleDebug
ou gradle assembleRelease
assim que terminar de escrever o arquivo .aidl
,
para que o código possa ser vinculado à classe gerada.
Implementação da interface
Ao criar seu aplicativo, as ferramentas do SDK do Android geram um arquivo de interface .java
com o nome do arquivo .aidl
. A interface gerada inclui uma subclasse chamada Stub
,
que é uma implementação abstrata da interface pai, como YourInterface.Stub
, e declara todos os métodos do arquivo .aidl
.
Observação:Stub
também
define alguns métodos auxiliares, principalmente asInterface()
, que recebe um IBinder
, geralmente o transmitido para o método de callback onServiceConnected()
de um cliente, e
retorna uma instância da interface stub. Para mais detalhes sobre como fazer esse envio, consulte a seção Como chamar um método
IPC.
Para implementar a interface gerada pelo .aidl
, estenda a interface Binder
gerada, como YourInterface.Stub
, e implemente os métodos
herdados do arquivo .aidl
.
Confira um exemplo de implementação de uma interface chamada IRemoteService
, definida pelo exemplo
IRemoteService.aidl
anterior, usando uma instância 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. } };
Agora, o binder
é uma instância da classe Stub
(um Binder
),
que define a interface IPC do serviço. Na próxima etapa, essa instância é exposta aos
clientes para que eles possam interagir com o serviço.
Observe algumas regras ao implementar sua interface AIDL:
- Não há garantia de que as chamadas recebidas sejam executadas na linha de execução principal. Portanto, pense em linhas de execução múltiplas desde o início e crie seu serviço de maneira adequada para que ele seja seguro para linhas de execução.
- Por padrão, as chamadas de IPC são síncronas. Se você souber que o serviço leva mais de alguns milissegundos para concluir uma solicitação, não o chame da linha de execução principal da atividade. Isso pode travar o aplicativo, fazendo com que o Android mostre uma caixa de diálogo "O app não está respondendo". Chame-o de uma linha de execução separada no cliente.
- Somente os tipos de exceção listados na documentação de referência de
Parcel.writeException()
são enviados de volta para o autor da chamada.
Exposição da interface aos clientes
Depois de implementar a interface do serviço, você precisa expô-la para
os clientes se vincularem a ela. Para expor a interface
do serviço, estenda Service
e implemente onBind()
para retornar uma instância da classe que implementa
o Stub
gerado, conforme discutido na seção anterior. Confira um exemplo
de serviço que expõe a interface de exemplo IRemoteService
aos 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. } }; }
Agora, quando um cliente, como uma atividade, chama bindService()
para se conectar a esse serviço, o callback onServiceConnected()
do cliente recebe a
instância binder
retornada pelo método
onBind()
do serviço.
O cliente também precisa ter acesso à classe de interface. Portanto, se o cliente e o serviço estiverem em
aplicativos separados, o aplicativo do cliente precisará ter uma cópia do arquivo .aidl
no diretório src/
, que gera a interface android.os.Binder
,
oferecendo ao cliente acesso aos métodos AIDL.
Quando o cliente recebe o IBinder
no callback onServiceConnected()
, ele precisa chamar
YourServiceInterface.Stub.asInterface(service)
para transmitir o parâmetro
retornado ao 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 conferir mais exemplos de código, consulte a classe
RemoteService.java
em
ApiDemos.
Passagem de objetos via IPC
No Android 10 (nível 29 da API ou mais recente), é possível definir
objetos Parcelable
diretamente no
AIDL. Os tipos que têm suporte como argumentos de interface AIDL e outros parceláveis também
têm suporte aqui. Isso evita o trabalho extra de escrever manualmente o código de marshalling e uma classe
personalizada. No entanto, isso também cria uma estrutura simples. Se quiser usar acessórios personalizados ou outras funcionalidades, implemente 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; }
O exemplo de código anterior gera automaticamente uma classe Java com campos inteiros left
,
top
, right
e bottom
. Todo o código de marshalling relevante é
implementado automaticamente, e o objeto pode ser usado diretamente sem precisar adicionar nenhuma
implementação.
Também é possível enviar uma classe personalizada de um processo para outro usando uma interface IPC. No entanto,
verifique se o código da sua classe está disponível para o outro lado do canal IPC e
se a classe oferece suporte à interface Parcelable
. O suporte a
Parcelable
é importante
porque permite que o sistema Android decomponha objetos em primitivos que podem ser agrupados
em vários processos.
Para criar uma classe personalizada compatível com Parcelable
, faça o
seguinte:
- Faça com que a classe implemente a interface
Parcelable
. - Implemente
writeToParcel
, que recebe o estado atual do objeto e o grava em umParcel
. - Adicione um campo estático chamado
CREATOR
à sua classe, que é um objeto que implementa a interfaceParcelable.Creator
. - Por fim, crie um arquivo
.aidl
que declare sua classe parcelável, conforme mostrado no arquivoRect.aidl
abaixo.Se você estiver usando um processo de build personalizado, não adicione o arquivo
.aidl
ao build. Semelhante a um arquivo de cabeçalho na linguagem C, este arquivo.aidl
não é compilado.
O AIDL usa esses métodos e campos no código que ele gera para organizar e desorganizar seus objetos.
Por exemplo, aqui está um arquivo Rect.aidl
para criar uma classe Rect
que é
parcializável:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
Confira um exemplo de como a classe Rect
implementa o
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; } }
O marshalling na classe Rect
é simples. Confira os outros
métodos em Parcel
para ver os outros tipos de valores que podem ser gravados
em um Parcel
.
Aviso:lembre-se das implicações de segurança de receber
dados de outros processos. Nesse caso, o Rect
lê quatro números do Parcel
, mas cabe a você garantir que eles estejam dentro do intervalo de
valores aceitável para o que o autor da chamada está tentando fazer. Para mais informações sobre como proteger seu aplicativo contra malware, consulte Dicas de segurança.
Métodos com argumentos Bundle contendo Parcelables
Se um método aceitar um objetoBundle
que deve conter
parceláveis, defina o classloader do Bundle
chamando
Bundle.setClassLoader(ClassLoader)
antes de tentar ler
o Bundle
. Caso contrário, você vai encontrar ClassNotFoundException
, mesmo que o parcelável esteja definido corretamente no aplicativo.
Por exemplo, considere o seguinte arquivo .aidl
de exemplo:
// 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
é
definido explicitamente no Bundle
antes de ler 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. } };
Chamada de um método IPC
Para chamar uma interface remota definida com AIDL, siga estas etapas na classe de chamada:
- Inclua o arquivo
.aidl
no diretóriosrc/
do projeto. - Declare uma instância da interface
IBinder
, que é gerada com base no AIDL. - Implemente
ServiceConnection
. - Chame
Context.bindService()
, transmitindo a implementaçãoServiceConnection
. - Na implementação de
onServiceConnected()
, você recebe uma instânciaIBinder
, chamadaservice
. ChameYourInterfaceName.Stub.asInterface((IBinder)service)
para transmitir o parâmetro retornado ao tipoYourInterface
. - Chame os métodos que definiu em sua interface. Sempre capture
exceções
DeadObjectException
, que são geradas quando a conexão é interrompida. Além disso, capture exceçõesSecurityException
, que são geradas quando os dois processos envolvidos na chamada de método IPC têm definições de AIDL conflitantes. - Para desconectar, chame
Context.unbindService()
com a instância da sua interface.
Considere estes pontos ao chamar um serviço de IPC:
- Objetos são referências contadas entre processos.
- É possível enviar objetos anônimos como argumentos de método.
Para mais informações sobre a vinculação a um serviço, leia a Visão geral dos serviços vinculados.
Confira um exemplo de código que demonstra como chamar um serviço criado pelo AIDL, extraído do exemplo de serviço remoto no projeto 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); } } } }