Android Interface Definition Language (AIDL).

Język definiowania interfejsów Androida (AIDL) jest podobny do innych IDL: pozwala zdefiniować interfejs programowania, który jest uzgadniany przez klienta i usługę w celu komunikacji między sobą za pomocą komunikacji między procesami (IPC).

W Androidzie jeden proces nie może normalnie korzystać z pamięci innego procesu. Aby się komunikować, muszą rozkładać swoje obiekty na elementy, które system operacyjny może zrozumieć, oraz przekazywać je przez tę granicę. Kod potrzebny do tego przekształcania jest trudny do napisania, dlatego Android wykonuje to za Ciebie za pomocą AIDL.

Uwaga: interfejs AIDL jest potrzebny tylko wtedy, gdy zezwalasz klientom z innych aplikacji na dostęp do usługi w ramach komunikacji międzyprocesowej i chcesz obsługiwać wielowątkowość w swojej usłudze. Jeśli nie musisz wykonywać równoczesnych operacji IPC w różnych aplikacjach, utwórz interfejs, wdrażając Binder. Jeśli chcesz wykonywać IPC, ale nie potrzebujesz obsługi wielowątkowości, zaimplementuj swój interfejs za pomocą Messenger. Przed wdrożeniem AIDL upewnij się, że rozumiesz usługi powiązane.

Zanim zaczniesz projektować interfejs AIDL, pamiętaj, że wywołania interfejsu AIDL są bezpośrednimi wywołaniami funkcji. Nie rób założeń dotyczących wątku, w którym występuje wywołanie. To, co się stanie, zależy od tego, czy wywołanie pochodzi z wątku w procesie lokalnym czy zdalnym:

  • Wywołania wykonywane z procesu lokalnego są wykonywane w tym samym wątku, który je wywołuje. Jeśli jest to Twój główny wątek UI, będzie on nadal wykonywany w interfejsie AIDL. Jeśli jest to inny wątek, to on wykonuje kod w usłudze. Jeśli więc tylko wątki lokalne uzyskują dostęp do usługi, możesz w pełni kontrolować, które wątki się w niej wykonują. W takim przypadku jednak w ogóle nie używaj AIDL. Zamiast tego utwórz interfejs, wdrażając Binder.
  • Wywołania procesu zdalnego są wysyłane z puli wątków, którą platforma obsługuje w ramach własnego procesu. Przygotuj się na połączenia z nieznanych wątków, w tym kilka połączeń naraz. Inaczej mówiąc, implementacja interfejsu AIDL musi być całkowicie bezpieczna w zakresie wątków. Wywołania z jednego wątku na tym samym obiekcie zdalnym docierają w kolejności po stronie odbiorczej.
  • Słowo kluczowe oneway modyfikuje działanie połączeń zdalnych. Gdy jest używane, wywołanie zdalne nie jest blokowane. Wysyła dane transakcji i natychmiast się zwraca. Implementacja interfejsu ostatecznie otrzymuje to jako zwykłe wywołanie z poolu wątków Binder jako zwykłe zdalne wywołanie. Jeśli użyjesz oneway z wywołaniem lokalnym, nie będzie to miało żadnego wpływu i wywołanie będzie nadal synchroniczne.

Definiowanie interfejsu AIDL

Zdefiniuj interfejs AIDL w pliku .aidl przy użyciu składni języka programowania w języku Java, a następnie zapisz go w kodzie źródłowym, w katalogu src/ zarówno aplikacji hostującej usługę, jak i innej aplikacji powiązanej z usługą.

Gdy skompilujesz każdą aplikację, która zawiera plik .aidl, narzędzia Android SDK generują interfejs IBinder na podstawie pliku .aidl i zapisują go w katalogu gen/ projektu. Usługa musi odpowiednio zaimplementować interfejs IBinder. Aplikacje klienckie mogą następnie tworzyć powiązania z usługą i wywołaniami metod z IBinder w celu wykonywania IPC.

Aby utworzyć usługę ograniczoną za pomocą AIDL, wykonaj te czynności opisane w następnych sekcjach:

  1. Tworzenie pliku .aidl

    Plik ten definiuje interfejs programowania za pomocą sygnatur metod.

  2. Implementacja interfejsu

    Na podstawie pliku .aidl narzędzia Android SDK generują interfejs w języku programowania Java. Ten interfejs ma wewnętrzną abstrakcyjną klasę o nazwie Stub, która rozszerza klasę Binder i implementuje metody z interfejsu AIDL. Musisz rozszerzyć klasę Stub i zaimplementować metody.

  3. Wyświetlanie interfejsu klientom

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

