Android Interface Definition Language (AIDL).

Język definiowania interfejsu Androida (AIDL) jest podobny do innych Identyfikatory IDL: pozwala zdefiniować interfejs programowania, uzgodnione przez klienta i usługę w celu komunikowania się ze sobą za pomocą komunikacji międzyprocesowej (IPC).

W Androidzie jeden proces nie może normalnie uzyskać dostępu do pamięci innego procesu. Aby mówić, trzeba podzielić obiekty na elementy podstawowe, system operacyjny jest w stanie zrozumieć i zeskanować obiekty za Ciebie. Kod do i udawanie, że pisanie jest uciążliwe, dlatego Android robi to za Ciebie dzięki AIDL.

Uwaga: zastosowanie AIDL jest konieczne tylko wtedy, gdy klienci z różne aplikacje uzyskują dostęp do Twojej usługi dla IPC i chcesz obsługiwać wielowątkowość posprzedażna. Jeśli nie trzeba przeprowadzać równoległego IPC na różnych aplikacji, utwórz interfejs, implementując Binder Jeśli chcesz przeprowadzać IPC, ale nie musisz obsługiwać wielowątkowości, zaimplementuj interfejs za pomocą Messenger. Niezależnie od tego upewnij się, że znasz powiązane usługi przed wdrożenia AIDL.

Przed rozpoczęciem projektowania interfejsu AIDL należy pamiętać, że wywołania interfejsu AIDL są bezpośrednich wywołań funkcji. Nie należy przyjmować żadnych założeń na temat wątku, w którym wywołanie ma miejsce. Sposób działania różni się w zależności od tego, czy wywołanie pochodzi z wątku proces lokalny lub zdalny:

  • Wywołania wykonywane z procesu lokalnego są wykonywane w tym samym wątku, w którym następują to wywołanie. Jeśli to jest główny wątek UI, który jest nadal wykonywany w interfejsie AIDL. Jeśli tak który wykonuje kod w usłudze. Dlatego, jeśli tylko lokalne gdy wątki uzyskują dostęp do usługi, masz pełną kontrolę nad tym, które wątki są w niej wykonywane. Ale w takim przypadku nie używaj w ogóle AIDL. zamiast tego utwórz przez zaimplementowanie Binder
  • Wywołania z procesu zdalnego są wysyłane z puli wątków, którą utrzymuje platforma do własnych potrzeb. Przygotuj się na wiele rozmów przychodzących z nieznanych wątków w tym samym czasie. Innymi słowy, implementacja interfejsu AIDL musi być są całkowicie bezpieczne w wątkach. Wywołania wykonywane z jednego wątku w przypadku tego samego obiektu zdalnego dotrze w zamówieniu po stronie odbiorcy.
  • Słowo kluczowe oneway zmienia działanie wywołań zdalnych. W przypadku jego użycia połączenie zdalne nie blokować. Wysyła dane transakcji i natychmiast zwraca dane. Implementacja interfejsu otrzymuje w końcu zwykłe wywołanie z puli wątków Binder jako zwykłe wywołanie zdalne. Jeśli w połączeniu z lokalnymi połączeniami używany jest numer oneway, Nie ma to żadnego wpływu, a wywołanie jest nadal synchroniczne.

Definiowanie interfejsu AIDL

Zdefiniuj interfejs AIDL w pliku .aidl za pomocą języka Java składni języka programowania, a potem zapisz ją w kodzie źródłowym, w katalogu src/ aplikację hostującą usługę i wszelkie inne aplikacje powiązane z usługą.

Przy tworzeniu każdej aplikacji, która zawiera plik .aidl, narzędzia Android SDK wygeneruj interfejs IBinder na podstawie pliku .aidl i zapisz go w katalogu gen/ projektu. Usługa musi implementować interfejs IBinder interfejsu użytkownika. Aplikacje klienckie mogą wówczas powiązać usługę i metody wywoływania z IBinder do przeprowadzenia IPC.

Aby utworzyć usługę ograniczoną za pomocą AIDL, wykonaj opisane poniżej czynności w następujących sekcjach:

  1. Utwórz plik .aidl

    Definiuje interfejs programowania za pomocą podpisów metod.

  2. Implementacja interfejsu

    Narzędzia Android SDK generują interfejs w języku programowania Java na podstawie .aidl. Ten interfejs ma wewnętrzną klasę abstrakcyjną o nazwie Stub, która rozciąga się Binder i implementuje metody z interfejsu AIDL. Musisz przedłużyć Stub i zaimplementuj metody.

  3. Prezentowanie interfejsu klientom

    Zaimplementuj Service i zastąp onBind(), aby zwrócić implementację Stub zajęcia.

