Język definiowania interfejsów Androida (AIDL) jest podobny do innych IDL: pozwala zdefiniować interfejs programowania, który jest uzgadniany przez klienta i usługę w celu komunikacji między sobą za pomocą komunikacji między procesami (IPC).
W Androidzie jeden proces nie może normalnie korzystać z pamięci innego procesu. Aby się komunikować, muszą rozkładać swoje obiekty na elementy, które system operacyjny może zrozumieć, oraz przekazywać je przez tę granicę. Kod potrzebny do tego przekształcania jest trudny do napisania, dlatego Android wykonuje to za Ciebie za pomocą AIDL.
Uwaga: interfejs AIDL jest potrzebny tylko wtedy, gdy zezwalasz klientom z innych aplikacji na dostęp do usługi w ramach komunikacji międzyprocesowej i chcesz obsługiwać wielowątkowość w swojej usłudze. Jeśli nie musisz wykonywać równoczesnych operacji IPC w różnych aplikacjach, utwórz interfejs, wdrażając Binder
.
Jeśli chcesz wykonywać IPC, ale nie potrzebujesz obsługi wielowątkowości, zaimplementuj swój interfejs za pomocą Messenger
.
Przed wdrożeniem AIDL upewnij się, że rozumiesz usługi powiązane.
Zanim zaczniesz projektować interfejs AIDL, pamiętaj, że wywołania interfejsu AIDL są bezpośrednimi wywołaniami funkcji. Nie rób założeń dotyczących wątku, w którym występuje wywołanie. To, co się stanie, zależy od tego, czy wywołanie pochodzi z wątku w procesie lokalnym czy zdalnym:
- Wywołania wykonywane z procesu lokalnego są wykonywane w tym samym wątku, który je wywołuje. Jeśli jest to Twój główny wątek UI, będzie on nadal wykonywany w interfejsie AIDL. Jeśli jest to inny wątek, to on wykonuje kod w usłudze. Jeśli więc tylko wątki lokalne uzyskują dostęp do usługi, możesz w pełni kontrolować, które wątki się w niej wykonują. W takim przypadku jednak w ogóle nie używaj AIDL. Zamiast tego utwórz interfejs, wdrażając
Binder
. - Wywołania procesu zdalnego są wysyłane z puli wątków, którą platforma obsługuje w ramach własnego procesu. Przygotuj się na połączenia z nieznanych wątków, w tym kilka połączeń naraz. Inaczej mówiąc, implementacja interfejsu AIDL musi być całkowicie bezpieczna w zakresie wątków. Wywołania z jednego wątku na tym samym obiekcie zdalnym docierają w kolejności po stronie odbiorczej.
- Słowo kluczowe
oneway
modyfikuje działanie połączeń zdalnych. Gdy jest używane, wywołanie zdalne nie jest blokowane. Wysyła dane transakcji i natychmiast się zwraca. Implementacja interfejsu ostatecznie otrzymuje to jako zwykłe wywołanie z poolu wątkówBinder
jako zwykłe zdalne wywołanie. Jeśli użyjeszoneway
z wywołaniem lokalnym, nie będzie to miało żadnego wpływu i wywołanie będzie nadal synchroniczne.
Definiowanie interfejsu AIDL
Zdefiniuj interfejs AIDL w pliku .aidl
przy użyciu składni języka programowania w języku Java, a następnie zapisz go w kodzie źródłowym, w katalogu src/
zarówno aplikacji hostującej usługę, jak i innej aplikacji powiązanej z usługą.
Gdy skompilujesz każdą aplikację, która zawiera plik .aidl
, narzędzia Android SDK generują interfejs IBinder
na podstawie pliku .aidl
i zapisują go w katalogu gen/
projektu. Usługa musi odpowiednio zaimplementować interfejs IBinder
. Aplikacje klienckie mogą następnie tworzyć powiązania z usługą i wywołaniami metod z IBinder
w celu wykonywania IPC.
Aby utworzyć usługę ograniczoną za pomocą AIDL, wykonaj te czynności opisane w następnych sekcjach:
- Tworzenie pliku
.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ą abstrakcyjną klasę o nazwieStub
, która rozszerza klasęBinder
i implementuje metody z interfejsu AIDL. Musisz rozszerzyć klasęStub
i zaimplementować metody. - Wyświetlanie 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 zwracane wartości mogą być dowolnego typu, nawet w przypadku interfejsów wygenerowanych przez AIDL.
Plik .aidl
musisz utworzyć w języku programowania Java. Każdy plik .aidl
musi definiować pojedynczy interfejs i wymagać tylko deklaracji interfejsu oraz podpisów metod.
Domyślnie AIDL obsługuje te typy danych:
- Wszystkie typy podstawowe w języku programowania Java (np.
int
,long
,char
,boolean
itp.) - tablice dowolnego typu, np.
int[]
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.List
może być opcjonalnie używane jako klasa typu z parametrami, np.List<String>
. Rzeczywista konkretna klasa, którą otrzymuje druga strona, jest zawszeArrayList
, chociaż metoda jest generowana do użycia 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ą przyjmować zero lub więcej parametrów i zwracać wartość lub wartość void.
- Wszystkie parametry nieelementarne wymagają tagu kierunkowego wskazującego kierunek przesyłania danych:
in
,out
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 do kodu zawarte w pliku
.aidl
są uwzględniane w wygenerowanym interfejsieIBinder
z wyjątkiem komentarzy przed importem i instrukcjami pakietu. - W interfejsie AIDL można zdefiniować stałe typu string i int, np.
const int VERSION = 1;
. - Wywołania metod są wysyłane przez
transact()
kod, który zwykle jest oparty na indeksie metody w interfejsie. Ponieważ utrudnia to wersjonowanie, możesz ręcznie przypisać kod transakcji do metody:void method() = 10;
. - Argumenty i typy zwracane, które mogą być puste, muszą być opatrzone adnotacją
@nullable
.
Oto przykładowy plik .aidl
:
// IRemoteService.aidl package com.example.android; // Declare any non-default types here with import statements. /** Example service interface */ interface IRemoteService { /** Request the process ID of this service. */ int getPid(); /** Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); }
Zapisz plik .aidl
w katalogu src/
projektu. Gdy tworzysz aplikację, narzędzia SDK generują plik interfejsu IBinder
w katalogu gen/
Twojego projektu. Nazwa wygenerowanego pliku odpowiada nazwie pliku .aidl
, ale ma rozszerzenie .java
. Na przykład IRemoteService.aidl
daje wynik IRemoteService.java
.
Jeśli używasz Android Studio, kompilacja przyrostowa wygeneruje klasę bindera niemal natychmiast.
Jeśli nie używasz Android Studio, narzędzie Gradle wygeneruje klasę bindera przy następnym kompilowaniu aplikacji. Zbuduj projekt za pomocą gradle assembleDebug
lub gradle assembleRelease
, gdy tylko skończysz pisać plik .aidl
, aby Twój kod mógł połączyć się z wygenerowaną klasą.
Wdrażanie interfejsu
Podczas kompilowania aplikacji narzędzia pakietu Android SDK generują plik interfejsu .java
o nazwie .aidl
. Wygenerowany interfejs zawiera podklasę o nazwie Stub
, która jest abstraktną implementacją interfejsu nadrzędnego, np. YourInterface.Stub
, i deklaruje wszystkie metody z pliku .aidl
.
Uwaga: Stub
definiuje również kilka metod pomocniczych, przede wszystkim asInterface()
, która pobiera IBinder
, zwykle tę przekazywaną do metody wywołania zwrotnego onServiceConnected()
klienta, i zwraca instancję interfejsu namiotowego. Więcej informacji o tym, jak wykonać to przypisanie, znajdziesz w sekcji Wywoływanie metody IPC.
Aby zaimplementować interfejs wygenerowany na podstawie pliku .aidl
, rozszerz wygenerowany interfejs Binder
(np. YourInterface.Stub
) i zaimplementuj metody odziedziczone z pliku .aidl
.
Oto przykład implementacji interfejsu o nazwie IRemoteService
, zdefiniowanego w poprzednim przykładzie IRemoteService.aidl
, przy użyciu anonimowej instancji:
Kotlin
private val binder = object : IRemoteService.Stub() { override fun getPid(): Int = Process.myPid() override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } }
Java
private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } };
Teraz binder
jest wystąpieniem klasy Stub
(Binder
), która definiuje interfejs IPC usługi. W następnym kroku ta instancja zostanie udostępniona klientom, aby mogli wchodzić w interakcję z usługą.
Podczas implementowania interfejsu AIDL należy pamiętać o kilku zasadach:
- Nie ma gwarancji, że wywołania przychodzące będą wykonywane w wątku głównym, dlatego już na początku musisz pomyśleć o wielowątkowości i odpowiednio stworzyć usługę, aby była bezpieczna w zakresie wątków.
- Domyślnie wywołania IPC są synchroniczne. Jeśli wiesz, że wykonanie żądania przez usługę trwa dłużej niż kilka milisekund, nie wywołujej jej z głównego wątku aktywności. Może to spowodować zawieszenie aplikacji, w efekcie Android wyświetli okno „Aplikacja nie odpowiada”. Wywołaj go z osobnego wątku w kliencie.
- Do wywołującego są wysyłane tylko typy wyjątków wymienione w dokumentacji referencyjnej dotyczącej
Parcel.writeException()
.
Udostępnianie interfejsu klientom
Po zaimplementowaniu interfejsu usługi musisz udostępnić go klientom, aby mogli się z nim połączyć. Aby udostępnić interfejs swojej usługi, rozszerz klasę Service
i zaimplementuj metodę onBind()
, która zwraca instancję klasy implementującej wygenerowany interfejs Stub
, jak opisano w poprzedniej sekcji. Oto przykładowa usługa, która udostępnia klientom przykładowy interfejs IRemoteService
.
Kotlin
class RemoteService : Service() { override fun onCreate() { super.onCreate() } override fun onBind(intent: Intent): IBinder { // Return the interface. return binder } private val binder = object : IRemoteService.Stub() { override fun getPid(): Int { return Process.myPid() } override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } } }
Java
public class RemoteService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { // Return the interface. return binder; } private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } }; }
Teraz, gdy klient, np. aktywność, wywołuje funkcję bindService()
, aby połączyć się z tą usługą, wywołanie zwrotne onServiceConnected()
klienta otrzymuje instancję binder
zwracaną przez metodę onBind()
usługi.
Klient musi też mieć dostęp do klasy interfejsu. Jeśli więc klient i usługa znajdują się w oddzielnych aplikacjach, aplikacja klienta musi mieć kopię pliku .aidl
w katalogu src/
, który generuje interfejs android.os.Binder
, zapewniając klientowi dostęp do metod AIDL.
Gdy klient otrzyma parametr IBinder
w wywołaniu zwrotnym onServiceConnected()
, musi wywołać funkcję YourServiceInterface.Stub.asInterface(service)
, aby zamienić zwrócony parametr na typ YourServiceInterface
:
Kotlin
var iRemoteService: IRemoteService? = null val mConnection = object : ServiceConnection { // Called when the connection with the service is established. override fun onServiceConnected(className: ComponentName, service: IBinder) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service) } // Called when the connection with the service disconnects unexpectedly. override fun onServiceDisconnected(className: ComponentName) { Log.e(TAG, "Service has unexpectedly disconnected") iRemoteService = null } }
Java
IRemoteService iRemoteService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established. public void onServiceConnected(ComponentName className, IBinder service) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly. public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "Service has unexpectedly disconnected"); iRemoteService = null; } };
Więcej przykładowego kodu znajdziesz w klasie
RemoteService.java
w pakiecie
ApiDemos.
Przekazywanie obiektów przez IPC
W Androidzie 10 (poziom interfejsu API 29 lub wyższy) możesz definiować obiekty Parcelable
bezpośrednio w AIDL. Tutaj są też obsługiwane typy obsługiwane jako argumenty interfejsu AIDL i inne obiekty parcelable. Dzięki temu nie musisz ręcznie pisać kodu porządkowania i klasy niestandardowej. Jednak powoduje też utworzenie pustej struktury. Jeśli chcesz użyć niestandardowych akcesoriów lub innych funkcji, zastosuj zamiast tego Parcelable
.
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect { int left; int top; int right; int bottom; }
Poprzedni przykładowy kod automatycznie generuje klasę Java z polami liczb całkowitych left
, top
, right
i bottom
. Cały kod dotyczący obsługi jest implementowany automatycznie, a obiekt może być używany bezpośrednio bez konieczności dodawania implementacji.
Możesz też wysłać klasę niestandardową z jednego procesu do innego przez interfejs IPC. Upewnij się jednak, że kod Twojej klasy jest dostępny po drugiej stronie kanału IPC, a klasa musi obsługiwać interfejs Parcelable
. Obsługa Parcelable
jest ważna, ponieważ pozwala systemowi Androida rozkładać obiekty na elementy, które można porządkować w różnych procesach.
Aby utworzyć klasę niestandardową, która obsługuje Parcelable
, wykonaj te czynności:
- Klasa musi implementować interfejs
Parcelable
. - Zaimplementuj
writeToParcel
, który 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; } }
Tworzenie w klasie Rect
jest proste. Zapoznaj się z innymi metodami w obiekcie Parcel
, aby poznać inne rodzaje wartości, które możesz zapisać w obiekcie Parcel
.
Ostrzeżenie: pamiętaj o wpływie na bezpieczeństwo odbierania danych z innych procesów. W tym przypadku Rect
odczytuje 4 liczby z Parcel
, ale to od Ciebie zależy, czy będą one mieścić się w akceptowalnym zakresie wartości, niezależnie od tego, co chce zrobić dzwoniący. Więcej informacji o tym, jak chronić aplikację przed złośliwym oprogramowaniem, znajdziesz w artykule Wskazówki dotyczące bezpieczeństwa.
Metody z argumentami pakietu zawierającymi obiekty Parcelable
Jeśli metoda przyjmuje obiektBundle
, który ma zawierać obiekty Parcelable, przed próbą odczytu z obiektu Bundle
pamiętaj o ustawieniu ładowarki klas Bundle
przez wywołanie metody Bundle.setClassLoader(ClassLoader)
. W przeciwnym razie wystąpi błąd ClassNotFoundException
, mimo że klasa Parcelable jest prawidłowo zdefiniowana w aplikacji.
Przyjrzyjmy się na przykład temu przykładowemu plikowi .aidl
:
// IRectInsideBundle.aidl package com.example.android; /** Example service interface */ interface IRectInsideBundle { /** Rect parcelable is stored in the bundle with key "rect". */ void saveRect(in Bundle bundle); }Jak widać w poniższej implementacji, element
ClassLoader
jest wyraźnie ustawiony w elemencie Bundle
przed odczytaniem Rect
:
Kotlin
private val binder = object : IRectInsideBundle.Stub() { override fun saveRect(bundle: Bundle) { bundle.classLoader = classLoader val rect = bundle.getParcelable<Rect>("rect") process(rect) // Do more with the parcelable. } }
Java
private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() { public void saveRect(Bundle bundle){ bundle.setClassLoader(getClass().getClassLoader()); Rect rect = bundle.getParcelable("rect"); process(rect); // Do more with the parcelable. } };
Wywoływanie metody IPC
Aby wywołać zdalny interfejs zdefiniowany za pomocą AIDL, w klasie wywołującej wykonaj te czynności:
- Dołącz plik
.aidl
do katalogu projektusrc/
. - Zadeklaruj instancję interfejsu
IBinder
, który jest generowany na podstawie AIDL. - Wdróż
ServiceConnection
. - Wywołaj
Context.bindService()
, przekazując swoją 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, który demonstruje wywołanie usługi utworzonej za pomocą AIDL. Przykład pochodzi z usługi zdalnej w projekcie ApiDemos.
Kotlin
private const val BUMP_MSG = 1 class Binding : Activity() { /** The primary interface you call on the service. */ private var mService: IRemoteService? = null /** Another interface you use on the service. */ internal var secondaryService: ISecondary? = null private lateinit var killButton: Button private lateinit var callbackText: TextView private lateinit var handler: InternalHandler private var isBound: Boolean = false /** * Class for interacting with the main interface of the service. */ private val mConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // This is called when the connection with the service is // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service) killButton.isEnabled = true callbackText.text = "Attached." // We want to monitor the service for as long as we are // connected to it. try { mService?.registerCallback(mCallback) } catch (e: RemoteException) { // In this case, the service crashes before we can // do anything with it. We can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText( this@Binding, R.string.remote_service_connected, Toast.LENGTH_SHORT ).show() } override fun onServiceDisconnected(className: ComponentName) { // This is called when the connection with the service is // unexpectedly disconnected—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); } } } }