Uwaga: wszelkie zmiany wprowadzone w interfejsie AIDL po opublikowaniu pierwszej wersji muszą być zgodne z poprzednimi wersjami, aby nie zakłócać działania innych aplikacji korzystających z Twojej usługi. Plik .aidl musi zostać skopiowany do innych aplikacji, aby mogły uzyskać dostęp do interfejsu Twojej usługi. Musisz więc zachować obsługę oryginalnego interfejsu.

Tworzenie pliku .aidl

AIDL używa prostej składni, która umożliwia zadeklarowanie interfejsu z co najmniej jedną metodą, która może przyjmować parametry i zwracać wartości. Parametry i zwracane wartości mogą być dowolnego typu, nawet w przypadku interfejsów wygenerowanych przez AIDL.

Plik .aidl musisz utworzyć w języku programowania Java. Każdy plik .aidl musi definiować pojedynczy interfejs i wymagać tylko deklaracji interfejsu oraz podpisów metod.

Domyślnie AIDL obsługuje te typy danych:

  • Wszystkie typy podstawowe w języku programowania Java (np. int, long, char, boolean itp.)
  • tablice dowolnego typu, np. int[] lub MyParcelable[]
  • String
  • CharSequence
  • List

    Wszystkie elementy w polu List muszą być jednym z obsługiwanych typów danych na tej liście albo w jednym z innych zadeklarowanych interfejsów lub parcelable wygenerowanych przez AIDL. List może być opcjonalnie używane jako klasa typu z parametrami, np. List<String>. Rzeczywista konkretna klasa, którą otrzymuje druga strona, jest zawsze ArrayList, chociaż metoda jest generowana do użycia interfejsu List.

  • Map

    Wszystkie elementy w Map muszą być jednym z obsługiwanych typów danych na tej liście lub jednym z innych interfejsów wygenerowanych przez AIDL lub obiektów Parcelable zadeklarowanych przez Ciebie. Mapy typów z parametrami, takie jak Map<String,Integer>, nie są obsługiwane. Rzeczywista konkretna klasa, którą otrzymuje druga strona, jest zawsze HashMap, chociaż metoda jest generowana do użycia interfejsu Map. Rozważ użycie Bundle zamiast Map.

Musisz uwzględnić instrukcję import dla każdego dodatkowego typu, który nie został wymieniony wcześniej, nawet jeśli jest zdefiniowany w tym samym pakiecie co interfejs.

Podczas definiowania interfejsu usługi pamiętaj o tych kwestiach:

  • Metody mogą przyjmować zero lub więcej parametrów i zwracać wartość lub wartość void.
  • Wszystkie parametry nieelementarne wymagają tagu kierunkowego wskazującego kierunek przesyłania danych: in, out lub inout (patrz przykład poniżej).

    Pierwiastki, String, IBinder i interfejsy wygenerowane przez AIDL są domyślnie in i nie można ich zmienić.

    Uwaga: ogranicz kierunek do tego, co naprawdę konieczne, ponieważ parametry realizacji są drogie.

  • Wszystkie komentarze do kodu zawarte w pliku .aidl są uwzględniane w wygenerowanym interfejsie IBinder z wyjątkiem komentarzy przed importem i instrukcjami pakietu.
  • W interfejsie AIDL można zdefiniować stałe typu string i int, np. const int VERSION = 1;.
  • Wywołania metod są wysyłane przez transact()kod, który zwykle jest oparty na indeksie metody w interfejsie. Ponieważ utrudnia to wersjonowanie, możesz ręcznie przypisać kod transakcji do metody: void method() = 10;.
  • Argumenty i typy zwracane, które mogą być puste, muszą być opatrzone adnotacją @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 tworzysz aplikację, narzędzia SDK generują plik interfejsu IBinder w katalogu gen/ Twojego projektu. Nazwa wygenerowanego pliku odpowiada nazwie pliku .aidl, ale ma rozszerzenie .java. Na przykład IRemoteService.aidl daje wynik IRemoteService.java.