Uwaga: wszelkie zmiany wprowadzone w interfejsie AIDL po pierwsza wersja musi być zgodna wstecznie, aby uniknąć uszkodzenia innych aplikacji którzy korzystają z Twojej usługi. Wynika to z faktu, że plik .aidl musi zostać skopiowany do innych aplikacji aby mieli dostęp do interfejsu usługi, musisz utrzymać obsługę za pomocą prostego interfejsu online.

Tworzenie pliku .aidl

AIDL używa prostej składni, która umożliwia zadeklarowanie interfejsu za pomocą jednej lub kilku metod, które biorą parametry i zwracają wartości. Parametry i zwracane wartości mogą być dowolnego typu, nawet Interfejsy wygenerowane przez AIDL.

Plik .aidl musisz utworzyć w języku programowania Java. Co .aidl musi definiować jeden interfejs i wymaga jedynie deklaracji interfejsu i metody podpisy.

Domyślnie AIDL obsługuje te typy danych:

  • Wszystkie typy podstawowe w języku programowania Java (np. int, long, char, boolean itd.)
  • Tablice typów podstawowych, np. int[]
  • String
  • CharSequence
  • List

    Wszystkie elementy w polu List muszą być jednym z typów danych obsługiwanych w tym lub jeden z innych zadeklarowanych przez Ciebie interfejsów bądź usług wygenerowanych przez AIDL. O Parametr List może być opcjonalnie używany jako klasa typu z parametrami, taka jak List<String> Rzeczywista klasa betonowa, którą otrzymuje druga strona, to zawsze ArrayList, chociaż jest generowana w interfejsie List.

  • Map

    Wszystkie elementy w polu Map muszą być jednym z typów danych obsługiwanych w tym lub jeden z innych zadeklarowanych przez Ciebie interfejsów bądź usług wygenerowanych przez AIDL. mapowania typów z parametrami; na przykład w formie Map<String,Integer>, nie są obsługiwane. Rzeczywista klasa betonu, którą druga strona otrzymuje zawsze HashMap, chociaż metoda jest generowana pod kątem użycia interfejsu Map. Rozważ użycie Bundle jako alternatywę dla Map.

Musisz dołączyć instrukcję import dla każdego dodatkowego typu, który nie został wymieniony wcześniej, nawet jeśli są zdefiniowane w tym samym pakiecie co interfejs.

Podczas definiowania interfejsu usługi pamiętaj, że:

  • Metody mogą przyjmować zero lub więcej parametrów i zwracać wartość lub wartość void.
  • Wszystkie parametry inne niż podstawowe wymagają tagu kierunkowego wskazującego, w którą stronę przekazywane są dane: in, out lub inout (zobacz przykład poniżej).

    Elementy podstawowe, String, IBinder i wygenerowane przez AIDL interfejsy mają domyślnie wartość in i nie można ich zmienić w inny sposób.

    Uwaga: ogranicz wskazówki do treści, które są rzeczywiście ponieważ parametry wyznaczania trasy są drogie.

  • Wszystkie komentarze do kodu zawarte w pliku .aidl są uwzględnione w parametrze wygenerowano IBinder z wyjątkiem komentarzy przed importem i pakietem wyciągów.
  • Ciągi tekstowe i stałe int mogą być zdefiniowane w interfejsie AIDL, np. w const int VERSION = 1;.
  • Wywołania metod są wysyłane przez transact() , który zwykle opiera się na indeksie metod w interfejsie. Ponieważ utrudniają obsługę wersji, Użytkownik może ręcznie przypisać kod transakcji do metody: void method() = 10;.
  • Argumenty do wartości null i zwracane typy muszą być opatrzone adnotacjami za pomocą funkcji @nullable.

Oto przykładowy plik .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);
}

Zapisz plik .aidl w katalogu src/ projektu. Gdy jeśli utworzysz aplikację, narzędzia SDK wygenerują plik interfejsu IBinder w katalogu gen/ projektu. Nazwa wygenerowanego pliku jest taka sama jak nazwa pliku .aidl, ale z rozszerzeniem .java. Na przykład IRemoteService.aidl daje IRemoteService.java.

Jeśli używasz Android Studio, kompilacja przyrostowa generuje klasę sposobu niemal natychmiast. Jeśli nie używasz Android Studio, narzędzie Gradle wygeneruje klasę segregatora następnym razem, utworzyć aplikację. Skompiluj projekt w usłudze gradle assembleDebug lub gradle assembleRelease, gdy tylko skończysz zapisywać plik .aidl, aby kod mógł połączyć się z wygenerowaną klasą.

