Android Interface Definition Language (AIDL)

Die Android Interface Definition Language (AIDL) ähnelt anderen IDLs: Sie können damit die Programmierschnittstelle definieren, auf die sich der Client und der Dienst einigen, um über Interprocess Communication (IPC) miteinander zu kommunizieren.

Unter Android kann ein Prozess normalerweise nicht auf den Arbeitsspeicher eines anderen Prozesses zugreifen. Dazu müssen sie ihre Objekte in Primitive zerlegen, die das Betriebssystem verstehen kann, und die Objekte für Sie über diese Grenze hinweg bringen. Der Code für dieses Marshalling ist mühsam zu schreiben, sodass Android ihn mit AIDL für Sie übernimmt.

Hinweis:AIDL ist nur erforderlich, wenn Sie Clients aus verschiedenen Anwendungen für IPC den Zugriff auf Ihren Dienst erlauben und Multithreading in Ihrem Dienst verarbeiten möchten. Wenn Sie nicht gleichzeitig IPC über verschiedene Anwendungen hinweg ausführen müssen, erstellen Sie die Schnittstelle durch Implementieren einer Binder. Wenn Sie IPC ausführen möchten, aber kein Multithreading benötigen, implementieren Sie Ihre Schnittstelle mit einem Messenger. Unabhängig davon sollten Sie sich mit den gebundenen Diensten vertraut machen, bevor Sie einen AIDL implementieren.

Beachten Sie vor dem Entwerfen der AIDL-Schnittstelle, 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 Remote-Prozess stammt:

  • Aufrufe aus dem lokalen Prozess werden im selben Thread ausgeführt, in dem der Aufruf erfolgt. Wenn dies Ihr Haupt-UI-Thread ist, wird dieser Thread weiterhin in der AIDL-Schnittstelle ausgeführt. Wenn es sich um einen anderen Thread handelt, führt dieser Ihren Code im 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 überhaupt kein AIDL. Erstellen Sie stattdessen die Schnittstelle durch Implementieren einer Binder.
  • Aufrufe von einem Remote-Prozess werden aus einem Thread-Pool weitergeleitet, der von der Plattform innerhalb Ihres eigenen Prozesses verwaltet wird. Seien Sie auf eingehende Anrufe von unbekannten Threads vorbereitet, auf die mehrere Anrufe gleichzeitig eingehen. Mit anderen Worten: Die Implementierung einer AIDL-Schnittstelle muss absolut Thread-sicher sein. Aufrufe, die von einem Thread auf demselben Remote-Objekt ausgeführt werden, gehen in der richtigen Reihenfolge auf Empfängerseite ein.
  • Mit dem Schlüsselwort oneway wird das Verhalten von Remote-Aufrufen geändert. Ein Remote-Aufruf wird dabei nicht blockiert. Es sendet die Transaktionsdaten und kehrt sofort zurück. Die Implementierung der Schnittstelle empfängt dies schließlich als regulären Aufruf aus dem Thread-Pool Binder als normalen Remote-Aufruf. Wenn oneway mit einem lokalen Aufruf verwendet wird, gibt es keine Auswirkungen und der Aufruf ist weiterhin synchron.

AIDL-Schnittstelle definieren

Definieren Sie die AIDL-Schnittstelle in einer .aidl-Datei mithilfe der Java-Syntax für Programmiersprachen und speichern Sie sie dann im Quellcode im Verzeichnis src/ sowohl der Anwendung, die den Dienst hostet, als auch jeder anderen Anwendung, die an den Dienst gebunden wird.

Wenn Sie die einzelnen Anwendungen erstellen, die die Datei .aidl enthalten, generieren die Android SDK-Tools basierend auf der Datei .aidl eine IBinder-Schnittstelle und speichern sie im Verzeichnis gen/ des Projekts. Der Dienst muss die Schnittstelle IBinder entsprechend implementieren. Die Clientanwendungen können dann eine Bindung an den Dienst herstellen und Methoden aus dem IBinder aufrufen, um IPC auszuführen.

Führen Sie die folgenden Schritte aus, die in den folgenden Abschnitten beschrieben werden, um einen begrenzten Dienst mit AIDL zu erstellen:

  1. Datei .aidl erstellen

    Diese Datei definiert die Programmierschnittstelle mit Methodensignaturen.

  2. Schnittstelle implementieren

    Die Android SDK-Tools generieren basierend auf der Datei .aidl 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 freigeben

    Implementieren Sie Service und überschreiben Sie onBind(), um Ihre Implementierung der Stub-Klasse zurückzugeben.

