O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Android Interface Definition Language (AIDL)

A AIDL (Android Interface Definition Language) é similar a outras IDLs com que você talvez já tenha trabalhado. Ela permite definir a interface de programação que o cliente e o serviço usam para se comunicar entre si a partir da comunicação entre processos (IPC). No Android, um processo normalmente não pode acessar a memória de outro. Para conversar, eles precisam decompor os objetos em primitivos que o sistema operacional consegue entender e gerenciar além dessa fronteira. O código para esse gerenciamento é tedioso de escrever, e o Android trata isso com a AIDL.

Observação: usar AIDL é necessário somente se você permitir que clientes de aplicativos diferentes acessem seu serviço para IPC e quiser processar vários threads no seu serviço. Se não for necessário realizar IPC simultâneo entre diversos aplicativos, você deverá criar a interface implementando um Binder. Ou, se você quiser realizar IPC, mas não precisar processar vários threads, implemente a interface usando um Messenger. Independentemente disso, verifique se você entende como funcionam os serviços vinculados antes de implementar uma AIDL.

Antes de começar a projetar a interface AIDL, lembre-se de que chamadas para ela são feitas a partir de funções diretas. Você não deve fazer suposições sobre o thread em que a chamada ocorre. O que acontece é diferente dependendo de a chamada ser de um thread no processo local ou remoto. Mais especificamente:

  • As chamadas feitas do processo local são executadas no mesmo thread que faz a chamada. Se esse for o thread da IU principal, ele continuará sendo executado na interface AIDL. Se for outro thread, ele que executará o código no serviço. Portanto, se somente threads locais acessarem o serviço, você poderá controlar completamente quais deles são executados. Porém, se for esse o caso, não use AIDL e crie a interface implementando um Binder.
  • As chamadas de um processo remoto são despachadas a partir de um conjunto de threads que a plataforma mantém dentro do próprio processo. Você precisa estar preparado para receber chamadas de threads desconhecidos que ocorrem ao mesmo tempo. Em outras palavras, a implementação de uma interface AIDL deve ser completamente segura para threads. As chamadas feitas a partir de um thread no mesmo objeto remoto chegam em ordem no receptor.
  • A palavra-chave oneway modifica o comportamento de chamadas remotas. Quando usada, uma chamada remota não é bloqueada, ela simplesmente envia os dados da transação e os retorna imediatamente. A implementação da interface por fim recebe esta chamada regular do thread 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

É preciso definir a interface AIDL em um arquivo .aidl usando a sintaxe da linguagem de programação Java e salvá-la no código-fonte (o diretório src/) do aplicativo que hospeda o serviço e de qualquer outro aplicativo que se associe ao serviço.

Ao criar cada aplicativo que contém o arquivo .aidl, as ferramentas do Android SDK geram uma interface IBinder com base no arquivo .aidl e a salvam no diretório gen/ do projeto. O serviço deve implementar a interface IBinder como apropriado. Depois disso, os aplicativos clientes poderão se associar ao serviço e chamar métodos do IBinder para executar IPC.

Para criar um serviço associado usando AIDL, siga estes passos:

  1. Criação do arquivo .aidl

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

  2. Implementação da interface

    As ferramentas do Android SDK geram uma interface na linguagem de programação Java baseada em seu arquivo .aidl. Essa interface tem uma classe abstrata interna chamada Stub que estende Binder e implementa métodos de sua interface AIDL. É preciso estender a classe Stub e implementar os métodos.

  3. Exposição da interface aos clientes

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

Atenção: qualquer alteração que você fizer na interface AIDL depois da primeira versão deverá permanecer compatível com versões anteriores para evitar interromper outros aplicativos que usam seu serviço. Ou seja, como seu arquivo .aidl deve ser copiado para outros aplicativos para que acessem a interface do seu serviço, é preciso manter a compatibilidade com a interface original.

1. 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 deve definir uma única interface e precisa somente da declaração da interface e das assinaturas de método.

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

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

    Todos os elementos na List devem ser um dos tipos de dados compatíveis nessa lista ou em uma das outras interfaces geradas com AIDL ou parcelables declarados. Uma List pode ser usada opcionalmente como uma classe “genérica” (por exemplo, List<String>). A classe concreta real que o outro lado recebe é sempre ArrayList, mas o método é gerado para usar a interface List.

  • Map

    Todos os elementos em Map devem ser um dos tipos de dados compatíveis nessa lista ou em uma das outras interfaces geradas com AIDL ou parcelables declarados. Os mapas genéricos, como aqueles na forma Map<String,Integer>, não são aceitos. A classe concreta real que o outro lado recebe é sempre HashMap, mas o método é gerado para usar a interface Map.

