Android Interface Definition Language (AIDL)

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 comunicarem usando a comunicação entre processos (IPC).

No Android, um processo normalmente não pode acessar a memória de outro. Para falar, eles precisam decompor os objetos em primitivos que o sistema operacional pode entender e gerenciar para você. O código para esse gerenciamento é tedioso de escrever, então o Android processa isso com a AIDL.

Observação:a AIDL é necessária apenas se você permite que clientes de aplicativos diferentes acessem seu serviço para IPC e quer lidar com várias linhas de execução no seu serviço. Se você não precisar executar IPC simultânea em diferentes aplicativos, implemente um Binder para criar sua interface. Se você quiser realizar a IPC, mas não precisar processar várias linhas de execução, implemente sua interface usando um Messenger. Independentemente disso, entenda os serviços vinculados antes de implementar uma AIDL.

Antes de começar a projetar a interface AIDL, lembre-se de que chamadas para essa interface são chamadas de função diretas. 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 em um processo remoto:

  • As chamadas feitas do processo local são executadas na mesma linha de execução que está fazendo a chamada. Se essa for sua linha de execução de IU principal, ela continuará sendo executada na interface AIDL. Se for outra linha de execução, é ela 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 serão executadas nele. No entanto, se esse for o caso, não use a AIDL. Em vez disso, crie a interface implementando uma Binder.
  • As chamadas de um processo remoto são despachadas de um pool de linhas de execução que a plataforma mantém dentro do seu próprio processo. Prepare-se para receber chamadas de linhas de execução desconhecidas, com várias chamadas acontecendo ao mesmo tempo. Em outras palavras, uma implementação de uma interface AIDL precisa ser completamente segura para linhas de execução. As chamadas feitas de uma linha de execução no mesmo objeto remoto chegam em ordem no receptor.
  • A palavra-chave oneway modifica o comportamento de chamadas remotas. Quando usado, uma chamada remota não é bloqueada. Ela envia os dados da transação e retorna imediatamente. Em algum momento, a implementação da interface recebe isso como uma chamada normal do pool de linhas de execução Binder como uma chamada remota normal. Se oneway 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-a 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 salva no diretório gen/ do projeto. O serviço precisa implementar a interface IBinder conforme apropriado. Os aplicativos clientes podem se vincular ao serviço e chamar métodos do IBinder para realizar a IPC.

Para criar um serviço vinculado usando AIDL, siga estas etapas, que são descritas nas seções a seguir:

  1. Crie o arquivo .aidl

    Esse arquivo define a interface de programação com assinaturas de método.

  2. Implementar a interface

    As Ferramentas do SDK do Android geram uma interface na linguagem de programação Java com base no seu arquivo .aidl. Essa interface tem uma classe abstrata interna chamada Stub que estende Binder e implementa métodos da interface AIDL. Estenda a classe Stub e implemente os métodos.

  3. Exposição da interface aos clientes

    Implemente um Service e substitua onBind() para retornar a implementação da classe Stub.

Cuidado:todas as alterações feitas na interface AIDL após a primeira versão precisam permanecer compatíveis com versões anteriores para evitar interromper 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 seu serviço, é necessário manter a compatibilidade com a interface original.

Criação do arquivo .aidl

A 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 com AIDL.

É preciso construir o arquivo .aidl usando a linguagem de programação Java. Cada arquivo .aidl precisa definir uma única interface e requer apenas a declaração da interface e as assinaturas de método.

Por padrão, a AIDL suporta os seguintes tipos de dados:

  • Todos os tipos primitivos na linguagem de programação Java (como int, long, char, boolean e assim por diante)
  • Matrizes de tipos primitivos, como int[]
  • String
  • CharSequence
  • List

    Todos os elementos na List precisam ser um dos tipos de dados com suporte nessa lista ou em uma das outras interfaces geradas com AIDL ou parcelables declarados. Um List pode ser usado opcionalmente como uma classe de tipo parametrizada, como List<String>. A classe concreta real que o outro lado recebe é sempre um ArrayList, embora o método seja gerado para usar a interface List.

  • Map

    Todos os elementos na Map precisam ser um dos tipos de dados com suporte nessa lista ou em uma das outras interfaces geradas com AIDL ou parcelables declarados. Mapas de tipo parametrizado, como aqueles no formato Map<String,Integer>, não são aceitos. A classe concreta real que o outro lado recebe é sempre um HashMap, embora o método seja gerado para usar a interface Map. Use um Bundle como alternativa a Map.

É necessário incluir uma instrução import para cada outro tipo não listado anteriormente, mesmo que ele esteja definido no mesmo pacote da interface.

Ao definir a interface de serviço, tenha ciência do seguinte:

  • Os métodos podem receber zero ou mais parâmetros e retornar um valor ou nulo.
  • Todos os parâmetros não primitivos exigem uma tag direcional que indica para onde os dados vão: in, out ou inout (veja o exemplo abaixo).

    Primitivas, String, IBinder e interfaces geradas por AIDL são in por padrão e não podem ser de outra forma.

    Cuidado:limite a direção ao que é realmente necessário, porque o gerenciamento dos parâmetros é caro.

  • Todos os comentários de código incluídos no arquivo .aidl são incluídos na interface IBinder gerada, exceto os comentários antes das instruções de importação e do pacote.
  • As constantes string e int podem ser definidas na interface AIDL, como const int VERSION = 1;.
  • As chamadas de método são despachadas 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;.
  • Argumentos anuláveis e tipos de retorno precisam ser anotados usando @nullable.

Veja 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 vai gerar a classe de binder na próxima vez que você criar o aplicativo. Crie seu projeto com gradle assembleDebug ou gradle assembleRelease assim que terminar de gravar o arquivo .aidl, para que seu código possa ser vinculado à classe gerada.

