Die Android Interface Definition Language (AIDL) ähnelt anderen IDLs: können Sie die Programmierschnittstelle definieren, vereinbarten sich die Kundschaft und der Service, um mithilfe Interprocess Communication (IPC)
Unter Android kann ein Prozess normalerweise nicht auf die eines anderen Prozesses. Um zu sprechen, müssen sie ihre Objekte in Primitive zerlegen, das Betriebssystem die Objekte über diese Begrenzungen hinweg verstehen und platzieren kann. Der Code für ist das Marshalling mühsam, daher übernimmt Android dies mit AIDL für Sie.
Hinweis:AIDL ist nur erforderlich, wenn Sie Clients aus
verschiedene Anwendungen auf Ihren IPC-Dienst zugreifen
und Sie Multithreading in Ihrem
. Wenn Sie IPCs nicht gleichzeitig
Anwendungen zu erstellen, erstellen Sie Ihre Schnittstelle, indem Sie eine
Binder
Wenn Sie IPC ausführen möchten, aber kein Multithreading verarbeiten müssen,
Implementieren Sie Ihre Schnittstelle mithilfe eines Messenger
.
Unabhängig davon sollten Sie sich mit gebundenen Diensten vertraut machen, bevor Sie
Implementierung eines AIDL.
Bevor Sie mit dem Entwerfen Ihrer AIDL-Schnittstelle beginnen, beachten Sie, dass Aufrufe einer AIDL-Schnittstelle für direkten Funktionsaufrufen. Treffen Sie keine Annahmen über den Thread, in dem der Aufruf erfolgt. Was passiert, hängt davon ab, ob der Anruf aus einem Thread im einem lokalen Prozess oder einem Remote-Prozess:
- Aufrufe aus dem lokalen Prozess werden in dem Thread ausgeführt, der den Aufruf durchführt. Wenn
Dies ist Ihr UI-Hauptthread. Dieser Thread wird weiterhin in der AIDL-Schnittstelle ausgeführt. Falls ja
in dem der Code
in dem Dienst ausgeführt wird. Wenn also nur lokale
Threads auf den Dienst zugreifen, können Sie
vollständig steuern, welche Threads darin ausgeführt werden. Aber
Verwenden Sie in diesem Fall AIDL überhaupt nicht. erstellen Sie stattdessen die
Schnittstelle durch Implementierung eines
Binder
- Aufrufe von einem Remoteprozess werden aus einem Thread-Pool weitergeleitet, den die Plattform enthält Ihren eigenen Prozess. Auf eingehende Anrufe von unbekannten Threads mit mehreren Anrufen vorbereitet sein gleichzeitig passiert. Mit anderen Worten, die Implementierung einer AIDL-Schnittstelle muss Thread-sicher. Aufrufe von einem Thread für dasselbe Remote-Objekt in der richtigen Reihenfolge beim Empfänger eintreffen.
- Mit dem Schlüsselwort
oneway
wird das Verhalten von Remoteaufrufen geändert. Bei einem Remote-Aufruf nicht blockieren. Es sendet die Transaktionsdaten und kehrt sofort zurück. Die Implementierung der Schnittstelle empfängt dies schließlich als regulären Aufruf aus dem ThreadpoolBinder
als normaler Remoteaufruf. Wennoneway
mit einem lokalen Anruf verwendet wird, ohne Auswirkungen und der Aufruf ist dennoch synchron.
AIDL-Schnittstelle definieren
AIDL-Schnittstelle mithilfe der Java-Datei in einer .aidl
-Datei definieren
Syntax einer Programmiersprache und speichern Sie sie dann im Quellcode von beiden im Verzeichnis src/
.
die Anwendung, die den Dienst hostet, und jede andere Anwendung, die an den Dienst gebunden wird.
Wenn Sie jede Anwendung erstellen, die die Datei .aidl
enthält,
IBinder
-Schnittstelle basierend auf der Datei .aidl
generieren und speichern unter
Verzeichnis gen/
des Projekts. Der Dienst muss die IBinder
implementieren.
an die jeweilige Benutzeroberfläche. Die Client-Anwendungen können sich dann an den Dienst binden und Methoden aus
den IBinder
, um IPC durchzuführen.
Führen Sie die folgenden Schritte aus, um einen begrenzten Dienst mit AIDL zu erstellen. finden Sie in den folgenden Abschnitten:
.aidl
-Datei erstellenDiese Datei definiert die Programmierschnittstelle mit Methodensignaturen.
- Schnittstelle implementieren
Die Android SDK-Tools generieren eine Schnittstelle in der Programmiersprache Java basierend auf Ihrem
.aidl
-Datei. Diese Schnittstelle hat eine innere abstrakte Klasse namensStub
, dieBinder
und implementiert Methoden aus Ihrer AIDL-Schnittstelle. Sie müssen dieStub
und implementiere die Methoden. - Schnittstelle für Clients verfügbar machen
Implementieren Sie ein
Service
und überschreiben SieonBind()
, um die Implementierung derStub
zurückzugeben. .
Achtung:Alle Änderungen, die Sie nach dem
Ihr erster Release muss abwärtskompatibel bleiben, damit andere Anwendungen nicht beeinträchtigt werden
die Ihren Dienst nutzen. Das liegt daran, dass die Datei „.aidl
“ in andere Anwendungen kopiert werden muss
damit sie auf die Oberfläche Ihres Service zugreifen können, müssen Sie den Support für die ursprüngliche
.
AIDL-Datei erstellen
AIDL verwendet eine einfache Syntax, mit der Sie eine Schnittstelle mit einer oder mehreren Methoden deklarieren können, Parameter und Rückgabewerte. Die Parameter und Rückgabewerte können einen beliebigen Typ haben, auch AIDL-generierte Schnittstellen.
Sie müssen die Datei .aidl
mit der Programmiersprache Java erstellen. Jeweils .aidl
-Datei muss eine einzelne Schnittstelle definieren und erfordert nur die Interface-Deklaration und -Methode.
Signaturen.
Standardmäßig unterstützt AIDL die folgenden Datentypen:
- Alle primitiven Typen in der Programmiersprache Java (z. B.
int
,long
,char
,boolean
usw.) - Arrays primitiver Typen wie
int[]
String
CharSequence
List
Alle Elemente in der
List
müssen einen der unterstützten Datentypen in diesem Liste oder eine der anderen AIDL-generierten Schnittstellen oder Pakete, die Sie deklarieren. AList
kann optional als parametrisierte Typklasse verwendet werden, z. B.List<String>
. Die tatsächliche konkrete Klasse, die die andere Seite empfängt, ist immer einArrayList
, obwohl der wird generiert, um dieList
-Schnittstelle zu verwenden.Map
Alle Elemente in der
Map
müssen einen der unterstützten Datentypen in diesem Liste oder eine der anderen AIDL-generierten Schnittstellen oder Pakete, die Sie deklarieren. Parametrisierte Typzuordnungen, zum Beispiel in der FormMap<String,Integer>
werden nicht unterstützt. Die konkrete Klasse, die die andere Seite ist immer einHashMap
, obwohl die Methode zur Verwendung derMap
-Schnittstelle generiert wurde. Erwägen Sie die VerwendungBundle
als Alternative zuMap
.
Für jeden zusätzlichen Typ, der zuvor nicht aufgeführt wurde, müssen Sie eine import
-Anweisung verwenden.
auch wenn sie im selben Paket
wie Ihre Schnittstelle definiert sind.
Beachten Sie beim Definieren der Dienstschnittstelle Folgendes:
- Methoden können null oder mehr Parameter annehmen und einen Wert oder ein void-Element zurückgeben.
- Für alle nicht-primitiven Parameter ist ein Richtungs-Tag erforderlich, das angibt, in welche Richtung die Daten gehen sollen:
in
,out
oderinout
(siehe Beispiel unten).Primitives,
String
,IBinder
und AIDL-generiert Schnittstellen sind standardmäßigin
. Andernfalls ist dies nicht möglich.Achtung:Beschränken Sie die Richtung auf das, was wirklich ist. erforderlich, da das Mars-lling-Parameter- system teuer ist.
- Alle Codekommentare in der Datei
.aidl
sind in der generiertenIBinder
Schnittstelle mit Ausnahme von Kommentaren vor dem Import und dem Paket Aussagen. - String- und Ganzzahl-Konstanten können in der AIDL-Schnittstelle wie
const int VERSION = 1;
definiert werden. - Methodenaufrufe werden von einem
transact()
, der normalerweise auf einem Methodenindex in der Schnittstelle basiert. Da diese die Versionsverwaltung erschwert, können Sie kann den Transaktionscode manuell einer Methode zuweisen:void method() = 10;
. - Argumente und Rückgabetypen, für die Nullwerte zulässig sind, müssen mit
@nullable
annotiert werden.
Hier ist eine .aidl
-Beispieldatei:
// 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); }
Speichern Sie die Datei .aidl
im Verzeichnis src/
Ihres Projekts. Wenn Sie
App erstellen, generieren die SDK-Tools die IBinder
-Schnittstellendatei in Ihrem
Verzeichnis gen/
des Projekts. Der Name der generierten Datei stimmt mit dem Namen der Datei .aidl
überein, aber
mit der Erweiterung .java
. Beispielsweise führt IRemoteService.aidl
zu IRemoteService.java
.
Wenn Sie Android Studio verwenden, generiert der inkrementelle Build fast sofort die Binder-Klasse.
Wenn Sie Android Studio nicht verwenden, generiert das Gradle-Tool die Binder-Klasse das nächste Mal, wenn Sie
um Ihre Anwendung zu erstellen. Projekt mit gradle assembleDebug
erstellen
oder gradle assembleRelease
, sobald Sie mit dem Schreiben der .aidl
-Datei fertig sind,
damit Ihr Code mit der generierten Klasse verknüpft werden kann.
Schnittstelle implementieren
Wenn du deine App erstellst, generieren die Android SDK-Tools eine .java
-Schnittstellendatei.
benannt nach der Datei .aidl
. Die generierte Schnittstelle enthält eine Unterklasse mit dem Namen Stub
.
eine abstrakte Implementierung der übergeordneten Schnittstelle, wie z. B. YourInterface.Stub
, und deklariert alle Methoden aus der Datei .aidl
.
Hinweis: Stub
müssen auch
definiert mehrere Hilfsmethoden, insbesondere asInterface()
, die ein IBinder
-Element verwenden, das normalerweise an die onServiceConnected()
-Callback-Methode eines Clients übergeben wird, und
gibt eine Instanz der Stub-Schnittstelle zurück. Weitere Informationen zur Übertragung finden Sie im Abschnitt IPC anrufen
.
Erweitern Sie den generierten Binder
, um die aus .aidl
generierte Schnittstelle zu implementieren.
wie YourInterface.Stub
erstellen und die Methoden
von der Datei .aidl
übernommen.
Hier sehen Sie eine Beispielimplementierung einer Schnittstelle mit dem Namen IRemoteService
, die durch die vorhergehende
Beispiel für IRemoteService.aidl
mit einer anonymen Instanz:
Kotlin
private val binder = object : IRemoteService.Stub() { override fun getPid(): Int = Process.myPid() override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } }
Java
private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } };
binder
ist jetzt eine Instanz der Klasse Stub
(ein Binder
).
definiert die IPC-Schnittstelle für den Dienst. Im nächsten Schritt wird diese Instanz
damit sie mit dem Dienst interagieren können.
Beachten Sie bei der Implementierung der AIDL-Schnittstelle einige Regeln:
- Eingehende Anrufe werden nicht zwangsläufig im Hauptthread ausgeführt. Sie müssen sich also überlegen, von Anfang an über Multithreading informieren und Ihren Dienst so erstellen, dass er threadsicher ist.
- IPC-Aufrufe sind standardmäßig synchron. Wenn Sie wissen, dass der Service Millisekunden bis zum Ausführen einer Anfrage erforderlich sind, rufen Sie diese nicht aus dem Hauptthread der Aktivität auf. Dadurch kann die App hängen bleiben, sodass Android die Meldung „App reagiert nicht“ anzeigt Dialogfeld. Rufen Sie sie aus einem separaten Thread im Client auf.
- Nur die Ausnahmetypen, die in der Referenzdokumentation für
Parcel.writeException()
an den Anrufer zurückgesendet werden.
Schnittstelle für Clients verfügbar machen
Nachdem Sie die Schnittstelle für Ihren Dienst implementiert haben, müssen Sie sie für
damit sie sich daran binden können. So machen Sie die Benutzeroberfläche verfügbar:
für Ihren Dienst: Erweitern Sie Service
und implementieren Sie onBind()
, um eine Instanz Ihrer Klasse zurückzugeben, die
Den generierten Stub
, wie im vorherigen Abschnitt erläutert. Hier ein Beispiel:
, der Clients die Beispielschnittstelle IRemoteService
zur Verfügung stellt.
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. } }; }
Wenn jetzt ein Client, z. B. eine Aktivität, bindService()
aufruft, um eine Verbindung zu diesem Dienst herzustellen, empfängt der onServiceConnected()
-Callback des Clients die
binder
-Instanz, die von onBind()
des Dienstes zurückgegeben wird
.
Der Client muss auch Zugriff auf die Interface-Klasse haben. Wenn also der Kunde und der Service
separate Anwendungen erstellt haben, muss die Clientanwendung über eine Kopie der Datei .aidl
verfügen
im Verzeichnis src/
, das die Datei android.os.Binder
generiert
und dem Client Zugriff auf die AIDL-Methoden gewähren.
Wenn der Client die IBinder
empfängt
im onServiceConnected()
-Callback angegeben werden,
YourServiceInterface.Stub.asInterface(service)
, um die zurückgegebene
auf den 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; } };
Weiteren Beispielcode finden Sie in der
<ph type="x-smartling-placeholder"></ph>
RemoteService.java
Kurs in
<ph type="x-smartling-placeholder"></ph>
ApiDemos zu finden.
Objekte über IPC übergeben
In Android 10 (API-Level 29 oder höher) kannst du
Parcelable
Objekte direkt in
AIDL Typen, die als AIDL-Schnittstellenargumente und andere Attribute unterstützt werden,
unterstützt. Dadurch entfällt der zusätzliche Aufwand zum manuellen Schreiben von Marshaling-Code und
. Dadurch wird jedoch auch eine Grundstruktur erstellt. Wenn benutzerdefinierte Zugriffsfunktionen oder andere Funktionen
implementieren Sie stattdessen 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; }
Mit dem vorherigen Codebeispiel wird automatisch eine Java-Klasse mit den ganzzahligen Feldern left
,
top
, right
und bottom
. Alle relevanten Marshalling-Codes
automatisch implementiert und das Objekt kann direkt verwendet werden, ohne
Implementierung.
Sie können eine benutzerdefinierte Klasse auch über eine IPC-Schnittstelle von einem Prozess an einen anderen senden. Sie können jedoch
stellen Sie sicher, dass der Code für Ihre Klasse
auf der anderen Seite des IPC-Kanals verfügbar ist.
Ihre Klasse muss die Parcelable
-Schnittstelle unterstützen. Unterstützend
Parcelable
ist wichtig
da das Android-System damit Objekte in Primitive zerlegt, die sich dann zusammenfügen lassen.
über verschiedene Prozesse hinweg.
So erstellen Sie eine benutzerdefinierte Klasse, die Parcelable
unterstützt:
Folgendes:
- Sorgen Sie dafür, dass Ihre Klasse die
Parcelable
-Schnittstelle implementiert. - Implementieren Sie
writeToParcel
, aktuellen Status des Objekts und schreibt ihn in einParcel
-Objekt. - Fügen Sie der Klasse ein statisches Feld namens
CREATOR
hinzu, das ein Objekt ist, das derParcelable.Creator
-Schnittstelle. - Erstellen Sie abschließend eine
.aidl
-Datei, die Ihre parcelable-Klasse deklariert, wie im Folgenden gezeigt:Rect.aidl
-Datei.Wenn Sie einen benutzerdefinierten Build-Prozess verwenden, fügen Sie die Datei
.aidl
nicht zu Ihrem erstellen. Ähnlich wie eine Headerdatei in der Sprache C wird diese Datei.aidl
nicht kompiliert.
AIDL verwendet diese Methoden und Felder im generierten Code, um ein Marshalling- und Unmarshalling durchzuführen für Ihre Objekte.
Hier sehen Sie beispielsweise eine Rect.aidl
-Datei, um eine Rect
-Klasse zu erstellen, die
parcelable:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
Und hier ist ein Beispiel dafür, wie die Rect
-Klasse die
Parcelable
-Protokoll.
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; } }
Das Marshalling in der Rect
-Klasse ist unkompliziert. Schauen Sie sich die andere
auf Parcel
, um die anderen Arten von Werten zu sehen, die Sie schreiben können
zu einem Parcel
.
Warnung:Beachten Sie die Sicherheitsaspekte des Empfangs
Daten aus anderen Prozessen. In diesem Fall liest Rect
vier Zahlen aus dem Parcel
. Es liegt an Ihnen, dafür zu sorgen, dass diese Werte innerhalb des zulässigen
für alles, was der Aufrufer zu tun versucht. Weitere Informationen zum Schutz Ihrer Anwendung vor Malware finden Sie in den Sicherheitstipps.
Methoden mit Bundle-Argumenten, die Parcelables enthalten
Wenn in einer Methode einBundle
-Objekt akzeptiert wird, das die folgenden Informationen enthält:
Parcelables müssen Sie den Classloader von Bundle
festlegen, indem Sie
Bundle.setClassLoader(ClassLoader)
aufrufen, bevor versucht wird,
von Bundle
. Andernfalls tritt ClassNotFoundException
auf, obwohl das Paket in Ihrer Anwendung richtig definiert ist.
Sehen Sie sich beispielsweise die folgende .aidl
-Beispieldatei an:
// 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
explizit in Bundle
festgelegt ist, bevor Rect
gelesen wird:
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. } };
IPC-Methode aufrufen
Führen Sie die folgenden Schritte aus, um eine mit AIDL definierte Remote-Schnittstelle aufzurufen: Ihrem Anrufkurs:
- Fügen Sie die Datei
.aidl
in das Projektverzeichnissrc/
ein. - Deklarieren Sie eine Instanz der Schnittstelle
IBinder
, die basierend auf dem AIDL - Implementieren Sie
ServiceConnection
. - Rufen Sie uns unter
Context.bindService()
an. und IhreServiceConnection
-Implementierung übergeben. - Bei der Implementierung von
onServiceConnected()
erhalten Sie einIBinder
Instanz namensservice
. AnrufYourInterfaceName.Stub.asInterface((IBinder)service)
bis den zurückgegebenen Parameter in den TypYourInterface
um. - Rufen Sie die Methoden auf, die Sie in Ihrer Schnittstelle definiert haben. Immer erfassen
DeadObjectException
-Ausnahmen, die ausgelöst werden, wenn bricht die Verbindung ab. Erfassen Sie außerdemSecurityException
-Ausnahmen, die ausgelöst werden, wenn die beiden am IPC-Methodenaufruf beteiligten Prozesse widersprüchliche AIDL-Definitionen haben. - Rufen Sie zum Trennen der Verbindung
Context.unbindService()
mit der Instanz Ihrer Schnittstelle auf.
Beachten Sie beim Aufrufen eines IPC-Dienstes die folgenden Punkte:
- Objekte werden prozessübergreifend gezählt.
- Sie können anonyme Objekte als Methodenargumente.
Weitere Informationen zum Binden an einen Dienst finden Sie in der Übersicht zu gebundenen Diensten.
Hier ist Beispielcode, der den Aufruf eines von AIDL erstellten Dienstes veranschaulicht. „Remote Service“-Beispiel im Projekt „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); } } } }