É preciso incluir uma declaração import para cada tipo adicional não listado acima, 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 indicando para que lado os dados vão. Seja in, out ou inout (veja o exemplo abaixo).

    Os primitivos são in por padrão e não podem ser diferentes.

    Atenção: você deve limitar a direção ao que é realmente necessário, já que o gerenciamento dos parâmetros é caro.

  • Todos os comentários de código inclusos no arquivo .aidl são inclusos na interface IBinder (exceto comentários antes das declarações de importação e pacote).
  • As constantes string e int podem ser definidas na interface AIDL. Por exemplo: 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ão, você pode atribuir manualmente o código da transação a um método: void method() = 10;.
  • Anote tipos de argumentos ou retornos anuláveis 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 */
internal interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    val pid:Int

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    fun basicTypes(anInt:Int, aLong:Long, aBoolean:Boolean, aFloat:Float,
                 aDouble:Double, aString:String)
}

Basta salvar o arquivo .aidl no diretório src/ do projeto e, ao compilar 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 usar o Android Studio, a ferramenta Gradle gerará a classe binder na próxima vez em que o aplicativo for compilado — compile o 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.

2. Implementação da interface

Ao compilar o aplicativo, as ferramentas do Android SDK geram um arquivo de interface .java com nome igual ao do arquivo .aidl. A interface gerada contém uma subclasse chamada Stub, que é uma implementação resumida da interface pai (por exemplo, YourInterface.Stub) e declara todos os métodos do arquivo .aidl.

Observação: Stub também define alguns métodos de ajuda, principalmente asInterface(), que usa um IBinder (normalmente o que é passado para o método de callback de um cliente onServiceConnected()) e retorna uma instância da interface de esboço. Consulte a seção Chamada de um método IPC para ver mais detalhes sobre como fazer essa transmissão.

Para implementar a interface gerada de .aidl, estenda a interface Binder gerada (por exemplo, YourInterface.Stub) e implemente os métodos herdados do arquivo .aidl.

Veja um exemplo de implementação de uma interface chamada IRemoteService (definida pelo exemplo IRemoteService.aidl acima) 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 (um Binder), o que define a interface RPC (remote procedure cloud) do serviço. No próximo passo, essa instância é exposta aos clientes para que eles possam interagir com o serviço.

Há algumas regras de que você deve ter ciência ao implementar a interface AIDL:

  • Não há garantia de que as chamadas recebidas sejam executadas no thread principal. Por isso, é preciso considerar o uso de vários threads desde o início e criar o serviço adequadamente para que seja seguro.
  • Por padrão, chamadas RPC (remote procedure cloud) são síncronas. Se você souber que o serviço levará mais do que alguns milissegundos para concluir uma solicitação, não o chame no thread principal da atividade, já que isso poderá travar o aplicativo (o Android poderá exibir uma caixa de diálogo “O aplicativo não está respondendo”) — normalmente, é preciso chamá-lo em um thread separado no cliente.
  • Nenhuma exceção criada é enviada de volta ao autor da chamada.

3. Exposição da interface aos clientes

