Android Interface Definition Language (AIDL)

שפת ההגדרה של Android Interface (AIDL) דומה לניסוח אחר IDL: הוא מאפשר להגדיר את ממשק התכנות הלקוח והשירות מסכימים זה עם זה כדי לתקשר זה עם זה באמצעות תקשורת בין תהליכים (IPC).

ב-Android, בדרך כלל תהליך אחד לא יכול לגשת אל זיכרון של תהליך אחר. כדי לדבר, הם צריכים לפרק את האובייקטים שלהם לפרימיטיביות מערכת ההפעלה יכולה להבין ולתכנן את האובייקטים שחוצים את הגבולות האלה עבורכם. הקוד כדי קשה לכתוב את הקוד הזה, ו-Android מטפל בו בשבילכם בעזרת AIDL.

הערה: צריך להשתמש ב-AIDL רק אם מאפשרים ללקוחות: אפליקציות שונות ניגשות לשירות ל-IPC ואתם רוצים לטפל בריבוי שרשורים לאחר השיפור. אם אתם לא צריכים לבצע IPC בו-זמנית שונות, ליצור את הממשק באמצעות הטמעה Binder אם אתם רוצים לבצע IPC אבל לא צריך לטפל בריבוי שרשורים, להטמיע את הממשק באמצעות Messenger. בכל מקרה, חשוב להבין לפני כן את השירותים המקושרים להטמיע AIDL.

לפני שמתחילים לעצב את ממשק AIDL, חשוב לזכור שקריאות לממשק AIDL קריאות לפונקציה ישירה. אל תניחו הנחות לגבי השרשור שבו השיחה מתרחשת. מה קורה אם השיחה היא משרשור תהליך מקומי או תהליך מרוחק:

  • שיחות שמתבצעות מהתהליך המקומי מתבצעות באותו שרשור שמבצע את הקריאה. אם המיקום זה ה-thread הראשי בממשק המשתמש. ה-thread הזה ממשיך לפעול בממשק AIDL. אם הוא שרשור אחר, הוא השרשור שמפעיל את הקוד בשירות. לכן, אם רק מפרסמים מקומיים שרשורים ניגשים לשירות, ואפשר לשלוט בהם לגמרי אילו שרשורים מריצים בו. אבל במקרה כזה, לא להשתמש ב-AIDL בכלל. במקום זאת, יוצרים באמצעות הטמעה Binder
  • שיחות מתהליך מרוחק נשלחות ממאגר שרשורים שבפלטפורמה תהליך משלכם. חשוב להתכונן לשיחות נכנסות משרשורים לא ידועים, עם כמה שיחות שמתרחשת בו-זמנית. במילים אחרות, הטמעה של ממשק AIDL בטוח לגמרי לשרשורים. קריאות שבוצעו משרשור אחד באותו אובייקט מרוחק יגיע לפי הסדר בצד של המקבל.
  • מילת המפתח oneway משנה את ההתנהגות של שיחות מרחוק. בזמן השימוש בשיחה מרחוק, ולא לחסום. הפעולה הזו שולחת את נתוני העסקאות וחוזרת באופן מיידי. בסופו של דבר, הטמעת הממשק תקבל את ההודעה הזו כקריאה רגילה ממאגר ה-threads של Binder, כקריאה רגילה מרחוק. אם oneway נמצא בשימוש בשיחה מקומית, אין השפעה, והשיחה עדיין מסונכרנת.

הגדרה של ממשק AIDL

מגדירים את ממשק AIDL בקובץ .aidl באמצעות ה-Java תחביר שפת התכנות, ואז לשמור אותו בקוד המקור, בספריית src/, של שניהם האפליקציה המארחת את השירות וכל אפליקציה אחרת שמקשרת לשירות.

כשיוצרים כל אפליקציה שמכילה את הקובץ .aidl, אפשר להשתמש בכלים של Android SDK ליצור ממשק IBinder שמבוסס על הקובץ .aidl ולשמור אותו ב לספריית gen/ של הפרויקט. השירות חייב להטמיע את IBinder בהתאם לצורך. לאחר מכן אפליקציות הלקוח יכולות לאגד לשירות ולשיטות קריאה מ- IBinder כדי לבצע IPC.