Jeśli używasz Android Studio, kompilacja przyrostowa wygeneruje klasę bindera niemal natychmiast. Jeśli nie używasz Android Studio, narzędzie Gradle wygeneruje klasę bindera przy następnym kompilowaniu aplikacji. Zbuduj projekt za pomocą gradle assembleDebug lub gradle assembleRelease, gdy tylko skończysz pisać plik .aidl, aby Twój kod mógł połączyć się z wygenerowaną klasą.

Wdrażanie interfejsu

Podczas kompilowania aplikacji narzędzia pakietu Android SDK generują plik interfejsu .java o nazwie .aidl. Wygenerowany interfejs zawiera podklasę o nazwie Stub, która jest abstraktną implementacją interfejsu nadrzędnego, np. YourInterface.Stub, i deklaruje wszystkie metody z pliku .aidl.

Uwaga: Stub definiuje również kilka metod pomocniczych, przede wszystkim asInterface(), która pobiera IBinder, zwykle tę przekazywaną do metody wywołania zwrotnego onServiceConnected() klienta, i zwraca instancję interfejsu namiotowego. Więcej informacji o tym, jak wykonać to przypisanie, znajdziesz w sekcji Wywoływanie metody IPC.

Aby zaimplementować interfejs wygenerowany na podstawie pliku .aidl, rozszerz wygenerowany interfejs Binder (np. YourInterface.Stub) i zaimplementuj metody odziedziczone z pliku .aidl.

Oto przykład implementacji interfejsu o nazwie IRemoteService, zdefiniowanego w poprzednim przykładzie IRemoteService.aidl, przy użyciu anonimowej instancji:

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 wystąpieniem klasy Stub (Binder), która definiuje interfejs IPC usługi. W następnym kroku ta instancja zostanie udostępniona klientom, aby mogli wchodzić w interakcję z usługą.

Podczas implementowania interfejsu AIDL należy pamiętać o kilku zasadach:

  • Nie ma gwarancji, że wywołania przychodzące będą wykonywane w wątku głównym, dlatego już na początku musisz pomyśleć o wielowątkowości i odpowiednio stworzyć usługę, aby była bezpieczna w zakresie wątków.
  • Domyślnie wywołania IPC są synchroniczne. Jeśli wiesz, że wykonanie żądania przez usługę trwa dłużej niż kilka milisekund, nie wywołujej jej z głównego wątku aktywności. Może to spowodować zawieszenie aplikacji, w efekcie Android wyświetli okno „Aplikacja nie odpowiada”. Wywołaj go z osobnego wątku w kliencie.
  • Do wywołującego są wysyłane tylko typy wyjątków wymienione w dokumentacji referencyjnej dotyczącej Parcel.writeException().

Udostępnianie interfejsu klientom

Po zaimplementowaniu interfejsu usługi musisz udostępnić go klientom, aby mogli się z nim połączyć. Aby udostępnić interfejs swojej usługi, rozszerz klasę Service i zaimplementuj metodę onBind(), która zwraca instancję klasy implementującej wygenerowany interfejs Stub, jak opisano w poprzedniej sekcji. Oto przykładowa usługa, która udostępnia 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, np. aktywność, wywołuje funkcję bindService(), aby połączyć się z tą usługą, wywołanie zwrotne onServiceConnected() klienta otrzymuje instancję binder zwracaną przez metodę onBind() usługi.

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

Gdy klient otrzyma parametr IBinder w wywołaniu zwrotnym onServiceConnected(), musi wywołać funkcję YourServiceInterface.Stub.asInterface(service), aby zamienić zwrócony parametr na typ 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 w klasie RemoteService.java w pakiecie ApiDemos.

Przekazywanie obiektów przez IPC

W Androidzie 10 (poziom interfejsu API 29 lub wyższy) możesz definiować obiekty Parcelable bezpośrednio w AIDL. Tutaj są też obsługiwane typy obsługiwane jako argumenty interfejsu AIDL i inne obiekty parcelable. Dzięki temu nie musisz ręcznie pisać kodu porządkowania i klasy niestandardowej. Jednak powoduje też utworzenie pustej struktury. Jeśli chcesz użyć niestandardowych akcesoriów lub innych funkcji, zastosuj zamiast tego 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 kod dotyczący obsługi jest implementowany automatycznie, a obiekt może być używany bezpośrednio bez konieczności dodawania implementacji.