Depois de implementar a interface do serviço, será preciso expô-la aos clientes para que eles possam se associar 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 (como 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 (como uma atividade) chamar bindService() para se conectar a esse serviço, o callback de onServiceConnected() do cliente receberá a instância de binder retornada pelo método onBind() do serviço.

O cliente também deve ter acesso à classe da interface. Por isso, se o cliente e o serviço estiverem em aplicativos separados, o aplicativo do cliente deverá ter uma cópia do arquivo .aidl no diretório src/ (o que gerará a interface android.os.Binder — fornecendo ao cliente o acesso aos métodos AIDL).

Quando o cliente recebe IBinder no callback de onServiceConnected(), ele deve chamar YourServiceInterface.Stub.asInterface(service) para lançar o parâmetro retornado ao tipo YourServiceInterface. Por exemplo:

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 example above 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 example above 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 códigos de exemplo, consulte a classe RemoteService.java em ApiDemos.

Passagem de objetos via IPC

Se você tiver uma classe que gostaria de enviar de um processo a outro usando uma interface IPC, poderá fazê-lo. No entanto, é preciso garantir que o código para a classe esteja disponível para o outro lado do canal IPC e que a classe aceite a interface Parcelable. O suporte à interface Parcelable é importante porque permite que o sistema Android decomponha objetos em primitivos que podem ser gerenciados entre processos.

Para criar uma classe que suporte o protocolo Parcelable, é preciso fazer o seguinte:

  1. Faça com que sua classe implemente a interface Parcelable.
  2. Implemente writeToParcel, que recebe o estado atual do objeto e grava-o em um Parcel.
  3. Adicione um campo estático chamado CREATOR à sua classe, que é um objeto que implementa a interface Parcelable.Creator.
  4. Finalmente, crie um arquivo .aidl que declare a classe parcelable (como mostrado no arquivo Rect.aidl abaixo).

    Se estiver usando um processo de compilação personalizado, não adicione o arquivo .aidl à compilação. Similar a um arquivo de cabeçalho 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, veja um arquivo Rect.aidl para criar uma classe Rect que seja parcelable:

package android.graphics;

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

E este é 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) { Rect() }
        }
    }

    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 é bastante simples. Observe os outros métodos em Parcel para ver outros tipos de valores que podem ser gravados em um Parcel.

Aviso: não se esqueça das implicações de segurança de receber dados de outros processos. Nesse caso, o Rect lê quatro números de Parcel, mas é sua responsabilidade garantir que eles estejam dentro do intervalo aceitável de valores para o que o autor da chamada está tentando fazer. Consulte Segurança e permissões para ver mais informações sobre como manter o aplicativo seguro contra malware.

Métodos com argumentos Bundle contendo Parcelables

Caso sua interface aidl inclua métodos que aceitam Bundle como argumento que devem conter parcelables, verifique se o Classloader foi configurado chamando Bundle.setClassLoader(ClassLoader) antes de tentar ler a partir de Bundle. Caso contrário, você verá ClassNotFoundException mesmo que parcelable esteja corretamente definido no seu aplicativo. Por exemplo,

se você tiver um arquivo .aidl:

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect" */
    void saveRect(in Bundle bundle);
}
Como pode ser visto na implementação abaixo, o ClassLoader está sendo explicitamente definido em 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

Veja os passos que uma classe chamadora deve tomar para chamar uma interface remota definida com AIDL:

  1. Inclua o arquivo .aidl no diretório src/ do projeto.
  2. Declare uma instância da interface IBinder (gerada com base em AIDL).
  3. Implemente ServiceConnection.
  4. Chame Context.bindService(), passando na sua implementação ServiceConnection.
  5. Em sua implementação de onServiceConnected(), você receberá 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. Deve-se sempre capturar exceções DeadObjectException, que são lançadas quando a conexão apresenta erros. Você também deve capturar exceções SecurityException, que são lançadas quando os dois processos envolvidos na chamada do método IPC têm definições AIDL conflitantes.
  7. Para desconectar, chame Context.unbindService() com a instância de sua interface.

Alguns comentários sobre a chamada de 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 associação a um serviço, leia o documento Serviços vinculados.

Veja um exemplo de código que demonstra a chamada de um serviço criado por AIDL, retirado do exemplo de Serviço Remoto no projeto ApiDemos.

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface we will be calling on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface we 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 has been
            // 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 has crashed before we could 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(
                    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 has been
            // 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 allows other applications to 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
                // has crashed.
            }

            // 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 our service, we need to know its
        // PID.  Conveniently our service has a call that will return
        // to us that information.
        try {
            secondaryService?.pid?.also { pid ->
                // Note that, though this API allows us to request to
                // kill any process based on its PID, the kernel will
                // still impose standard restrictions on which PIDs you
                // are actually able to 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 will also be 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.
            // Just for purposes of the 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 will
         * NOT be 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 poke it before doing anything.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.remote_service_binding)

        // Watch for button clicks.
        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 will be 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 poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button clicks.
        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 has been
            // 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 has crashed before we could 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 has been
            // 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 allows other applications to 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
                        // has crashed.
                    }
                }

                // 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 will return
            // to us that information.
            if (secondaryService != null) {
                try {
                    int pid = secondaryService.getPid();
                    // Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to 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 will also be 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.
                    // Just for purposes of the 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 will
         * NOT be 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);
            }
        }
    }
}