כדי ליצור שירות מוגבל באמצעות AIDL, פועלים לפי השלבים הבאים, שמתוארים בסעיפים הבאים:

  1. יוצרים את הקובץ .aidl

    הקובץ הזה מגדיר את ממשק התכנות עם חתימות שיטה.

  2. הטמעת הממשק

    הכלים של Android SDK יוצרים ממשק בשפת התכנות Java בהתאם קובץ .aidl. בממשק הזה יש מחלקה מופשטת פנימית בשם Stub שמתרחבת Binder ומיישם שיטות מממשק AIDL. עליך להאריך את Stub ומטמיעים את השיטות.

  3. חשיפת הממשק ללקוחות

    להטמיע Service ולבטל את המאפיין onBind() כדי להחזיר את ההטמעה של Stub בכיתה.

זהירות: כל שינוי שיתבצע בממשק AIDL אחרי הגרסה הראשונה חייבת להישאר תואמת לאחור כדי למנוע תקלות באפליקציות אחרות שמשתמשים בשירות שלכם. כלומר, מכיוון שצריך להעתיק את קובץ ה-.aidl שלך לאפליקציות אחרות כדי שיוכלו לגשת לממשק של השירות שלך, עליך לקבל תמיכה בגרסה המקורית גרפי.

יוצרים את קובץ ה- .aidl

ל-AIDL יש תחביר פשוט, שמאפשר להצהיר על ממשק שכולל שיטה אחת או יותר לוקח פרמטרים ומחזירים ערכים. הפרמטרים והערכים המוחזרים יכולים להיות מכל סוג, אפילו ממשקים שנוצרו על ידי AIDL.

צריך ליצור את הקובץ .aidl באמצעות שפת התכנות Java. בכל .aidl חייב להגדיר ממשק יחיד וצריך להשתמש בו רק בהצהרה ובשיטה של הממשק חתימות.

כברירת מחדל, ב-AIDL יש תמיכה בסוגי הנתונים הבאים:

  • כל הסוגים הראשוניים בשפת התכנות Java (למשל int, long, char, boolean וכן הלאה)
  • מערך של סוגים של פרימיטיביות, כמו int[]
  • String
  • CharSequence
  • List

    כל הרכיבים ב-List חייבים להיות אחד מסוגי הנתונים הנתמכים של רשימה או אחד מהממשקים או החבילות האחרים שנוצרו על ידי AIDL שהצהרתם עליהם. א' אופציונלי: אפשר להשתמש ב-List כמחלקה של סוג פרמטר, כמו List<String>. מחלקת הבטון בפועל שהצד השני מקבל היא תמיד ArrayList, על אף שהצד השני נוצרה כדי להשתמש בממשק List.

  • Map

    כל הרכיבים ב-Map חייבים להיות אחד מסוגי הנתונים הנתמכים של רשימה או אחד מהממשקים או החבילות האחרים שנוצרו על ידי AIDL שהצהרתם עליהם. מפות לפי פרמטרים, כמו שדות הטופס Map<String,Integer>, לא נתמכים. את סיווג הבטון בפועל שהצד השני מקבל הוא תמיד HashMap, למרות שהשיטה נוצרה לשימוש בממשק Map. כדאי להשתמש Bundle כחלופה ל-Map.

צריך לכלול הצהרת import עבור כל סוג נוסף שלא צוין קודם לכן, גם אם הם מוגדרים באותה חבילה שמוגדרת בממשק.

