Die Android Interface Definition Language (AIDL) ähnelt anderen IDLs: Sie können damit die Programmierschnittstelle definieren, auf die sich sowohl der Client als auch der Dienst einigen, um über die Inter-Process Communication (IPC) miteinander zu kommunizieren.
Unter Android kann ein Prozess normalerweise nicht auf den Arbeitsspeicher eines anderen Prozesses zugreifen. Um miteinander zu kommunizieren, müssen sie ihre Objekte in Primitive zerlegen, die das Betriebssystem verstehen kann, und die Objekte für Sie über diese Grenze hinweg verwalten. Der Code für das Marshalling ist mühsam zu schreiben, daher übernimmt Android dies für Sie mit AIDL.
Hinweis:AIDL ist nur dann erforderlich, wenn Sie Clients aus verschiedenen Anwendungen für die interprozedurale Kommunikation auf Ihren Dienst zugreifen lassen und Multithreading in Ihrem Dienst verarbeiten möchten. Wenn Sie IPC nicht gleichzeitig in verschiedenen Anwendungen ausführen müssen, erstellen Sie die Schnittstelle, indem Sie eine Binder
implementieren.
Wenn Sie IPC ausführen möchten, aber kein Multithreading benötigen, implementieren Sie Ihre Schnittstelle mit einer Messenger
.
Unabhängig davon sollten Sie sich mit gebundenen Diensten vertraut machen, bevor Sie einen AIDL implementieren.
Bevor Sie mit dem Entwerfen Ihrer AIDL-Schnittstelle beginnen, sollten Sie wissen, dass Aufrufe einer AIDL-Schnittstelle direkte Funktionsaufrufe sind. Machen Sie keine Annahmen über den Thread, in dem der Aufruf erfolgt. Was passiert, hängt davon ab, ob der Aufruf von einem Thread im lokalen Prozess oder von einem Remoteprozess stammt:
- Aufrufe, die vom lokalen Prozess ausgeführt werden, werden in demselben Thread ausgeführt, in dem der Aufruf erfolgt. Wenn dies Ihr UI-Hauptthread ist, wird dieser Thread weiterhin in der AIDL-Schnittstelle ausgeführt. Wenn es sich um einen anderen Thread handelt, führt dieser Ihren Code in dem Dienst aus. Wenn also nur lokale Threads auf den Dienst zugreifen, können Sie vollständig steuern, welche Threads darin ausgeführt werden. Verwenden Sie in diesem Fall jedoch keine AIDL. Erstellen Sie stattdessen die Benutzeroberfläche, indem Sie eine
Binder
implementieren. - Aufrufe von einem Remoteprozess werden von einem Threadpool gesendet, den die Plattform in Ihrem eigenen Prozess verwaltet. Seien Sie auf eingehende Anrufe von unbekannten Threads vorbereitet, wobei mehrere Anrufe gleichzeitig stattfinden können. Mit anderen Worten: Eine Implementierung einer AIDL-Schnittstelle muss vollständig threadsicher sein. Aufrufe, die von einem Thread auf dasselbe Remote-Objekt erfolgen, kommen am Empfängerende in der richtigen Reihenfolge an.
- Mit dem Schlüsselwort
oneway
wird das Verhalten von Remoteaufrufen geändert. Wenn sie verwendet wird, wird ein Remoteaufruf nicht blockiert. Es sendet die Transaktionsdaten und kehrt sofort zurück. Die Implementierung der Schnittstelle empfängt dies schließlich als regulären Aufruf vomBinder
-Threadpool als normalen Remoteaufruf. Wennoneway
mit einem lokalen Aufruf verwendet wird, hat das keine Auswirkungen und der Aufruf ist weiterhin synchron.
AIDL-Schnittstelle definieren
Definieren Sie die AIDL-Schnittstelle in einer .aidl
-Datei mit der Syntax der Java-Programmiersprache und speichern Sie sie im Quellcode im src/
-Verzeichnis sowohl der Anwendung, die den Dienst hostet, als auch jeder anderen Anwendung, die mit dem Dienst verbunden ist.
Wenn Sie jede Anwendung erstellen, die die Datei .aidl
enthält, generieren die Android SDK-Tools anhand der Datei .aidl
eine IBinder
-Schnittstelle und speichern sie im Verzeichnis gen/
des Projekts. Der Dienst muss die IBinder
-Schnittstelle entsprechend implementieren. Die Clientanwendungen können dann an den Dienst binden und Methoden aus der IBinder
aufrufen, um die interprozedurale Kommunikation auszuführen.
So erstellen Sie einen begrenzten Dienst mit AIDL:
.aidl
-Datei erstellenIn dieser Datei wird die Programmierschnittstelle mit Methodensignaturen definiert.
- Schnittstelle implementieren
Die Android SDK-Tools generieren anhand Ihrer
.aidl
-Datei eine Schnittstelle in der Programmiersprache Java. Diese Schnittstelle hat eine innere abstrakte Klasse namensStub
, dieBinder
erweitert und Methoden aus Ihrer AIDL-Schnittstelle implementiert. Sie müssen die KlasseStub
erweitern und die Methoden implementieren. - Schnittstelle für Clients verfügbar machen
Implementieren Sie eine
Service
und überschreiben SieonBind()
, um Ihre Implementierung der KlasseStub
zurückzugeben.
Achtung:Alle Änderungen, die Sie nach Ihrem ersten Release an Ihrer AIDL-Schnittstelle vornehmen, müssen abwärtskompatibel bleiben, damit andere Anwendungen, die Ihren Dienst nutzen, nicht beeinträchtigt werden. Da die Datei .aidl
in andere Anwendungen kopiert werden muss, damit diese auf die Schnittstelle Ihres Dienstes zugreifen können, müssen Sie weiterhin die Unterstützung für die ursprüngliche Oberfläche beibehalten.
.aidl-Datei erstellen
AIDL verwendet eine einfache Syntax, mit der Sie eine Schnittstelle mit einer oder mehreren Methoden deklarieren können, die Parameter verwenden und Werte zurückgeben. Die Parameter und Rückgabewerte können beliebigen Typen haben, auch andere von AIDL generierte Schnittstellen.
Sie müssen die .aidl
-Datei mit der Programmiersprache Java erstellen. Jede .aidl
-Datei muss eine einzelne Schnittstelle definieren und erfordert nur die Schnittstellendeklaration und die Methodensignaturen.
Standardmäßig werden in AIDL die folgenden Datentypen unterstützt:
- Alle primitiven Typen in der Programmiersprache Java (z. B.
int
,long
,char
,boolean
usw.) - Arrays beliebiger Typen, z. B.
int[]
oderMyParcelable[]
String
CharSequence
List
Alle Elemente in
List
müssen zu den unterstützten Datentypen in dieser Liste oder zu einer der anderen von Ihnen deklarierten AIDL-generierten Schnittstellen oder Parcelables gehören. EinList
kann optional als parametrisierte Typklasse verwendet werden, z. B.List<String>
. Die tatsächliche konkrete Klasse, die die andere Seite empfängt, ist immer eineArrayList
, obwohl die Methode für die Verwendung derList
-Schnittstelle generiert wird.Map
Alle Elemente in
Map
müssen zu den unterstützten Datentypen in dieser Liste oder zu einer der anderen von Ihnen deklarierten AIDL-generierten Schnittstellen oder Parcelables gehören. Parametrisierte Typzuordnungen wieMap<String,Integer>
werden nicht unterstützt. Die tatsächliche konkrete Klasse, die die andere Seite empfängt, ist immer eineHashMap
, obwohl die Methode für die Verwendung derMap
-Schnittstelle generiert wird. Verwenden Sie stattdessen einenBundle
.Map
Sie müssen für jeden zusätzlichen Typ, der nicht oben aufgeführt ist, eine import
-Anweisung angeben, auch wenn sie im selben Paket wie Ihre Benutzeroberfläche definiert sind.
Beachten Sie beim Definieren der Dienstschnittstelle Folgendes:
- Methoden können null oder mehr Parameter haben und einen Wert oder „void“ 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-generierte Schnittstellen sind standardmäßigin
und können nicht verwendet werden.Achtung:Begrenzen Sie die Richtung auf das, was wirklich erforderlich ist, da das Marshalling von Parametern teuer ist.
- Alle Codekommentare in der
.aidl
-Datei sind in der generiertenIBinder
-Benutzeroberfläche enthalten, mit Ausnahme von Kommentaren vor den Import- und Paketanweisungen. - String- und Ganzzahl-Konstanten können in der AIDL-Schnittstelle definiert werden, z. B.
const int VERSION = 1;
. - Methodenaufrufe werden durch einen
transact()
-Code geleitet, der normalerweise auf einem Methodenindex in der Benutzeroberfläche basiert. Da dies die Versionierung erschwert, können Sie den Transaktionscode einer Methode manuell zuweisen:void method() = 10;
. - Argumente und Rückgabetypen, die null sein können, müssen mit
@nullable
annotiert werden.
Hier sehen Sie 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 .aidl
-Datei im src/
-Verzeichnis Ihres Projekts. Wenn Sie Ihre Anwendung erstellen, generieren die SDK-Tools die Schnittstellendatei IBinder
im Verzeichnis gen/
Ihres Projekts. Der Name der generierten Datei stimmt mit dem Namen der .aidl
-Datei überein, hat aber die Erweiterung .java
. Beispiel: IRemoteService.aidl
ergibt 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 beim nächsten Erstellen Ihrer Anwendung. Erstellen Sie Ihr Projekt mit gradle assembleDebug
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 Sie Ihre App erstellen, generieren die Android SDK-Tools eine .java
-Schnittstellendatei, die nach der Datei .aidl
benannt ist. Die generierte Schnittstelle enthält eine Unterklasse namens Stub
, die eine abstrakte Implementierung der übergeordneten Schnittstelle ist, z. B. YourInterface.Stub
, und alle Methoden aus der Datei .aidl
deklariert.
Hinweis:Stub
definiert auch einige Hilfsmethoden, insbesondere asInterface()
. Dieser verwendet ein IBinder
-Objekt, in der Regel die Methode, die an die Callback-Methode onServiceConnected()
eines Clients übergeben wird, und gibt eine Instanz der Stub-Schnittstelle zurück. Weitere Informationen dazu finden Sie im Abschnitt IPC-Methode aufrufen.
Wenn Sie die aus der .aidl
generierte Schnittstelle implementieren möchten, erweitern Sie die generierte Binder
-Schnittstelle, z. B. YourInterface.Stub
, und implementieren Sie die aus der .aidl
-Datei übernommenen Methoden.
Hier sehen Sie eine Beispielimplementierung einer Schnittstelle mit dem Namen IRemoteService
, die durch das vorherige IRemoteService.aidl
-Beispiel definiert wurde, 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. } };
Jetzt ist binder
eine Instanz der Klasse Stub
(eine Binder
), die die IPC-Schnittstelle für den Dienst definiert. Im nächsten Schritt wird diese Instanz für Clients freigegeben, damit sie mit dem Dienst interagieren können.
Beachten Sie bei der Implementierung Ihrer AIDL-Schnittstelle einige Regeln:
- Eingehende Aufrufe werden nicht garantiert im Hauptthread ausgeführt. Sie müssen also von Anfang an an Multithreading denken und Ihren Dienst so entwickeln, dass er threadsicher ist.
- IPC-Aufrufe sind standardmäßig synchron. Wenn Sie wissen, dass der Dienst mehr als ein paar Millisekunden für die Verarbeitung einer Anfrage benötigt, rufen Sie ihn nicht aus dem Hauptthread der Aktivität auf. Dies kann dazu führen, dass die Anwendung hängt und Android das Dialogfeld „Die Anwendung reagiert nicht“ anzeigt. Rufen Sie sie über einen separaten Thread im Client auf.
- Nur die in der Referenzdokumentation für
Parcel.writeException()
aufgeführten Ausnahmetypen werden an den Aufrufer zurückgesendet.
Schnittstelle für Clients verfügbar machen
Nachdem Sie die Schnittstelle für Ihren Dienst implementiert haben, müssen Sie sie für Clients verfügbar machen, damit sie eine Bindung daran vornehmen können. Wenn Sie die Schnittstelle für Ihren Dienst freigeben möchten, erweitern Sie Service
und implementieren Sie onBind()
, um eine Instanz Ihrer Klasse zurückzugeben, die die generierte Stub
implementiert, wie im vorherigen Abschnitt erläutert. Hier ist ein Beispiel für einen Dienst, der die IRemoteService
-Beispieloberfläche für Clients verfügbar macht.
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 ein Client, z. B. eine Aktivität, bindService()
aufruft, um eine Verbindung zu diesem Dienst herzustellen, erhält der onServiceConnected()
-Rückruf des Clients die binder
-Instanz, die von der onBind()
-Methode des Dienstes zurückgegeben wird.
Der Client muss auch Zugriff auf die Benutzeroberflächenklasse haben. Wenn sich Client und Dienst also in separaten Anwendungen befinden, muss die Anwendung des Clients eine Kopie der .aidl
-Datei im src/
-Verzeichnis haben. Diese generiert die android.os.Binder
-Schnittstelle, die dem Client Zugriff auf die AIDL-Methoden gewährt.
Wenn der Client IBinder
im onServiceConnected()
-Callback empfängt, muss er YourServiceInterface.Stub.asInterface(service)
aufrufen, um den zurückgegebenen Parameter in den Typ YourServiceInterface
zu konvertieren:
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; } };
Weitere Beispielcode finden Sie in der Klasse
RemoteService.java
in
ApiDemos.
Objekte über IPC übergeben
In Android 10 (API-Level 29 oder höher) können Sie Parcelable
-Objekte direkt in AIDL definieren. Auch Typen, die als AIDL-Schnittstellenargumente und andere Parcelable-Objekte unterstützt werden, sind hier zulässig. Dadurch entfällt der zusätzliche Aufwand zum manuellen Schreiben von Marshallingcode und einer benutzerdefinierten Klasse. Dadurch wird jedoch auch ein einfacher Typ erstellt. Wenn benutzerdefinierte Zugriffsmethoden oder andere Funktionen erforderlich sind, 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; }
Im vorherigen Codebeispiel wird automatisch eine Java-Klasse mit den Ganzzahlfeldern left
, top
, right
und bottom
generiert. Der gesamte relevante Marshalling-Code wird automatisch implementiert und das Objekt kann direkt verwendet werden, ohne dass eine Implementierung hinzugefügt werden muss.
Sie können eine benutzerdefinierte Klasse auch über eine IPC-Schnittstelle von einem Prozess an einen anderen senden. Der Code für Ihre Klasse muss jedoch für die andere Seite des IPC-Kanals verfügbar sein und Ihre Klasse muss die Parcelable
-Schnittstelle unterstützen. Die Unterstützung von Parcelable
ist wichtig, da das Android-System Objekte in Primitive zerlegen kann, die über Prozesse hinweg gemarshallt werden können.
So erstellen Sie eine benutzerdefinierte Klasse, die Parcelable
unterstützt:
- Sorgen Sie dafür, dass Ihre Klasse die
Parcelable
-Schnittstelle implementiert. - Implementieren Sie
writeToParcel
, das den aktuellen Status des Objekts in einParcel
schreibt. - Fügen Sie Ihrer Klasse ein statisches Feld namens
CREATOR
hinzu, das ein Objekt ist, das die SchnittstelleParcelable.Creator
implementiert. - Erstellen Sie abschließend eine
.aidl
-Datei, in der Sie Ihre Parcelable-Klasse deklarieren, wie in der folgendenRect.aidl
-Datei gezeigt.Wenn Sie einen benutzerdefinierten Build-Prozess verwenden, fügen Sie dem Build nicht die Datei
.aidl
hinzu. Ähnlich wie eine Header-Datei in der C-Programmiersprache wird diese.aidl
-Datei nicht kompiliert.
AIDL verwendet diese Methoden und Felder im generierten Code, um Ihre Objekte zu marshallen und zu unmarshallen.
Hier ist beispielsweise eine Rect.aidl
-Datei zum Erstellen einer parzellierbaren Rect
-Klasse:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
Hier ist ein Beispiel dafür, wie die Klasse Rect
das Parcelable
-Protokoll implementiert.
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 Klasse Rect
ist unkompliziert. Sehen Sie sich die anderen Methoden für Parcel
an, um zu erfahren, welche anderen Arten von Werten Sie in ein Parcel
schreiben können.
Warnung:Denken Sie an die Sicherheitsrisiken, die mit dem Empfang von Daten aus anderen Prozessen verbunden sind. In diesem Fall liest der Rect
vier Ziffern aus dem Parcel
. Sie müssen jedoch dafür sorgen, dass diese Werte innerhalb des zulässigen Wertebereichs für die Aktionen des Aufrufers liegen. Weitere Informationen dazu, wie Sie Ihre Anwendung vor Malware schützen, finden Sie unter Sicherheitstipps.
Methoden mit Bundle-Argumenten, die Parcelables enthalten
Wenn eine Methode einBundle
-Objekt akzeptiert, das Pakete enthalten soll, müssen Sie den Classloader von Bundle
festlegen. Rufen Sie dazu Bundle.setClassLoader(ClassLoader)
auf, bevor Sie versuchen, aus Bundle
zu lesen. Andernfalls tritt ClassNotFoundException
auf, obwohl das Parcelable in Ihrer Anwendung korrekt definiert ist.
Sehen Sie sich beispielsweise die folgende Beispieldatei .aidl
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, 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
Wenn Sie eine mit AIDL definierte Remote-Schnittstelle aufrufen möchten, gehen Sie in Ihrer aufrufenden Klasse so vor:
- Fügen Sie die Datei
.aidl
in das Projektverzeichnissrc/
ein. - Deklarieren Sie eine Instanz der Schnittstelle
IBinder
, die basierend auf AIDL generiert wird. ServiceConnection
implementieren.- Rufen Sie
Context.bindService()
auf und übergeben Sie IhreServiceConnection
-Implementierung. - In Ihrer Implementierung von
onServiceConnected()
erhalten Sie eineIBinder
-Instanz namensservice
. Rufen SieYourInterfaceName.Stub.asInterface((IBinder)service)
auf, um den zurückgegebenen Parameter in den TypYourInterface
zu konvertieren. - Rufen Sie die Methoden auf, die Sie in Ihrer Benutzeroberfläche definiert haben. Erfassen Sie
DeadObjectException
-Ausnahmen immer, wenn die Verbindung unterbrochen wird. Außerdem müssen SieSecurityException
-Ausnahmen abfangen, die auftreten, wenn die beiden Prozesse, die am IPC-Methodenaufruf beteiligt sind, in Konflikt stehende AIDL-Definitionen haben. - Wenn Sie die Verbindung trennen möchten, rufen Sie
Context.unbindService()
mit der Instanz Ihrer Benutzeroberfläche auf.
Beachten Sie beim Aufruf eines IPC-Dienstes Folgendes:
- Objekte werden prozessübergreifend referenziert.
- Sie können anonyme Objekte als Methodeinträge senden.
Weitere Informationen zur Bindung an einen Dienst finden Sie in der Übersicht über gebundene Dienste.
Im Folgenden finden Sie Beispielcode zum Aufrufen eines mit AIDL erstellten Dienstes aus dem Beispiel für Remote-Dienste im ApiDemos-Projekt.
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); } } } }