Możesz też wysłać klasę niestandardową z jednego procesu do innego przez interfejs IPC. Upewnij się jednak, że kod Twojej klasy jest dostępny po drugiej stronie kanału IPC, a klasa musi obsługiwać interfejs Parcelable. Obsługa Parcelable jest ważna, ponieważ pozwala systemowi Androida rozkładać obiekty na elementy, które można porządkować w różnych procesach.

Aby utworzyć klasę niestandardową, która obsługuje Parcelable, wykonaj te czynności:

  1. Klasa musi implementować interfejs Parcelable.
  2. Zaimplementuj writeToParcel, który pobiera bieżący stan obiektu i zapisuje go w Parcel.
  3. Dodaj do klasy statyczne pole o nazwie CREATOR, które jest obiektem implementującym interfejs Parcelable.Creator.
  4. Na koniec utwórz plik .aidl, który deklaruje klasę parcelable, jak pokazano w następującym pliku Rect.aidl.

    Jeśli używasz niestandardowego procesu kompilacji, nie dodawaj do kompilacji pliku .aidl. Podobnie jak plik nagłówka w języku C, plik .aidl nie jest skompilowany.

AIDL używa tych metod i pol w generowanym kodzie do parsowania i rozparsowania Twoich obiektów.

Oto na przykład plik Rect.aidl, który służy do utworzenia klasy Rect, która jest sparcjalna:

package android.graphics;

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

Oto przykład implementacji protokołu Parcelable przez klasę Rect.

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. Zapoznaj się z innymi metodami w obiekcie Parcel, aby poznać inne rodzaje wartości, które możesz zapisać w obiekcie Parcel.

Ostrzeżenie: pamiętaj o wpływie na bezpieczeństwo odbierania danych z innych procesów. W tym przypadku Rect odczytuje 4 liczby z Parcel, ale to od Ciebie zależy, czy będą one mieścić się w akceptowalnym zakresie wartości, niezależnie od tego, co chce zrobić dzwoniący. Więcej informacji o tym, jak chronić aplikację 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 przyjmuje obiekt Bundle, który ma zawierać obiekty Parcelable, przed próbą odczytu z obiektu Bundle pamiętaj o ustawieniu ładowarki klas Bundle przez wywołanie metody Bundle.setClassLoader(ClassLoader). W przeciwnym razie wystąpi błąd ClassNotFoundException, mimo że klasa Parcelable jest prawidłowo zdefiniowana 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, element ClassLoader jest wyraźnie ustawiony w elemencie Bundle przed odczytaniem 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ć zdalny interfejs zdefiniowany za pomocą AIDL, w klasie wywołującej wykonaj te czynności:

  1. Dołącz plik .aidl do katalogu projektu src/.
  2. Zadeklaruj instancję interfejsu IBinder, który jest generowany na podstawie AIDL.
  3. Wdróż ServiceConnection.
  4. Wywołaj Context.bindService(), przekazując swoją implementację ServiceConnection.
  5. W implementacji onServiceConnected() otrzymujesz instancję IBinder o nazwie service. Zadzwoń do funkcji YourInterfaceName.Stub.asInterface((IBinder)service), aby zamienić zwrócony parametr na typ YourInterface.
  6. Wywołuj metody zdefiniowane w interfejsie. Zawsze przechwytuj wyjątki DeadObjectException, które są zgłaszane, gdy połączenie zostanie przerwane. Ponadto przechwyć wyjątki SecurityException, które są zgłaszane, gdy 2 procesy biorące udział w wywołaniu metody IPC mają sprzeczne definicje AIDL.
  7. Aby się rozłączyć, wywołaj funkcję Context.unbindService() z instancją interfejsu.

Podczas korzystania z usługi IPC należy pamiętać o tych kwestiach:

  • Obiekty są zliczane w ramach procesów.
  • Jako argumenty metody możesz przesyłać anonimowe obiekty.

Więcej informacji o wiązaniu z usługą znajdziesz w artykule Omówienie usług powiązanych.

Oto przykładowy kod, który demonstruje wywołanie usługi utworzonej za pomocą AIDL. Przykład pochodzi z 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);
            }
        }
    }
}