כשמגדירים את ממשק השירות, חשוב לזכור:

  • ה-methods יכולות לכלול אפס פרמטרים או יותר, והן יכולות להחזיר ערך או ערך מבוטל.
  • לכל הפרמטרים הלא-פרימיטיביים נדרש תג כיוון שמציין את האופן שבו הנתונים עוברים: in, out או inout (ראו דוגמה בהמשך).

    פרימיטיביים, String, IBinder ו-AIDL הם in כברירת מחדל, והם לא יכולים להיות מוגדרים כאחרים.

    זהירות: הגבילו את הכיוון למה שבאמת חשוב נדרש, כי סידור פרמטרים הוא יקר.

  • כל התגובות לקוד שכלולות בקובץ .aidl נכללות נוצר ב-IBinder בממשק, מלבד הערות לפני הייבוא והחבילה הצהרות.
  • אפשר להגדיר מחרוזות וקבועים אינסופיים בממשק AIDL, למשל const int VERSION = 1;.
  • קריאות ה-method נשלחות על ידי transact() , שמבוסס בדרך כלל על אינדקס שיטה בממשק. כי זה מקשה על ניהול גרסאות, יכול להקצות באופן ידני את קוד העסקה לשיטה: void method() = 10;.
  • צריך להוסיף הערות לארגומנטים ולסוגי החזרה שאפשר לאפס באמצעות @nullable.

קובץ .aidl לדוגמה:

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

// Declare any non-default types here with import statements.

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

שומרים את הקובץ .aidl בספריית src/ של הפרויקט. אחרי ש את האפליקציה שלך, כלי ה-SDK יוצרים את קובץ הממשק של IBinder בספריית gen/ של הפרויקט. שם הקובץ שנוצר תואם לשם הקובץ .aidl, אבל עם סיומת .java. לדוגמה, IRemoteService.aidl בתוצאה של IRemoteService.java.

אם משתמשים ב-Android Studio, גרסת ה-build המצטבר יוצרת את סיווג הקישור באופן כמעט מיידי. אם אתם לא משתמשים ב-Android Studio, הכלי Gradle ייצור את הסיווג של binder בפעם הבאה לפתח את האפליקציה. בניית פרויקט עם gradle assembleDebug או gradle assembleRelease ברגע שמסיימים לכתוב את הקובץ .aidl, כדי שהקוד יוכל להתחבר לכיתה שנוצרה.

הטמעת הממשק

כשיוצרים את האפליקציה, הכלים ל-Android SDK יוצרים קובץ ממשק מסוג .java שנקרא על שם הקובץ .aidl. הממשק שנוצר כולל מחלקה משנית בשם Stub שהוא יישום מופשט של ממשק ההורה שלו, כמו YourInterface.Stub, שבו מוצהר על כל השיטות מהקובץ .aidl.

הערה: Stub גם מגדיר כמה שיטות מסייעות, בעיקר asInterface(), שמקבלת IBinder, בדרך כלל זו שמועברת לשיטת הקריאה החוזרת של הלקוח (callback) של onServiceConnected(), מחזירה מופע של ממשק ה-stub. לפרטים נוספים על ביצוע ההעברה (cast) הזו, אפשר לעיין בקטע התקשרות ל-IPC .

כדי להטמיע את הממשק שנוצר מ-.aidl, יש להרחיב את השדה Binder שנוצר למשל YourInterface.Stub, ולהטמיע את השיטות עברו בירושה מהקובץ .aidl.

הנה דוגמה ליישום של ממשק בשם IRemoteService, כפי שהוגדר על ידי הקוד הקודם IRemoteService.aidl לדוגמה, באמצעות מופע אנונימי:

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing.
    }
}

Java

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing.
    }
};

עכשיו binder הוא מופע של המחלקה Stub (a Binder), שמגדיר את ממשק ה-IPC של השירות. בשלב הבא, המופע הזה חשוף הלקוחות כדי שיוכלו ליצור אינטראקציה עם השירות.

חשוב לשים לב למספר כללים כשמטמיעים את ממשק AIDL:

  • לא מובטח שהשיחות הנכנסות יפעלו בשרשור הראשי, לכן צריך לחשוב על ריבוי שרשורים מההתחלה, וליצור כראוי את השירות כך שיהיה בטוח לשרשורים.
  • כברירת מחדל, קריאות IPC הן סינכרוניות. אם ידוע לך שהשירות דורש מספר אלפיות שנייה כדי להשלים בקשה, אין להפעיל אותה מה-thread הראשי של הפעילות. ייתכן שהאפליקציה תנתק את האפליקציה וכתוצאה מכך תוצג ב-Android ההודעה 'האפליקציה לא מגיבה' להתקשר לשרשור משרשור נפרד בלקוח.
  • רק סוגי החריגים המפורטים במסמכי העזר עבור Parcel.writeException() נשלחים חזרה אל המתקשר.