Implementacja interfejsu

Gdy tworzysz aplikację, narzędzia Android SDK generują plik interfejsu .java o nazwie odpowiadającej nazwie pliku .aidl. Wygenerowany interfejs zawiera podklasę o nazwie Stub jest abstrakcyjną implementacją interfejsu nadrzędnego, np. YourInterface.Stub, i deklaruje wszystkie metody z pliku .aidl.

Uwaga: Stub też określa kilka metod pomocniczych, przede wszystkim asInterface(), która wymaga IBinder, zwykle metody przekazywanej do metody wywołania zwrotnego onServiceConnected() klienta, oraz zwraca instancję interfejsu stub. Więcej informacji o realizacji obsady znajdziesz w sekcji Wywołanie IPC .

Aby wdrożyć interfejs wygenerowany na podstawie .aidl, przedłuż wygenerowany Binder takich jak YourInterface.Stub, i zaimplementuj metody odziedziczone z pliku .aidl.

Oto przykładowa implementacja interfejsu o nazwie IRemoteService zdefiniowany przez Przykład IRemoteService.aidl z użyciem instancji anonimowej:

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

Teraz binder jest instancją klasy Stub (Binder), który definiuje interfejs IPC usługi. W następnym kroku ta instancja będzie dostępna dla klientów, aby mogli korzystać z usługi.

Implementując interfejs AIDL, pamiętaj o kilku zasadach:

  • Nie ma gwarancji, że połączenia przychodzące będą wykonywane w wątku głównym, więc musisz się zastanowić, o wielowątkowości i prawidłowym skompilowaniu usługi w taki sposób, aby była bezpieczna w wątkach.
  • Domyślnie wywołania IPC są synchroniczne. Jeśli wiesz, że usługa wymaga więcej niż kilku milisekund na wykonanie żądania, nie używaj go w głównym wątku aktywności. Może to spowodować zawieszenie aplikacji i pojawia się komunikat „Aplikacja nie odpowiada” . Wywołaj je w osobnym wątku w kliencie.
  • Tylko typy wyjątków wymienione w dokumentacji referencyjnej dla Parcel.writeException() zostaną odesłane do rozmówcy.

Udostępnienie interfejsu klientom

Po zaimplementowaniu interfejsu usługi musisz udostępnić go klientów, aby mogli się z nimi wiązać. Udostępnienie interfejsu dla swojej usługi, rozszerz Service i zaimplementuj onBind(), by zwrócić instancję klasy, która w wygenerowanym obiekcie Stub, jak omówiliśmy w poprzedniej sekcji. Oto przykład: Udostępnia ona klientom przykładowy interfejs IRemoteService.

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

Teraz gdy klient, na przykład działanie, wywoła bindService(), aby połączyć się z tą usługą, wywołanie zwrotne onServiceConnected() klienta otrzyma binder instancja zwrócona przez usługę onBind() .

Klient musi też mieć dostęp do klasy interfejsu. Jeśli więc klient i usługa znajdują się osobnych aplikacji, to aplikacja klienta musi mieć kopię pliku .aidl w katalogu src/, który generuje android.os.Binder który zapewnia klientowi dostęp do metod AIDL.

Gdy klient otrzyma IBinder w wywołaniu zwrotnym onServiceConnected(), musi wywołać YourServiceInterface.Stub.asInterface(service), aby przesłać zwrócone do typu 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;
    }
};

Więcej przykładowego kodu znajdziesz tutaj RemoteService.java zajęcia w . ApiDemos

Przekazywanie obiektów przez IPC

W Androidzie 10 (poziom API 29 lub wyższy) możesz zdefiniować Parcelable obiektów bezpośrednio w AIDL. Typy obsługiwane jako argumenty interfejsu AIDL i inne obiekty parcelable są również obsługiwane tutaj. Pozwala to uniknąć dodatkowej pracy związanej z ręcznym pisaniem kodu zarządzania oraz zajęcia. W tym przypadku również powstaje słaba struktura. Jeśli niestandardowe akcesoria lub inne funkcje są zaimplementuj 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;
}

Poprzedni przykładowy kod automatycznie generuje klasę Java z polami liczb całkowitych left, top, right i bottom. Cały odpowiedni kod zarządzania to jest zaimplementowany automatycznie. Obiekt można używać bezpośrednio, bez konieczności dodawania implementacji.

