Android Interface Definition Language (AIDL)

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 vom Binder-Threadpool als normalen Remoteaufruf. Wenn oneway 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:

  1. .aidl-Datei erstellen

    In dieser Datei wird die Programmierschnittstelle mit Methodensignaturen definiert.

  2. Schnittstelle implementieren

    Die Android SDK-Tools generieren anhand Ihrer .aidl-Datei eine Schnittstelle in der Programmiersprache Java. Diese Schnittstelle hat eine innere abstrakte Klasse namens Stub, die Binder erweitert und Methoden aus Ihrer AIDL-Schnittstelle implementiert. Sie müssen die Klasse Stub erweitern und die Methoden implementieren.

  3. Schnittstelle für Clients verfügbar machen

    Implementieren Sie eine Service und überschreiben Sie onBind(), um Ihre Implementierung der Klasse Stub 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[] oder MyParcelable[]
  • 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. Ein List kann optional als parametrisierte Typklasse verwendet werden, z. B. List<String>. Die tatsächliche konkrete Klasse, die die andere Seite empfängt, ist immer eine ArrayList, obwohl die Methode für die Verwendung der List-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 wie Map<String,Integer> werden nicht unterstützt. Die tatsächliche konkrete Klasse, die die andere Seite empfängt, ist immer eine HashMap, obwohl die Methode für die Verwendung der Map-Schnittstelle generiert wird. Verwenden Sie stattdessen einen Bundle.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 oder inout (siehe Beispiel unten).

    Primitives, String-, IBinder- und AIDL-generierte Schnittstellen sind standardmäßig in 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 generierten IBinder-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:

  1. Sorgen Sie dafür, dass Ihre Klasse die Parcelable-Schnittstelle implementiert.
  2. Implementieren Sie writeToParcel, das den aktuellen Status des Objekts in ein Parcel schreibt.
  3. Fügen Sie Ihrer Klasse ein statisches Feld namens CREATOR hinzu, das ein Objekt ist, das die Schnittstelle Parcelable.Creator implementiert.
  4. Erstellen Sie abschließend eine .aidl-Datei, in der Sie Ihre Parcelable-Klasse deklarieren, wie in der folgenden Rect.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 ein Bundle-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);
}
Wie in der folgenden Implementierung gezeigt, wird 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:

  1. Fügen Sie die Datei .aidl in das Projektverzeichnis src/ ein.
  2. Deklarieren Sie eine Instanz der Schnittstelle IBinder, die basierend auf AIDL generiert wird.
  3. ServiceConnection implementieren.
  4. Rufen Sie Context.bindService() auf und übergeben Sie Ihre ServiceConnection-Implementierung.
  5. In Ihrer Implementierung von onServiceConnected() erhalten Sie eine IBinder-Instanz namens service. Rufen Sie YourInterfaceName.Stub.asInterface((IBinder)service) auf, um den zurückgegebenen Parameter in den Typ YourInterface zu konvertieren.
  6. 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 Sie SecurityException-Ausnahmen abfangen, die auftreten, wenn die beiden Prozesse, die am IPC-Methodenaufruf beteiligt sind, in Konflikt stehende AIDL-Definitionen haben.
  7. 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&mdash;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&mdash;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);
            }
        }
    }
}