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ówBinder
jako zwykłe zdalne wywołanie. Jeślioneway
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:
- Utwórz plik
.aidl
Plik ten definiuje interfejs programowania za pomocą sygnatur metod.
- 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 nazwieStub
, która rozszerza zakresBinder
i implementuje metody z interfejsu AIDL. Musisz rozszerzyć klasęStub
i zaimplementować metody. - Prezentowanie interfejsu klientom
Zaimplementuj
Service
i zastąponBind()
, aby zwrócić implementację klasyStub
.
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[]
lubMyParcelable[]
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. StrukturyList
można opcjonalnie używać jako parametryzowanej klasy typu, np.List<String>
. Rzeczywista klasa betonowa odbierana przez drugą stronę zawsze toArrayList
, choć metoda jest generowana przy użyciu interfejsuList
.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 jakMap<String,Integer>
, nie są obsługiwane. Rzeczywista konkretna klasa, którą otrzymuje druga strona, jest zawszeHashMap
, chociaż metoda jest generowana do użycia interfejsuMap
. Rozważ użycieBundle
zamiastMap
.
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
lubinout
(patrz przykład poniżej).Pierwiastki,
String
,IBinder
i interfejsy wygenerowane przez AIDL są domyślniein
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 interfejsieIBinder
, 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
, 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.
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:
- Klasa musi implementować interfejs
Parcelable
. - Zaimplementuj funkcję
writeToParcel
, która pobiera bieżący stan obiektu i zapisuje go wParcel
. - Dodaj do klasy statyczne pole o nazwie
CREATOR
, które jest obiektem implementującym interfejsParcelable.Creator
. - Na koniec utwórz plik
.aidl
, który deklaruje klasę parcelable, jak pokazano w następującym plikuRect.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 obiektBundle
, 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); }
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:
- Dołącz plik
.aidl
do katalogu projektusrc/
. - Zadeklaruj instancję interfejsu
IBinder
, który jest generowany na podstawie AIDL. - Wdróż
ServiceConnection
. - Zadzwoń do
Context.bindService()
, przekazując implementacjęServiceConnection
. - W implementacji
onServiceConnected()
otrzymujesz instancjęIBinder
o nazwieservice
. Zadzwoń do funkcjiYourInterfaceName.Stub.asInterface((IBinder)service)
, aby zamienić zwrócony parametr na typYourInterface
. - Wywołuj metody zdefiniowane w interfejsie. Zawsze przechwytuj wyjątki
DeadObjectException
, które są zgłaszane, gdy połączenie zostanie przerwane. Ponadto przechwyć wyjątkiSecurityException
, które są zgłaszane, gdy 2 procesy biorące udział w wywołaniu metody IPC mają sprzeczne definicje AIDL. - 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—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); } } } }