Achtung:Alle Änderungen, die Sie nach dem ersten Release an der AIDL-Schnittstelle vornehmen, müssen abwärtskompatibel bleiben, damit andere Anwendungen, die Ihren Dienst verwenden, nicht beeinträchtigt werden. Das heißt, die Datei .aidl muss in andere Anwendungen kopiert werden, damit diese auf die Schnittstelle des Dienstes zugreifen können, und muss die ursprüngliche Schnittstelle weiterhin unterstützen.

Datei „.aidl“ erstellen

AIDL verwendet eine einfache Syntax, mit der Sie eine Schnittstelle mit einer oder mehreren Methoden deklarieren können, die Parameter annehmen und Werte zurückgeben. Die Parameter und Rückgabewerte können von einem beliebigen Typ sein, auch von anderen AIDL-generierten Schnittstellen.

Die Datei .aidl muss mit der Programmiersprache Java erstellt werden. In jeder .aidl-Datei muss eine einzelne Schnittstelle definiert werden. Es sind nur die Schnittstellendeklaration und Methodensignaturen erforderlich.

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 List müssen einen der unterstützten Datentypen in dieser Liste oder einer der anderen AIDL-generierten Schnittstellen oder Parcelables sein, die Sie deklarieren. Ein List kann optional als parametrisierte Typklasse verwendet werden, z. B. List<String>. Die tatsächliche konkrete Klasse, die die andere Seite erhält, ist immer ein ArrayList, obwohl die Methode zur Verwendung der List-Schnittstelle generiert wird.

  • Map

    Alle Elemente in Map müssen einen der unterstützten Datentypen in dieser Liste oder einer der anderen AIDL-generierten Schnittstellen oder Parcelables sein, die Sie deklarieren. Parametrisierte Typzuordnungen, z. B. im Format Map<String,Integer>, werden nicht unterstützt. Die tatsächliche konkrete Klasse, die die andere Seite erhält, ist immer ein HashMap, obwohl die Methode zur Verwendung der Map-Schnittstelle generiert wird. Als Alternative zu Map können Sie Bundle verwenden.

Sie müssen für jeden zusätzlichen Typ, der zuvor nicht aufgeführt ist, eine import-Anweisung hinzufügen, auch wenn sie im selben Paket wie Ihre Schnittstelle definiert sind.

Beachten Sie beim Definieren der Dienstoberfläche Folgendes:

  • Methoden können null oder mehr Parameter annehmen und einen Wert oder einen leeren Wert zurückgeben.
  • Alle nicht-primitiven Parameter erfordern ein direktionales Tag, das die Richtung der Daten angibt: in, out oder inout (siehe Beispiel unten).

    Primitive, String, IBinder und von AIDL generierte Schnittstellen sind standardmäßig in und können nicht anderweitig verwendet werden.

    Achtung:Begrenzen Sie die Richtung auf das, was wirklich erforderlich ist, da Marschalling-Parameter teuer sind.

  • Alle Codekommentare in der Datei .aidl sind in der generierten IBinder-Oberfläche enthalten, mit Ausnahme der Kommentare vor den Import- und Paketanweisungen.
  • String- und Ganzzahlkonstanten können in der AIDL-Schnittstelle definiert werden, z. B. const int VERSION = 1;.
  • Methodenaufrufe werden von einem transact()-Code weitergeleitet, der normalerweise auf einem Methodenindex in der Schnittstelle basiert. Da dies die Versionsverwaltung erschwert, können Sie 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 ein Beispiel für eine .aidl-Datei:

// 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 Ihre Anwendung erstellen, generieren die SDK-Tools die IBinder-Schnittstellendatei im Verzeichnis gen/ Ihres Projekts. Der Name der generierten Datei entspricht dem Namen der .aidl-Datei, hat aber die Endung .java. Beispiel: IRemoteService.aidl führt zu IRemoteService.java.