Możesz też wysłać klasę niestandardową z jednego procesu do innego przez interfejs IPC. Pamiętaj jednak: W tym celu należy sprawdzić, czy kod zajęć jest dostępny po drugiej stronie kanału IPC. klasa musi obsługiwać interfejs Parcelable. Wsparcie Domena Parcelable jest ważna bo umożliwia systemowi Android rozkładanie obiektów na obiekty podstawowe, które można uporządkować między różnymi procesami.

Aby utworzyć klasę niestandardową obsługującą interfejs Parcelable, wykonaj :

  1. Przygotuj klasę do wdrożenia interfejsu Parcelable.
  2. Wdróż writeToParcel, który wymaga obecny stan obiektu i zapisuje go w Parcel.
  3. Dodaj do klasy statyczne pole o nazwie CREATOR, które jest obiektem implementującym w interfejsie Parcelable.Creator.
  4. Na koniec utwórz plik .aidl deklarujący klasę dosłowną – w przykładzie poniżej Rect.aidl.

    Jeśli korzystasz z niestandardowego procesu kompilacji, nie dodawaj pliku .aidl do pliku tworzyć. Podobnie jak plik nagłówka w języku C, ten plik .aidl nie jest skompilowany.

AIDL używa tych metod i pól w generowanym kodzie do segregowania i usuwania znaczników Twoje obiekty.

Oto przykładowy plik Rect.aidl służący do utworzenia klasy Rect, która parcelable:

package android.graphics;

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

A oto przykład tego, jak klasa Rect implementuje Protokół 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;
    }
}

Tworzenie w klasie Rect jest proste. Przyjrzyj się innej w Parcel, aby zobaczyć inne rodzaje wartości, które możesz zapisywać do: Parcel.

Ostrzeżenie: pamiętaj o potencjalnych problemach z odbieraniem z innych procesów. W tym przypadku Rect odczytuje 4 cyfry z Parcel, ale Ty musisz upewnić się, że mieszczą się one w akceptowalnym zakresie niezależnie od tego, co próbuje zrobić wywołujący. Więcej informacji o zabezpieczaniu aplikacji przed złośliwym oprogramowaniem znajdziesz w artykule Wskazówki dotyczące bezpieczeństwa.

Metody z argumentami pakietu zawierającymi obiekty Parcelable

Jeśli metoda akceptuje obiekt Bundle, który powinien zawierać Parcelables, pamiętaj, aby ustawić moduł uruchamiający klasy Bundle Wywołuję Bundle.setClassLoader(ClassLoader) przed próbą odczytania z Bundle. W przeciwnym razie napotkasz zdarzenie ClassNotFoundException, mimo że parcelable jest poprawnie określony w aplikacji.

Przyjrzyjmy się na przykład temu przykładowemu plikowi .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);
}
Jak widać w poniższej implementacji, ClassLoader to jawnie ustawiony w Bundle przed odczytem 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.
    }
};

Wywoływanie metody IPC

Aby wywołać interfejs zdalny zdefiniowany za pomocą AIDL, wykonaj następujące czynności w Twoja klasa dzwonienia:

  1. Umieść plik .aidl w katalogu src/ projektu.
  2. Zadeklaruj wystąpienie interfejsu IBinder, które jest generowane na podstawie AIDL.
  3. Wdróż ServiceConnection.
  4. Zadzwoń do firmy Context.bindService(), które przekazujesz w implementacji ServiceConnection.
  5. W ramach implementacji interfejsu onServiceConnected() otrzymasz IBinder instancji o nazwie service. Zadzwoń do nas YourInterfaceName.Stub.asInterface((IBinder)service) do rzutować zwracany parametr na typ YourInterface.
  6. Wywołaj metody zdefiniowane w interfejsie. Zawsze pułapka DeadObjectException wyjątków, które są zgłaszane, gdy połączenie może zostać przerwane. Dodatkowo wyjątki pułapki SecurityException, które są zgłaszane, gdy 2 procesy uczestniczące w wywołaniu metody IPC mają sprzeczne definicje AIDL.
  7. Aby się rozłączyć, wywołaj Context.unbindService() za pomocą instancji interfejsu.

Gdy wywołujesz usługę IPC, pamiętaj o tych kwestiach:

  • Obiekty są wartościami referencyjnymi zliczonymi w procesach.
  • Możesz wysyłać anonimowe obiekty jako argumentów metody.

Więcej informacji o tworzeniu powiązań z usługą znajdziesz w artykule Omówienie usług granicznych.

Oto przykładowy kod pokazujący wywoływanie usługi utworzonej przez AIDL, pobrane z przykładu usługi zdalnej w projekcie 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);
            }
        }
    }
}