Język definiowania interfejsu Androida (AIDL) jest podobny do innych Identyfikatory IDL: pozwala zdefiniować interfejs programowania, uzgodnione przez klienta i usługę w celu komunikowania się ze sobą za pomocą komunikacji międzyprocesowej (IPC).
W Androidzie jeden proces nie może normalnie uzyskać dostępu do pamięci innego procesu. Aby mówić, trzeba podzielić obiekty na elementy podstawowe, system operacyjny jest w stanie zrozumieć i zeskanować obiekty za Ciebie. Kod do i udawanie, że pisanie jest uciążliwe, dlatego Android robi to za Ciebie dzięki AIDL.
Uwaga: zastosowanie AIDL jest konieczne tylko wtedy, gdy klienci z
różne aplikacje uzyskują dostęp do Twojej usługi dla IPC i chcesz obsługiwać wielowątkowość
posprzedażna. Jeśli nie trzeba przeprowadzać równoległego IPC na
różnych aplikacji, utwórz interfejs, implementując
Binder
Jeśli chcesz przeprowadzać IPC, ale nie musisz obsługiwać wielowątkowości,
zaimplementuj interfejs za pomocą Messenger
.
Niezależnie od tego upewnij się, że znasz powiązane usługi przed
wdrożenia AIDL.
Przed rozpoczęciem projektowania interfejsu AIDL należy pamiętać, że wywołania interfejsu AIDL są bezpośrednich wywołań funkcji. Nie należy przyjmować żadnych założeń na temat wątku, w którym wywołanie ma miejsce. Sposób działania różni się w zależności od tego, czy wywołanie pochodzi z wątku proces lokalny lub zdalny:
- Wywołania wykonywane z procesu lokalnego są wykonywane w tym samym wątku, w którym następują to wywołanie. Jeśli
to jest główny wątek UI, który jest nadal wykonywany w interfejsie AIDL. Jeśli tak
który wykonuje kod w usłudze. Dlatego, jeśli tylko lokalne
gdy wątki uzyskują dostęp do usługi, masz pełną kontrolę nad tym, które wątki są w niej wykonywane. Ale
w takim przypadku nie używaj w ogóle AIDL. zamiast tego utwórz
przez zaimplementowanie
Binder
- Wywołania z procesu zdalnego są wysyłane z puli wątków, którą utrzymuje platforma do własnych potrzeb. Przygotuj się na wiele rozmów przychodzących z nieznanych wątków w tym samym czasie. Innymi słowy, implementacja interfejsu AIDL musi być są całkowicie bezpieczne w wątkach. Wywołania wykonywane z jednego wątku w przypadku tego samego obiektu zdalnego dotrze w zamówieniu po stronie odbiorcy.
- Słowo kluczowe
oneway
zmienia działanie wywołań zdalnych. W przypadku jego użycia połączenie zdalne nie blokować. Wysyła dane transakcji i natychmiast zwraca dane. Implementacja interfejsu otrzymuje w końcu zwykłe wywołanie z puli wątkówBinder
jako zwykłe wywołanie zdalne. Jeśli w połączeniu z lokalnymi połączeniami używany jest numeroneway
, Nie ma to żadnego wpływu, a wywołanie jest nadal synchroniczne.
Definiowanie interfejsu AIDL
Zdefiniuj interfejs AIDL w pliku .aidl
za pomocą języka Java
składni języka programowania, a potem zapisz ją w kodzie źródłowym, w katalogu src/
aplikację hostującą usługę i wszelkie inne aplikacje powiązane z usługą.
Przy tworzeniu każdej aplikacji, która zawiera plik .aidl
, narzędzia Android SDK
wygeneruj interfejs IBinder
na podstawie pliku .aidl
i zapisz go w
katalogu gen/
projektu. Usługa musi implementować interfejs IBinder
interfejsu użytkownika. Aplikacje klienckie mogą wówczas powiązać usługę i metody wywoływania z
IBinder
do przeprowadzenia IPC.
Aby utworzyć usługę ograniczoną za pomocą AIDL, wykonaj opisane poniżej czynności w następujących sekcjach:
- Utwórz plik
.aidl
Definiuje interfejs programowania za pomocą podpisów metod.
- Implementacja interfejsu
Narzędzia Android SDK generują interfejs w języku programowania Java na podstawie
.aidl
. Ten interfejs ma wewnętrzną klasę abstrakcyjną o nazwieStub
, która rozciąga sięBinder
i implementuje metody z interfejsu AIDL. Musisz przedłużyćStub
i zaimplementuj metody. - Prezentowanie interfejsu klientom
Zaimplementuj
Service
i zastąponBind()
, aby zwrócić implementacjęStub
zajęcia.
Uwaga: wszelkie zmiany wprowadzone w interfejsie AIDL po
pierwsza wersja musi być zgodna wstecznie, aby uniknąć uszkodzenia innych aplikacji
którzy korzystają z Twojej usługi. Wynika to z faktu, że plik .aidl
musi zostać skopiowany do innych aplikacji
aby mieli dostęp do interfejsu usługi, musisz utrzymać obsługę
za pomocą prostego interfejsu online.
Tworzenie pliku .aidl
AIDL używa prostej składni, która umożliwia zadeklarowanie interfejsu za pomocą jednej lub kilku metod, które biorą parametry i zwracają wartości. Parametry i zwracane wartości mogą być dowolnego typu, nawet Interfejsy wygenerowane przez AIDL.
Plik .aidl
musisz utworzyć w języku programowania Java. Co .aidl
musi definiować jeden interfejs i wymaga jedynie deklaracji interfejsu i metody
podpisy.
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 w polu
List
muszą być jednym z typów danych obsługiwanych w tym lub jeden z innych zadeklarowanych przez Ciebie interfejsów bądź usług wygenerowanych przez AIDL. O ParametrList
może być opcjonalnie używany jako klasa typu z parametrami, taka jakList<String>
Rzeczywista klasa betonowa, którą otrzymuje druga strona, to zawszeArrayList
, chociaż jest generowana w interfejsieList
.Map
Wszystkie elementy w polu
Map
muszą być jednym z typów danych obsługiwanych w tym lub jeden z innych zadeklarowanych przez Ciebie interfejsów bądź usług wygenerowanych przez AIDL. mapowania typów z parametrami; na przykład w formieMap<String,Integer>
, nie są obsługiwane. Rzeczywista klasa betonu, którą druga strona otrzymuje zawszeHashMap
, chociaż metoda jest generowana pod kątem użycia interfejsuMap
. Rozważ użycieBundle
jako alternatywę dlaMap
.
Musisz dołączyć instrukcję import
dla każdego dodatkowego typu, który nie został wymieniony wcześniej,
nawet jeśli są zdefiniowane w tym samym pakiecie co interfejs.
Podczas definiowania interfejsu usługi pamiętaj, że:
- Metody mogą przyjmować zero lub więcej parametrów i zwracać wartość lub wartość void.
- Wszystkie parametry inne niż podstawowe wymagają tagu kierunkowego wskazującego, w którą stronę przekazywane są dane:
in
,out
lubinout
(zobacz przykład poniżej).Elementy podstawowe,
String
,IBinder
i wygenerowane przez AIDL interfejsy mają domyślnie wartośćin
i nie można ich zmienić w inny sposób.Uwaga: ogranicz wskazówki do treści, które są rzeczywiście ponieważ parametry wyznaczania trasy są drogie.
- Wszystkie komentarze do kodu zawarte w pliku
.aidl
są uwzględnione w parametrze wygenerowanoIBinder
z wyjątkiem komentarzy przed importem i pakietem wyciągów. - Ciągi tekstowe i stałe int mogą być zdefiniowane w interfejsie AIDL, np. w
const int VERSION = 1;
. - Wywołania metod są wysyłane przez
transact()
, który zwykle opiera się na indeksie metod w interfejsie. Ponieważ utrudniają obsługę wersji, Użytkownik może ręcznie przypisać kod transakcji do metody:void method() = 10;
. - Argumenty do wartości null i zwracane typy muszą być opatrzone 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
jeśli utworzysz aplikację, narzędzia SDK wygenerują 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 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ę segregatora następnym razem,
utworzyć aplikację. Skompiluj projekt w usłudze gradle assembleDebug
lub gradle assembleRelease
, gdy tylko skończysz zapisywać plik .aidl
,
aby kod mógł połączyć się z wygenerowaną klasą.
Implementacja interfejsu
Gdy tworzysz aplikację, narzędzia Android SDK generują plik interfejsu .java
o nazwie odpowiadającej nazwie pliku .aidl
. Wygenerowany interfejs zawiera podklasę o nazwie Stub
jest abstrakcyjną implementacją interfejsu nadrzędnego, np. YourInterface.Stub
, i deklaruje wszystkie metody z pliku .aidl
.
Uwaga: Stub
też
określa kilka metod pomocniczych, przede wszystkim asInterface()
, która wymaga IBinder
, zwykle metody przekazywanej do metody wywołania zwrotnego onServiceConnected()
klienta, oraz
zwraca instancję interfejsu stub. Więcej informacji o realizacji obsady znajdziesz w sekcji Wywołanie IPC
.
Aby wdrożyć interfejs wygenerowany na podstawie .aidl
, przedłuż wygenerowany Binder
takich jak YourInterface.Stub
, i zaimplementuj metody
odziedziczone z pliku .aidl
.
Oto przykładowa implementacja interfejsu o nazwie IRemoteService
zdefiniowany przez
Przykład IRemoteService.aidl
z użyciem 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. } };
Teraz binder
jest instancją klasy Stub
(Binder
),
który definiuje interfejs IPC usługi. W następnym kroku ta instancja będzie dostępna dla
klientów, aby mogli korzystać z usługi.
Implementując interfejs AIDL, pamiętaj o kilku zasadach:
- Nie ma gwarancji, że połączenia przychodzące będą wykonywane w wątku głównym, więc musisz się zastanowić, o wielowątkowości i prawidłowym skompilowaniu usługi w taki sposób, aby była bezpieczna w wątkach.
- Domyślnie wywołania IPC są synchroniczne. Jeśli wiesz, że usługa wymaga więcej niż kilku milisekund na wykonanie żądania, nie używaj go w głównym wątku aktywności. Może to spowodować zawieszenie aplikacji i pojawia się komunikat „Aplikacja nie odpowiada” . Wywołaj je w osobnym wątku w kliencie.
- Tylko typy wyjątków wymienione w dokumentacji referencyjnej dla
Parcel.writeException()
zostaną odesłane do rozmówcy.
Udostępnienie interfejsu klientom
Po zaimplementowaniu interfejsu usługi musisz udostępnić go
klientów, aby mogli się z nimi wiązać. Udostępnienie interfejsu
dla swojej usługi, rozszerz Service
i zaimplementuj onBind()
, by zwrócić instancję klasy, która
w wygenerowanym obiekcie Stub
, jak omówiliśmy w poprzedniej sekcji. Oto przykład:
Udostępnia ona 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 działanie, wywoła bindService()
, aby połączyć się z tą usługą, wywołanie zwrotne onServiceConnected()
klienta otrzyma
binder
instancja zwrócona przez usługę onBind()
.
Klient musi też mieć dostęp do klasy interfejsu. Jeśli więc klient i usługa znajdują się
osobnych aplikacji, to aplikacja klienta musi mieć kopię pliku .aidl
w katalogu src/
, który generuje android.os.Binder
który zapewnia klientowi dostęp do metod AIDL.
Gdy klient otrzyma IBinder
w wywołaniu zwrotnym onServiceConnected()
, musi wywołać
YourServiceInterface.Stub.asInterface(service)
, aby przesłać zwrócone
do typu 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 tutaj
RemoteService.java
zajęcia w
.
ApiDemos
Przekazywanie obiektów przez IPC
W Androidzie 10 (poziom API 29 lub wyższy) możesz zdefiniować
Parcelable
obiektów bezpośrednio w
AIDL. Typy obsługiwane jako argumenty interfejsu AIDL i inne obiekty parcelable są również
obsługiwane tutaj. Pozwala to uniknąć dodatkowej pracy związanej z ręcznym pisaniem kodu zarządzania oraz
zajęcia. W tym przypadku również powstaje słaba struktura. Jeśli niestandardowe akcesoria lub inne funkcje są
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ę Java z polami liczb całkowitych left
,
top
, right
i bottom
. Cały odpowiedni kod zarządzania to
jest zaimplementowany automatycznie. Obiekt można używać bezpośrednio, bez konieczności dodawania
implementacji.
Możesz też wysłać klasę niestandardową z jednego procesu do innego przez interfejs IPC. Pamiętaj jednak:
W tym celu należy sprawdzić, czy kod zajęć jest dostępny po drugiej stronie kanału IPC.
klasa musi obsługiwać interfejs Parcelable
. Wsparcie
Domena Parcelable
jest ważna
bo umożliwia systemowi Android rozkładanie obiektów na obiekty podstawowe, które można uporządkować
między różnymi procesami.
Aby utworzyć klasę niestandardową obsługującą interfejs Parcelable
, wykonaj
:
- Przygotuj klasę do wdrożenia interfejsu
Parcelable
. - Wdróż
writeToParcel
, który wymaga obecny stan obiektu i zapisuje go wParcel
. - Dodaj do klasy statyczne pole o nazwie
CREATOR
, które jest obiektem implementującym w interfejsieParcelable.Creator
. - Na koniec utwórz plik
.aidl
deklarujący klasę dosłowną – w przykładzie poniżejRect.aidl
.Jeśli korzystasz z niestandardowego procesu kompilacji, nie dodawaj pliku
.aidl
do pliku tworzyć. Podobnie jak plik nagłówka w języku C, ten plik.aidl
nie jest skompilowany.
AIDL używa tych metod i pól w generowanym kodzie do segregowania i usuwania znaczników Twoje obiekty.
Oto przykładowy plik Rect.aidl
służący do utworzenia klasy Rect
, która
parcelable:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
A oto przykład tego, jak klasa Rect
implementuje
Protokół Parcelable
.
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. Przyjrzyj się innej
w Parcel
, aby zobaczyć inne rodzaje wartości, które możesz zapisywać
do: Parcel
.
Ostrzeżenie: pamiętaj o potencjalnych problemach z odbieraniem
z innych procesów. W tym przypadku Rect
odczytuje 4 cyfry z Parcel
, ale Ty musisz upewnić się, że mieszczą się one w akceptowalnym zakresie
niezależnie od tego, co próbuje zrobić wywołujący. 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 obiekty Parcelable
Jeśli metoda akceptuje obiektBundle
, który powinien zawierać
Parcelables, pamiętaj, aby ustawić moduł uruchamiający klasy Bundle
Wywołuję Bundle.setClassLoader(ClassLoader)
przed próbą odczytania
z Bundle
. W przeciwnym razie napotkasz zdarzenie ClassNotFoundException
, mimo że parcelable jest poprawnie określony 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); }
ClassLoader
to
jawnie ustawiony w Bundle
przed odczytem 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 następujące czynności w Twoja klasa dzwonienia:
- Umieść plik
.aidl
w katalogusrc/
projektu. - Zadeklaruj wystąpienie interfejsu
IBinder
, które jest generowane na podstawie AIDL. - Wdróż
ServiceConnection
. - Zadzwoń do firmy
Context.bindService()
, które przekazujesz w implementacjiServiceConnection
. - W ramach implementacji interfejsu
onServiceConnected()
otrzymaszIBinder
instancji o nazwieservice
. Zadzwoń do nasYourInterfaceName.Stub.asInterface((IBinder)service)
do rzutować zwracany parametr na typYourInterface
. - Wywołaj metody zdefiniowane w interfejsie. Zawsze pułapka
DeadObjectException
wyjątków, które są zgłaszane, gdy połączenie może zostać przerwane. Dodatkowo wyjątki pułapkiSecurityException
, które są zgłaszane, gdy 2 procesy uczestniczące w wywołaniu metody IPC mają sprzeczne definicje AIDL. - Aby się rozłączyć, wywołaj
Context.unbindService()
za pomocą instancji interfejsu.
Gdy wywołujesz usługę IPC, pamiętaj o tych kwestiach:
- Obiekty są wartościami referencyjnymi zliczonymi w procesach.
- Możesz wysyłać anonimowe obiekty jako argumentów metody.
Więcej informacji o tworzeniu powiązań z usługą znajdziesz w artykule Omówienie usług granicznych.
Oto przykładowy kod pokazujący wywoływanie usługi utworzonej przez AIDL, pobrane z przykładu 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); } } } }