Wenn Sie Android Studio verwenden, generiert der inkrementelle Build die Binder-Klasse fast sofort. Wenn Sie Android Studio nicht verwenden, generiert das Gradle-Tool die Binder-Klasse, wenn Sie Ihre Anwendung das nächste Mal erstellen. 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 Ihrer .aidl-Datei benannt ist. Die generierte Schnittstelle enthält eine Unterklasse namens Stub, die eine abstrakte Implementierung der übergeordneten Schnittstelle ist, z. B. YourInterface.Stub, und deklariert alle Methoden aus der Datei .aidl.

Hinweis:Stub definiert auch einige Hilfsmethoden, insbesondere asInterface(). Dabei wird ein IBinder verwendet, das normalerweise an die Callback-Methode onServiceConnected() eines Clients übergeben wird, und eine Instanz der Stub-Schnittstelle zurückgeben. Weitere Informationen zum Umwandeln finden Sie unter IPC-Methode aufrufen.

Um die von .aidl generierte Schnittstelle zu implementieren, erweitern Sie die generierte Binder-Schnittstelle, z. B. YourInterface.Stub, und implementieren Sie die Methoden, die aus der Datei .aidl übernommen wurden.

Hier sehen Sie eine Beispielimplementierung einer Schnittstelle mit dem Namen IRemoteService, die durch das vorherige IRemoteService.aidl-Beispiel definiert wurde. Dabei wird eine anonyme Instanz verwendet:

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 Stub-Klasse (Binder), die die IPC-Schnittstelle für den Dienst definiert. Im nächsten Schritt wird diese Instanz für Clients freigegeben, damit diese mit dem Dienst interagieren können.

Beachten Sie bei der Implementierung Ihrer AIDL-Schnittstelle einige Regeln:

  • Eingehende Aufrufe werden nicht zwangsläufig im Hauptthread ausgeführt. Sie müssen also von Anfang an über Multithreading nachdenken und Ihren Dienst ordnungsgemäß so gestalten, dass er Thread-sicher ist.
  • Standardmäßig sind IPC-Aufrufe synchron. Wenn Sie wissen, dass der Dienst mehr als ein paar Millisekunden benötigt, um eine Anfrage auszuführen, rufen Sie ihn nicht aus dem Hauptthread der Aktivität auf. Die Anwendung hängt möglicherweise ab, was dazu führt, dass Android das Dialogfeld „Anwendung reagiert nicht“ anzeigt. Rufen Sie es aus einem separaten Thread im Client auf.
  • Nur die Ausnahmetypen, die in der Referenzdokumentation zu Parcel.writeException() aufgeführt sind, werden an den Aufrufer zurückgesendet.

Schnittstelle für Clients freigeben

Nachdem Sie die Schnittstelle für Ihren Dienst implementiert haben, müssen Sie sie für Clients verfügbar machen, damit diese eine Bindung vornehmen können. Erweitern Sie Service und implementieren Sie onBind(), um eine Instanz Ihrer Klasse zurückzugeben, die die generierte Stub implementiert, wie im vorherigen Abschnitt beschrieben, um die Schnittstelle für Ihren Dienst verfügbar zu machen. Hier ist ein Beispieldienst, 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 der Methode onBind() des Dienstes zurückgegeben wird.

Der Client muss auch Zugriff auf die Schnittstellenklasse haben. Wenn sich also der Client und der Dienst in separaten Anwendungen befinden, muss die Clientanwendung im Verzeichnis src/ eine Kopie der Datei .aidl haben. Dadurch wird die Schnittstelle android.os.Binder generiert und der Client erhält Zugriff auf die AIDL-Methoden.

Wenn der Client den IBinder im onServiceConnected()-Callback empfängt, muss er YourServiceInterface.Stub.asInterface(service) aufrufen, um den zurückgegebenen Parameter in den Typ YourServiceInterface umzuwandeln:

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 findest du 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. Typen, die als AIDL-Schnittstellenargumente und andere Parcelables unterstützt werden, werden hier ebenfalls unterstützt. Dadurch entfällt der zusätzliche Aufwand, manuell Marshalling-Code und eine benutzerdefinierte Klasse zu schreiben. Dadurch wird jedoch auch eine reine Struktur erstellt. Wenn Sie benutzerdefinierte Zugriffsmethoden oder andere Funktionen wünschen, 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;
}

