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).

Na Androidzie jeden proces nie może normalnie uzyskiwać dostępu do pamięci innego procesu. Aby mówić, trzeba podzielić obiekty na elementy podstawowe, tak aby system operacyjny mógł je zrozumieć i uporządkować w ich granicach. 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 wykonać IPC, ale nie musisz obsługiwać wielowątkowości, zaimplementuj 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 z procesu lokalnego są wykonywane w tym samym wątku, w którym jest wywołanie. Jeśli jest to główny wątek interfejsu, nadal jest on uruchamiany 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 nie używaj AIDL, tylko utwórz interfejs, implementując Binder.
  • Wywołania z procesu zdalnego są wysyłane z elementu puli wątków, który platforma utrzymuje w ramach Twojego 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 wykonywane z jednego wątku w tym samym obiekcie zdalnym są następowane w określonej kolejności po stronie odbiorcy.
  • 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 oneway jest używane w przypadku wywołania lokalnego, nie ma to wpływu na wywołanie, które nadal jest synchroniczne.

Definiowanie interfejsu AIDL

Zdefiniuj interfejs AIDL w pliku .aidl, używając składni języka programowania Java, a potem zapisz go w źródle w katalogu src/ zarówno w aplikacji hostującej usługę, jak i w innej aplikacji, która łączy się z tą usługą.

Podczas kompilowania każdej aplikacji zawierającej 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. Utwórz plik .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ą klasę abstrakcyjną o nazwie Stub, która rozszerza zakres Binder i implementuje metody z interfejsu AIDL. Musisz rozszerzyć klasę Stub i zaimplementować metody.

  3. Prezentowanie 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 wartości zwracane mogą mieć dowolny typ, nawet inne interfejsy wygenerowane 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 proste w języku programowania Java (takie jak 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. Struktury List można opcjonalnie używać jako parametryzowanej klasy typu, np. List<String>. Rzeczywista klasa betonowa odbierana przez drugą stronę zawsze to ArrayList, choć metoda jest generowana przy użyciu 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ą mieć 0 lub więcej parametrów i mogą zwracać wartość lub 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 kodu zawarte w pliku .aidl są uwzględniane w wygenerowanym interfejsie IBinder, z wyjątkiem komentarzy po instrukcjach importu i pakowania.
  • 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. Utrudnia to obsługę wersji, dlatego 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. Podczas kompilowania aplikacji narzędzia pakietu SDK generują 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 wynik 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ę binder następnym razem, gdy będziesz kompilować aplikację. 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 też kilka metod pomocniczych, w tym asInterface(), która przyjmuje parametr IBinder (zwykle przekazywany do metody wywołania zwrotnego klienta onServiceConnected()) i zwraca wystąpienie interfejsu stub. Więcej informacji o tym, jak wykonać to przypisanie, znajdziesz w sekcji Wywoływanie metody IPC.

Aby zaimplementować interfejs wygenerowany za pomocą .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 instancją klasy Stub (Binder), która definiuje interfejs IPC usługi. W następnym kroku instancja jest udostępniana klientom, aby mogli korzystać z usługi.

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 musisz od razu zastanowić się nad wielowątkowością i odpowiednio zbudować usługę, która zapewnia bezpieczeństwo w wątkach.
  • Domyślnie wywołania IPC są synchroniczne. Jeśli wiesz, że usługa potrzebuje więcej niż kilku milisekund na wykonanie żądania, nie wywołuj 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ład usługi, 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 osobnych aplikacjach, aplikacja klienta musi mieć w katalogu src/ kopię pliku .aidl, który generuje interfejs android.os.Binder zapewniający 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. Obsługiwane są też typy, które są obsługiwane jako argumenty interfejsu AIDL, oraz inne obiekty Parcelable. Dzięki temu nie musisz ręcznie pisać kodu porządkowania i klasy niestandardowej. W tym przypadku również powstaje słaba struktura. 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ład kodu automatycznie generuje klasę Java z polami całkowitymi left, top, rightbottom. Cały kod dotyczący obsługi jest implementowany automatycznie, a obiekt może być używany bezpośrednio bez konieczności dodawania implementacji.

Klasę niestandardową możesz też wysyłać z jednego procesu do drugiego za pomocą interfejsu IPC. Upewnij się jednak, że kod Twojej klasy jest dostępny po drugiej stronie kanału IPC, a klasa obsługuje 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 funkcję writeToParcel, która 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;
    }
}

Marshalling w klasie Rect jest prosty. 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 konsekwencjach bezpieczeństwa związanych z odbieraniem 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 akceptuje obiekt Bundle, który powinien zawierać obiekty parcelable, upewnij się, że masz ustawiony moduł ładowania klasy Bundle, wywołując metodę Bundle.setClassLoader(ClassLoader), zanim spróbujesz odczytać dane z interfejsu Bundle. W przeciwnym razie wystąpi błąd ClassNotFoundException, mimo że klasa Parcelable jest prawidłowo zdefiniowana w aplikacji.

Rozważ na przykład ten przykładowy plik .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, zmienna ClassLoader jest jawnie ustawiana w funkcji Bundle przed odczytaniem wartości 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. Zadzwoń do Context.bindService(), przekazując 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 pokazujący wywoływanie usługi utworzonej przez AIDL, pobranego z przykładowej 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);
            }
        }
    }
}