Implementação da interface

Ao criar o app, 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 mãe, 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 usa um IBinder, geralmente transmitido para o método de callback onServiceConnected() de um cliente, e retorna uma instância da interface de stub. Para mais detalhes sobre como fazer essa conversão, 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.

Este é um exemplo de implementação de uma interface chamada IRemoteService, definido 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, binder é uma instância da classe Stub (uma 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.

Esteja ciente de algumas regras ao implementar a interface AIDL:

  • Não há garantia de que as chamadas recebidas sejam executadas na linha de execução principal. Por isso, é necessário pensar em várias linhas de execução desde o início e criar corretamente seu serviço para que seja seguro para linhas de execução.
  • Por padrão, as chamadas IPC são síncronas. Se você sabe que o serviço leva mais do que alguns milissegundos para concluir uma solicitação, não o chame na linha de execução principal da atividade. Ele pode travar o aplicativo, fazendo com que o Android exiba uma caixa de diálogo "O aplicativo não está respondendo". Chame-o em uma linha de execução separada no cliente.
  • Somente os tipos de exceção listados na documentação de referência para Parcel.writeException() são enviados de volta ao autor da chamada.

Exposição da interface aos clientes

Depois de implementar a interface do serviço, é necessário expô-la aos clientes para que eles possam se vincular 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. Veja um exemplo de serviço que expõe a interface de exemplo IRemoteService para os 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, por exemplo, uma atividade, chamar bindService() para se conectar a esse serviço, o callback onServiceConnected() do cliente receberá a instância binder retornada pelo método onBind() do serviço.

O cliente também precisa ter acesso à classe da interface. 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, fornecendo 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 para o 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 mais exemplos de código, consulte a classe RemoteService.java em ApiDemos.

Passagem de objetos via IPC

No Android 10 (API de nível 29 ou mais recente), é possível definir objetos Parcelable diretamente na AIDL. Os tipos com suporte como argumentos da interface AIDL e outros parcelables também são aceitos 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 básica. Se você quiser usar acessadores personalizados ou outra funcionalidade, 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 os campos de números inteiros left, top, right e bottom. Todo o código de gerenciamento relevante é implementado automaticamente, e o objeto pode ser usado diretamente, sem necessidade de adicionar nenhuma implementação.

Também é possível enviar uma classe personalizada de um processo a 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 precisa ser compatível com a interface Parcelable. O suporte a Parcelable é importante porque permite que o sistema Android decomponha objetos em primitivos que podem ser gerenciados entre processos.

Para criar uma classe personalizada com suporte ao Parcelable, faça o seguinte:

  1. Faça com que sua classe implemente a interface Parcelable.
  2. Implemente writeToParcel, que usa o estado atual do objeto e o grava em um Parcel.
  3. Adicione um campo estático chamado CREATOR à classe, que é um objeto que implementa a interface Parcelable.Creator.
  4. Por fim, crie um arquivo .aidl que declare sua classe parcelable, conforme mostrado no seguinte arquivo Rect.aidl.

    Se você estiver usando um processo de build personalizado, não adicione o arquivo .aidl ao seu build. Semelhante a um arquivo principal na linguagem C, esse arquivo .aidl não é compilado.

A AIDL usa esses métodos e campos no código que gera para gerenciar os objetos.

Por exemplo, aqui está um arquivo Rect.aidl para criar uma classe Rect que é parcelable:

package android.graphics;

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

Veja 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 gerenciamento 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 ao receber dados de outros processos. Nesse caso, o Rect lê quatro números da Parcel, mas cabe a você garantir que eles estejam dentro do intervalo aceitável de valores para o que o autor da chamada estiver tentando fazer. Confira mais informações sobre como manter seu aplicativo protegido contra malware em Dicas de segurança.

Métodos com argumentos Bundle contendo Parcelables

Se um método aceitar um objeto Bundle que precisa conter parceláveis, defina o carregador de classe do Bundle chamando Bundle.setClassLoader(ClassLoader) antes de tentar ler do Bundle. Caso contrário, o resultado será ClassNotFoundException, mesmo que o parcelable esteja definido corretamente no app.

Por exemplo, considere o exemplo de arquivo .aidl a seguir:

// 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);
}
Conforme mostrado na implementação abaixo, o ClassLoader é definido explicitamente no Bundle antes da leitura de Rect:

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

Chamada de um método IPC

Para chamar uma interface remota definida com AIDL, execute as seguintes etapas na classe de chamada:

  1. Inclua o arquivo .aidl no diretório src/ do projeto.
  2. Declare uma instância da interface IBinder, que é gerada com base na AIDL.
  3. Implemente ServiceConnection.
  4. Chame Context.bindService(), transmitindo a implementação de ServiceConnection.
  5. Na implementação de onServiceConnected(), você recebe uma instância de IBinder, chamada service. Chame YourInterfaceName.Stub.asInterface((IBinder)service) para transmitir o parâmetro retornado para o tipo YourInterface.
  6. Chame os métodos que definiu em sua interface. Sempre capture exceções DeadObjectException, que são geradas quando a conexão falha. Além disso, intercepte exceções SecurityException, que são geradas quando os dois processos envolvidos na chamada do método IPC têm definições AIDL conflitantes.
  7. Para se desconectar, chame Context.unbindService() com a instância da sua interface.

Tenha estes pontos em mente ao chamar um serviço IPC:

  • Objetos são referências contadas entre processos.
  • Você pode enviar objetos anônimos como argumentos do método.

Para mais informações sobre a vinculação a um serviço, leia a Visão geral dos serviços vinculados.

Veja um exemplo de código que demonstra a chamada de um serviço criado pela AIDL, retirado 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&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);
            }
        }
    }
}