Das vorherige Codebeispiel generiert automatisch eine Java-Klasse mit den ganzzahligen Feldern left, top, right und bottom. Der gesamte relevante Marschalling-Code wird automatisch implementiert und das Objekt kann direkt verwendet werden, ohne dass eine Implementierung hinzugefügt werden muss.

Sie können auch eine benutzerdefinierte Klasse über eine IPC-Schnittstelle von einem Prozess an einen anderen senden. Achten Sie jedoch darauf, dass der Code für Ihre Klasse auf der anderen Seite des IPC-Kanals verfügbar ist und Ihre Klasse die Schnittstelle Parcelable unterstützen muss. Die Unterstützung von Parcelable ist wichtig, da das Android-System damit Objekte in Primitiven zerlegen kann, die über Prozesse hinweg marshalliert 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. Dadurch wird der aktuelle Status des Objekts in ein Parcel-Objekt geschrieben.
  3. Fügen Sie Ihrer Klasse ein statisches Feld namens CREATOR hinzu. Es ist ein Objekt, das die Parcelable.Creator-Schnittstelle implementiert.
  4. Erstellen Sie schließlich eine .aidl-Datei, in der Ihre Paketklasse deklariert wird, wie in der folgenden Rect.aidl-Datei gezeigt.

    Wenn Sie einen benutzerdefinierten Build-Prozess verwenden, fügen Sie Ihrem Build die Datei .aidl nicht hinzu. Ähnlich wie eine Headerdatei in der Sprache C ist diese Datei .aidl nicht kompiliert.

AIDL verwendet diese Methoden und Felder in dem generierten Code, um ein Marshalling für Ihre Objekte durchzuführen oder ein Unmarshalling durchzuführen.

Hier sehen Sie beispielsweise eine Rect.aidl-Datei zum Erstellen einer Rect-Klasse, die parzelable ist:

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 Protokoll Parcelable 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 Rect-Klasse 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:Beachten Sie die Auswirkungen auf die Sicherheit, wenn Daten von anderen Prozessen empfangen werden. In diesem Fall liest Rect vier Zahlen aus dem Parcel. Sie müssen jedoch dafür sorgen, dass diese im akzeptablen Wertebereich für das, was der Aufrufer tun möchte, liegen. Weitere Informationen zum Schutz Ihrer Anwendung vor Malware finden Sie unter Sicherheitstipps.

Methoden mit Bundle-Argumenten, die Parcelables enthalten

Wenn eine Methode ein Bundle-Objekt akzeptiert, das Parcelables enthalten soll, müssen Sie den Classloader von Bundle festlegen. Dazu rufen Sie Bundle.setClassLoader(ClassLoader) auf, bevor Sie versuchen, aus Bundle zu lesen. Andernfalls wird ClassNotFoundException angezeigt, obwohl die Paketerstellung in Ihrer Anwendung richtig definiert ist.

Betrachten Sie beispielsweise die folgende Beispieldatei .aidl:

// IRectInsideBundle.aidl
package com.example.android;

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect". */
    void saveRect(in Bundle bundle);
}
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

Führen Sie die folgenden Schritte in Ihrer aufrufenden Klasse aus, um eine mit AIDL definierte Remote-Schnittstelle aufzurufen:

  1. Fügen Sie die Datei .aidl in das Projektverzeichnis src/ ein.
  2. Deklarieren Sie eine Instanz der IBinder-Schnittstelle, die anhand der AIDL generiert wird.
  3. ServiceConnection implementieren.
  4. Rufen Sie Context.bindService() auf und übergeben Sie Ihre ServiceConnection-Implementierung.
  5. In der 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 umzuwandeln.
  6. Rufen Sie die Methoden auf, die Sie auf Ihrer Schnittstelle definiert haben. DeadObjectException-Ausnahmen werden immer erfasst. Diese werden ausgelöst, wenn die Verbindung unterbrochen wird. Außerdem werden SecurityException-Ausnahmen abgefangen. Diese werden ausgelöst, wenn die beiden am IPC-Methodenaufruf beteiligten Prozesse widersprüchliche AIDL-Definitionen haben.
  7. 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 senden.

Weitere Informationen zur Bindung an einen Dienst finden Sie in der Übersicht über gebundene Dienste.

Der folgende Beispielcode zeigt den Aufruf eines mit AIDL erstellten Dienstes aus dem Remote-Dienst-Beispiel 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);
            }
        }
    }
}