חשיפת הממשק ללקוחות

לאחר שיישמת את הממשק של השירות, עליך לחשוף אותו של הלקוחות כדי שהם יוכלו לקשר אליו. כדי לחשוף את הממשק לשירות, להרחיב את Service ולהטמיע את onBind() כדי להחזיר מופע של המחלקה שמיישם Stub שנוצר, כמו שהסברנו בקטע הקודם. לדוגמה שירות שחושף בפני לקוחות את הממשק לדוגמה של IRemoteService.

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing.
        }
    }
}

Java

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface.
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing.
        }
    };
}

עכשיו, כשלקוח, למשל פעילות, מתקשר ל-bindService() כדי להתחבר לשירות הזה, הקריאה החוזרת של הלקוח onServiceConnected() מקבלת את מכונה אחת (binder) הוחזרה על ידי onBind() של השירות .

ללקוח צריכה להיות גם גישה לסיווג הממשק. כך שאם הלקוח והשירות לכל אפליקציה נפרדת, לאפליקציה של הלקוח חייב להיות עותק של הקובץ .aidl בספרייה src/ שלה, שיוצרת את הקובץ android.os.Binder שמספקים ללקוח גישה לשיטות AIDL.

כשהלקוח מקבל את IBinder בקריאה החוזרת של onServiceConnected(), הוא צריך לקרוא YourServiceInterface.Stub.asInterface(service) כדי להפעיל Cast של התוכן שהוחזר פרמטר לסוג YourServiceInterface:

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

Java

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

לקבלת קוד לדוגמה נוסף, אפשר לעיין כיתה אחת (RemoteService.java) ב- ApiDemos.

העברת אובייקטים דרך IPC

ב-Android 10 (רמת API 29 ומעלה), ניתן להגדיר Parcelable אובייקטים ישירות בתוך AIDL. גם סוגים שנתמכים כארגומנטים בממשק AIDL ומגרשים אחרים יש תמיכה כאן. כך נמנעת העבודה הנוספת שנדרשת בכתיבה ידנית של קוד מרצון בכיתה. עם זאת, הפעולה הזו גם יוצרת מבנה בסיסי. אם רכיבי גישה בהתאמה אישית או פונקציונליות אחרת במקום זאת, צריך להטמיע את הפורמט 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;
}

דוגמת הקוד שלמעלה יוצרת באופן אוטומטי מחלקה Java עם שדות מספרים שלמים left, top, right וגם bottom. כל קודי הלחימה הרלוונטיים הם מוטמע באופן אוטומטי, ואפשר להשתמש באובייקט ישירות בלי להוסיף יישום בפועל.

אתם יכולים גם לשלוח מחלקה מותאמת אישית מתהליך אחד לאחר באמצעות ממשק IPC. אבל, לפעמים ודאו שהקוד של הכיתה זמין בצד השני של ערוץ ה-IPC. הכיתה חייבת לתמוך בממשק Parcelable. תומך Parcelable חשוב כי היא מאפשרת למערכת Android לפרק אובייקטים לפרימיטיביים שאפשר לשנות בתהליכים שונים.

כדי ליצור כיתה בהתאמה אישית שתומכת ב-Parcelable, מבצעים את הפעולות הבאות: הבאים:

  1. מגדירים לכיתה את הממשק של Parcelable.
  2. מטמיעים את writeToParcel, את המצב הנוכחי של האובייקט וכותבים אותו אל Parcel.
  3. מוסיפים לכיתה שדה סטטי בשם CREATOR, שהוא אובייקט שמיישם הממשק Parcelable.Creator.
  4. לסיום, יוצרים קובץ .aidl שכולל הצהרה על סיווג מגרשים, כפי שמוצג בדוגמה הבאה קובץ Rect.aidl.

    אם משתמשים בתהליך build מותאם אישית, לא להוסיף את הקובץ .aidl אל build. בדומה לקובץ כותרת בשפת C, קובץ .aidl הזה לא עובר הידור.

