Android Interface Definition Language (AIDL) jest podobny do innych IDL: pozwala zdefiniować interfejs programowania, który będzie akceptowany zarówno przez klienta, jak i usługę, na potrzeby komunikacji między procesami (IPC).
Na Androidzie jeden proces nie ma normalnie dostępu do pamięci innego procesu. Mówiąc, trzeba rozłożyć obiekty na podstawowe, które system operacyjny może zrozumieć, i przesunąć je w obrębie tej granicy. Napisanie takiego kodu jest żmudne, dlatego Android obsługuje je za pomocą AIDL.
Uwaga: AIDL jest konieczne tylko wtedy, gdy zezwalasz klientom z różnych aplikacji na dostęp do usługi dla IPC i chcesz obsługiwać wielowątkowość w swojej usłudze. Jeśli nie musisz przeprowadzać równoczesnych testów IPC w różnych aplikacjach, utwórz interfejs, zaimplementując Binder
.
Jeśli chcesz wykonać kod IPC, ale nie musisz obsługiwać wielowątkowości, zaimplementuj swój interfejs za pomocą Messenger
.
Niezależnie od tego upewnij się, że znasz powiązane usługi, zanim wdrożysz AIDL.
Zanim zaczniesz projektować interfejs AIDL, pamiętaj, że wywołania interfejsu AIDL są bezpośrednimi wywołaniami funkcji. Nie należy wyciągać wniosków na temat wątku, w którym następuje wywołanie. To, co się stanie, zależy od tego, czy wywołanie pochodzi z wątku w procesie lokalnym, czy z procesu zdalnego:
- Wywołania z procesu lokalnego są wykonywane w tym samym wątku, z którego pochodzą. 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, będzie to ten, który wykonuje kod w usłudze. Dzięki temu, jeśli dostęp do usługi mają tylko wątki lokalne, możesz w pełni kontrolować, które z nich są w niej wykonywane. W takim przypadku w ogóle nie używaj AIDL. Zamiast tego utwórz interfejs, zaimplementując
Binder
. - Wywołania z 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 przychodzące z nieznanych wątków, które mogą odbywać się w tym samym czasie. Inaczej mówiąc, implementacja interfejsu AIDL musi być całkowicie bezpieczna w wątku. Wywołania wykonywane z jednego wątku w tym samym obiekcie zdalnym są w kolejności po stronie odbiorcy.
- Słowo kluczowe
oneway
zmienia działanie wywołań zdalnych. Wywołanie zdalne nie jest wtedy blokowane. Przesyła dane transakcji, a następnie natychmiast je zwraca. Implementacja interfejsu otrzymuje w końcu jako zwykłe wywołanie z puli wątkówBinder
jako zwykłe wywołanie zdalne. Jeśli używany jest interfejsoneway
w połączeniu z wywołaniem lokalnym, nie ma to wpływu na działanie usługi, a wywołanie jest nadal synchroniczne.
Definiowanie interfejsu AIDL
Zdefiniuj interfejs AIDL w pliku .aidl
przy użyciu składni języka programowania Java, a następnie zapisz go w kodzie źródłowym, w katalogu src/
, zarówno aplikacji hostującej usługę, jak i dowolnej innej aplikacji powiązanej z usługą.
Gdy kompilujesz 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 implementować interfejs IBinder
. Aplikacje klienckie mogą następnie powiązać się z usługą i wywołać metody z interfejsu IBinder
, aby wykonać IPC.
Aby utworzyć usługę ograniczoną za pomocą AIDL, wykonaj te czynności opisane w kolejnych sekcjach:
- Tworzenie pliku
.aidl
Ten plik definiuje interfejs programowania z podpisami metod.
- Implementowanie interfejsu
Narzędzia pakietu Android SDK generują interfejs w języku programowania Java na podstawie pliku
.aidl
. Ten interfejs zawiera wewnętrzną klasę abstrakcyjną o nazwieStub
, która stanowi rozszerzenieBinder
i implementuje metody z Twojego interfejsu AIDL. Musisz rozszerzyć klasęStub
i wdrożyć metody. - Udostępnianie interfejsu klientom
Zaimplementuj obiekt
Service
i zastąponBind()
, aby zwrócić implementację klasyStub
.
Uwaga: wszystkie zmiany wprowadzone w interfejsie AIDL po pierwszej wersji muszą pozostać zgodne wstecznie, aby uniknąć uszkodzenia innych aplikacji korzystających z Twojej usługi. Oznacza to, że plik .aidl
musi zostać skopiowany do innych aplikacji, aby mogły uzyskać dostęp do interfejsu usługi, dlatego musisz utrzymywać obsługę oryginalnego interfejsu.
Tworzenie pliku .aidl
AIDL wykorzystuje prostą składnię, która umożliwia zadeklarowanie interfejsu za pomocą co najmniej 1 metody, która może pobierać parametry i zwracać wartości. Parametry i zwracane wartości mogą być dowolnego typu, nawet w przypadku innych interfejsów generowanych przez AI.
Plik .aidl
musisz utworzyć w języku programowania Java. Każdy plik .aidl
musi definiować jeden interfejs i wymaga jedynie deklaracji interfejsu i podpisów metod.
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
List
muszą należeć do jednego z obsługiwanych typów danych na tej liście lub jednego z innych zadeklarowanych przez Ciebie interfejsów lub pakietów wygenerowanych przez AIDL. TypuList
można opcjonalnie używać jako klasy typu z parametrami, np.List<String>
. Rzeczywista klasa konkretna, którą otrzymuje druga strona, to zawszeArrayList
, chociaż metoda jest generowana, aby korzystać z interfejsuList
.Map
Wszystkie elementy
Map
muszą należeć do jednego z obsługiwanych typów danych na tej liście lub jednego z innych zadeklarowanych przez Ciebie interfejsów lub pakietów wygenerowanych przez AIDL. Mapy z parametrami, takie jak te w formacieMap<String,Integer>
, nie są obsługiwane. Rzeczywista klasa betonowa odbierana przez drugą stronę to zawszeHashMap
, choć metoda jest generowana na potrzeby interfejsuMap
. Rozważ użycie elementuBundle
jako alternatywy dlaMap
.
Musisz umieścić instrukcję import
dla każdego dodatkowego typu, którego nie ma na liście, nawet jeśli są one zdefiniowane w tym samym pakiecie co Twój interfejs.
Podczas definiowania interfejsu usługi pamiętaj o tych kwestiach:
- Metody mogą przyjmować zero lub więcej parametrów i zwracać wartości lub unieważnienia.
- Wszystkie parametry inne niż podstawowe wymagają tagu kierunkowego wskazującego kierunek przesyłania danych:
in
,out
lubinout
(zobacz przykład poniżej).Interfejsy podstawowe,
String
,IBinder
i interfejsy wygenerowane przez AIDL mają domyślnie wartośćin
i nie można ich zmienić.Uwaga: ogranicz kierunek do tego, co jest naprawdę potrzebne, ponieważ parametry marshallingu są drogie.
- Wszystkie komentarze do kodu zawarte w pliku
.aidl
są uwzględniane w wygenerowanym interfejsieIBinder
z wyjątkiem komentarzy poprzedzających instrukcję importu i pakietu. - Ciągi i stałe int można zdefiniować w interfejsie AIDL, np.
const int VERSION = 1;
. - Wywołania metod są wysyłane przez kod
transact()
, który zwykle opiera się na indeksie metod w interfejsie. Utrudnia to obsługę wersji, dlatego możesz ręcznie przypisać kod transakcji do metody:void method() = 10;
. - Argumenty null i zwracane typy muszą być oznaczone 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 skompilujesz aplikację, narzędzia pakietu SDK wygenerują plik interfejsu IBinder
w katalogu gen/
projektu. Nazwa wygenerowanego pliku jest taka sama jak nazwa pliku .aidl
, ale ma rozszerzenie .java
. Na przykład wartość IRemoteService.aidl
może skutkować IRemoteService.java
.
Jeśli używasz Android Studio, kompilacja przyrostowa generuje klasę powiązania niemal natychmiast.
Jeśli nie używasz Android Studio, narzędzie Gradle wygeneruje klasę bindowania przy następnym tworzeniu aplikacji. Utwórz projekt w gradle assembleDebug
lub gradle assembleRelease
, gdy tylko skończysz pisanie pliku .aidl
, aby Twój kod mógł utworzyć link do wygenerowanej klasy.
Implementacja interfejsu
Gdy tworzysz aplikację, narzędzia pakietu Android SDK generują plik interfejsu .java
o nazwie odpowiadającej plikowi .aidl
. Wygenerowany interfejs zawiera podklasę o nazwie Stub
, która jest abstrakcyjną implementacją interfejsu nadrzędnego, np. YourInterface.Stub
, i deklaruje wszystkie metody z pliku .aidl
.
Uwaga: Stub
definiuje też kilka metod pomocniczych, a zwłaszcza asInterface()
, które wymagają metody IBinder
(zazwyczaj przekazywanej do metody wywołania zwrotnego onServiceConnected()
klienta) i zwraca instancję interfejsu skróconego. Więcej informacji na temat rzutowania znajdziesz w sekcji Wywoływanie metody IPC.
Aby zaimplementować interfejs wygenerowany z .aidl
, rozszerz wygenerowany interfejs Binder
, np. YourInterface.Stub
, i zaimplementuj metody odziedziczone z pliku .aidl
.
Oto przykład implementacji interfejsu IRemoteService
zdefiniowanego w poprzednim przykładzie IRemoteService.aidl
przy użyciu 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. } };
binder
jest teraz instancją klasy Stub
(Binder
), która określa interfejs IPC usługi. W następnym kroku ta instancja będzie widoczna dla klientów, którzy mogą korzystać z usługi.
Implementując interfejs AIDL, pamiętaj o kilku zasadach:
- Nie ma gwarancji, że połączenia przychodzące zostaną wykonane w wątku głównym, więc musisz od początku zastanowić się nad wielowątkowością i odpowiednio przygotować usługę tak, aby była bezpieczna w wątku.
- Domyślnie wywołania IPC są synchroniczne. Jeśli wiesz, że wykonanie żądania przez usługę zajmuje więcej niż kilka milisekund, nie wywołuj go z głównego wątku aktywności. Aplikacja może zawiesić aplikację, co spowoduje wyświetlenie w Androidzie okna „Aplikacja nie odpowiada”. Wywołaj ją w osobnym wątku w kliencie.
- Tylko typy wyjątków wymienione w dokumentacji referencyjnej
Parcel.writeException()
są wysyłane z powrotem do elementu wywołującego.
Udostępnianie interfejsu klientom
Po zaimplementowaniu interfejsu usługi musisz udostępnić go klientom, aby mogli się z nim powiązać. Aby udostępnić interfejs swojej usługi, rozszerz Service
i zaimplementuj onBind()
, co zwróci instancję klasy, która implementuje wygenerowany obiekt Stub
. Jak omówiliśmy 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, na przykład aktywność, wywoła metodę bindService()
, aby połączyć się z tą usługą, wywołanie zwrotne onServiceConnected()
klienta odbierze wystąpienie binder
zwrócone przez metodę onBind()
usługi.
Klient musi też mieć dostęp do klasy interfejsu. Jeśli klient i usługa znajdują się w osobnych aplikacjach, to aplikacja kliencka 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ć YourServiceInterface.Stub.asInterface(service)
, aby rzutować 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
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ą również typy, które są obsługiwane jako argumenty interfejsu AIDL i inne paczki. Pozwala to uniknąć dodatkowej pracy związanej z ręcznym pisaniem kodu marshalla i klasy niestandardowej. Powoduje to jednak również utworzenie samej struktury. Jeśli chcesz korzystać z niestandardowych akcesorów lub innych funkcji, 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ę Javy z polami liczb całkowitych left
, top
, right
i bottom
. Cały odpowiedni kod organizacyjny jest implementowany automatycznie, a obiektu można używać bezpośrednio bez konieczności dodawania implementacji.
Możesz też wysyłać klasę niestandardową z jednego procesu do innego przez interfejs IPC. Upewnij się jednak, że klasa jest dostępna po drugiej stronie kanału IPC, a klasa musi obsługiwać interfejs Parcelable
. Obsługa Parcelable
jest ważna, ponieważ umożliwia systemowi Android rozkładanie obiektów na podstawowe, które mogą być łączone w różnych procesach.
Aby utworzyć klasę niestandardową obsługującą Parcelable
, wykonaj te czynności:
- Zadbaj o to, aby zajęcia korzystały z interfejsu
Parcelable
. - Wdróż
writeToParcel
, który pobiera aktualny stan obiektu i zapisuje go wParcel
. - Dodaj do klasy pole statyczne o nazwie
CREATOR
, które jest obiektem implementującym interfejsParcelable.Creator
. - Na koniec utwórz plik
.aidl
deklarujący klasę Twoich paczek, jak w poniższym plikuRect.aidl
.Jeśli korzystasz z niestandardowego procesu kompilacji, nie dodawaj do kompilacji pliku
.aidl
. Ten plik.aidl
nie jest skompilowany podobnie jak plik nagłówka w języku C.
AIDL wykorzystuje w generowanym kodzie te metody i pola do porządkowania i usuwania obiektów.
Oto na przykład plik Rect.aidl
umożliwiający utworzenie klasy Rect
z możliwością przetwarzania:
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 klasa 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; } }
Organizowanie w klasie Rect
jest proste. Zapoznaj się z pozostałymi metodami w Parcel
, aby poznać inne rodzaje wartości, które możesz zapisywać w polu Parcel
.
Ostrzeżenie: pamiętaj o wpływie odbierania danych z innych procesów na bezpieczeństwo. W tym przypadku Rect
odczytuje 4 cyfry z Parcel
, ale to Ty musisz się upewnić, że ich wartości mieszczą się w akceptowalnym zakresie wartości możliwych do wykonania. 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 interfejs Parcelable
Jeśli metoda akceptuje obiektBundle
, który powinien zawierać obiekty parcelable, ustaw moduł ładowania klasy Bundle
przez wywołanie Bundle.setClassLoader(ClassLoader)
przed próbą odczytu z interfejsu Bundle
. W przeciwnym razie napotkasz kod ClassNotFoundException
, mimo że plik parcelable jest prawidłowo zdefiniowany w Twojej aplikacji.
Przeanalizujmy 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, właściwość
ClassLoader
jest wyraźnie ustawiona w Bundle
przed odczytaniem parametru 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 te czynności w klasie wywołującej:
- Umieść plik
.aidl
w katalogu projektusrc/
. - Zadeklaruj instancję interfejsu
IBinder
, która jest generowana na podstawie AILIDL. - Wdróż
ServiceConnection
. - Wywołaj
Context.bindService()
, przekazując implementacjęServiceConnection
. - W implementacji
onServiceConnected()
otrzymujesz wystąpienieIBinder
o nazwieservice
. WywołajYourInterfaceName.Stub.asInterface((IBinder)service)
, aby rzutować zwrócony parametr na typYourInterface
. - Wywołaj metody zdefiniowane w interfejsie. Zawsze wykrywaj wyjątki
DeadObjectException
, które są zgłaszane po przerwaniu połączenia. Pułapka obejmuje też wyjątkiSecurityException
, które są wywoływane, gdy 2 procesy związane z wywołaniem metody IPC mają sprzeczne definicje AIDL. - Aby się rozłączyć, wywołaj polecenie
Context.unbindService()
przy użyciu instancji interfejsu.
Podczas wywoływania usługi IPC pamiętaj o tych kwestiach:
- Obiekty są zliczane w procesach.
- Jako argumenty metody możesz wysyłać anonimowe obiekty.
Więcej informacji o powiązaniu z usługą znajdziesz w artykule Omówienie usług granicznych.
Oto przykładowy kod, który pokazuje wywoływanie usługi utworzonej przez AIDL, pobranej 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—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—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); } } } }