ב-AIDL משתמשים בשיטות ובשדות האלה בקוד שהוא יוצר כדי לארגן ולבטל תהליכים של האובייקטים.

לדוגמה, הנה קובץ Rect.aidl ליצירת מחלקה Rect parcelable:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

והנה דוגמה לאופן שבו המחלקה Rect מטמיעה את פרוטוקול Parcelable.

Kotlin

import android.os.Parcel
import android.os.Parcelable

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect?> {
            return Array(size) { null }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

Java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

הסידור הפנימי של הכיתה Rect פשוט. כדאי לבדוק את המודל השני ב-Parcel כדי לראות את סוגי הערכים האחרים שאפשר לכתוב אל Parcel.

אזהרה: חשוב לזכור את השלכות האבטחה של קבלת מתהליכים אחרים. במקרה הזה, הפונקציה Rect קוראת ארבעה מספרים מה-Parcel, אבל באחריותך לוודא שהם נמצאים בטווח הקביל של לערכים של הפעולה שהמתקשר מנסה לעשות. לקבלת מידע נוסף על הגנה על האפליקציה מפני תוכנות זדוניות, אפשר לעיין בטיפים בנושא אבטחה.

שיטות עם ארגומנטים בחבילות שמכילות 'פריטי מגרשים'

אם שיטה מקבלת אובייקט Bundle שצפוי להכיל למגרשים, צריך להגדיר את ה-classload של Bundle באמצעות מתבצעת התקשרות אל Bundle.setClassLoader(ClassLoader) לפני ניסיון לקרוא מBundle. אחרת, ייתקלו ב-ClassNotFoundException למרות שהחבילה מוגדרת בצורה נכונה באפליקציה.

לדוגמה, שימו לב לקובץ .aidl לדוגמה הבא:

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

/** Example service interface */
interface IRectInsideBundle {
    /** Rect parcelable is stored in the bundle with key "rect". */
    void saveRect(in Bundle bundle);
}
כפי שאפשר לראות בהטמעה הבאה, הערך של ClassLoader הוא מוגדר במפורש ב-Bundle לפני קריאת Rect:

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

קריאה ל-method של IPC

כדי להפעיל ממשק מרוחק שהוגדר באמצעות AIDL, צריך לבצע את השלבים הבאים: שיעור השיחה שלך:

  1. כוללים את הקובץ .aidl בספריית הפרויקט src/.
  2. להצהיר על מופע של הממשק IBinder, שנוצר לפי AIDL.
  3. הטמעה של ServiceConnection.
  4. התקשרות אל Context.bindService(), מעבירה את ההטמעה של ServiceConnection.
  5. בהטמעה של onServiceConnected(), קיבלת IBinder בשם service. שיחת טלפון YourInterfaceName.Stub.asInterface((IBinder)service) עד להמיר את הפרמטר שהוחזר לסוג YourInterface.
  6. קריאה לשיטות שהגדרתם בממשק. מלכודות תמיד DeadObjectException חריגים, שמושלכים כאשר החיבור מתנתק. בנוסף, מלכודות מסוג SecurityException חריגות, שמוזרות כששני התהליכים שמעורבים בהפעלת ה-method של IPC כוללים הגדרות AIDL סותרות.
  7. כדי להתנתק, צריך לבצע קריאה אל Context.unbindService() באמצעות המכונה של הממשק.

חשוב לזכור את הנקודות הבאות כשקוראים לשירות IPC:

  • אובייקטים נספרים בהפניות בין תהליכים.
  • אפשר לשלוח אובייקטים אנונימיים כארגומנטים ל-method.

מידע נוסף על קישור לשירות זמין במאמר סקירה כללית על שירותים קשורים.

הנה קוד לדוגמה שמדגים קריאה לשירות שנוצר על ידי AIDL, מהדוגמה של שירות מרוחק בפרויקט ApiDemos.

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface you call on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface you use on the service.  */
    internal var secondaryService: ISecondary? = null

    private lateinit var killButton: Button
    private lateinit var callbackText: TextView
    private lateinit var handler: InternalHandler

    private var isBound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // In this case, the service crashes before we can
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&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);
            }
        }
    }
}