لغة تعريف واجهة Android (AIDL)

تشبه لغة تعريف واجهة Android (AIDL) لغات تعريف واجهة Android (AIDL) الأخرى: فهي تتيح لك تحديد واجهة البرمجة التي يتفق عليها كل من العميل والخدمة من أجل التواصل مع بعضهما باستخدام الاتصال البيني للعمليات (IPC).

في نظام Android، لا يمكن لعملية ما الوصول عادةً إلى ذاكرة عملية أخرى. وللحديث عن ذلك، فهي بحاجة إلى تحليل الكائنات إلى عناصر أولية يستطيع نظام التشغيل فهمها وتنظيمها عبر تلك الحدود من أجلك. ونظرًا لأن كتابة التعليمات البرمجية بهدف تنظيم المهام مملة، سيتعامل Android مع هذه العملية نيابةً عنك باستخدام لغة تعريف واجهة برمجة التطبيقات (AIDL).

ملاحظة: تكون تقنية AIDL ضرورية فقط إذا كنت تسمح للعملاء من التطبيقات المختلفة بالوصول إلى خدمتك في IPC وكنت تريد التعامل مع سلاسل التعليمات المتعددة في خدمتك. إذا لم تكن بحاجة إلى إجراء IPC متزامن في تطبيقات مختلفة، أنشِئ واجهتك من خلال تنفيذ Binder. إذا أردت تنفيذ IPC ولكنك لا تحتاج إلى التعامل مع سلاسل التعليمات المتعددة، يمكنك استخدام الواجهة باستخدام Messenger. بغض النظر عن ذلك، تأكَّد من فهم الخدمات المرتبطة قبل تنفيذ لغة تعريف واجهة برمجة التطبيقات (AIDL).

قبل البدء في تصميم واجهة AIDL، عليك الانتباه إلى أنّ الطلبات الموجّهة إلى واجهة AIDL هي استدعاءات مباشرة للدوال. لا تضع افتراضات حول سلسلة السلاسل التي تحدث فيها المكالمة. يختلف ما يحدث حسب ما إذا كانت المكالمة من سلسلة محادثات في عملية محلية أو عملية عن بُعد:

  • يتم تنفيذ المكالمات التي يتم إجراؤها من العملية المحلية في سلسلة التعليمات نفسها التي تُجري الطلب. إذا كانت هذه هي سلسلة التعليمات الرئيسية في واجهة المستخدم، سيستمر تنفيذ سلسلة التعليمات هذه في واجهة لغة AIDL. إذا كانت سلسلة التعليمات البرمجية أخرى، فهي التي تقوم بتنفيذ التعليمات البرمجية الخاصة بك في الخدمة. وبالتالي، إذا كانت سلاسل المحادثات المحلية فقط هي التي تصل إلى الخدمة، يمكنك التحكّم بشكل كامل في سلاسل التعليمات التي يتم تنفيذها فيها. ولكن في هذه الحالة، لا تستخدم لغة تعريف الارتباط (AIDL) على الإطلاق، بل يمكنك بدلاً من ذلك إنشاء الواجهة من خلال تنفيذ Binder.
  • يتم إرسال المكالمات من عملية عن بُعد من مجموعة سلاسل محادثات تحتفظ بها المنصة داخل عمليتك الخاصة. كن مستعدًا للمكالمات الواردة من سلاسل محادثات غير معروفة، مع حدوث مكالمات متعددة في نفس الوقت. بمعنى آخر، يجب أن يكون تنفيذ واجهة AIDL آمنًا تمامًا من سلسلة التعليمات. تصل المكالمات التي يتم إجراؤها من سلسلة محادثات واحدة على نفس الكائن البعيد بالترتيب إلى جهة الاستقبال.
  • تعمل الكلمة الرئيسية oneway على تعديل سلوك المكالمات عن بُعد. عند استخدامه، لا يتم حظر المكالمة عن بُعد. وتُرسِل هذه الميزة بيانات المعاملة وترجع على الفور. وفي نهاية المطاف، يؤدي تنفيذ الواجهة إلى تلقّيها كمكالمة عادية من مجموعة سلاسل المحادثات Binder كمكالمة عادية عن بُعد. إذا تم استخدام oneway مع مكالمة محلية، لن يكون هناك أي تأثير وستظل المكالمة متزامنة.

تحديد واجهة AIDL

حدِّد واجهة AIDL في ملف .aidl باستخدام بنية لغة برمجة Java، ثم احفظها في رمز المصدر في دليل src/ لكل من التطبيق الذي يستضيف الخدمة وأي تطبيق آخر يرتبط بالخدمة.

عند إنشاء كل تطبيق يحتوي على ملف .aidl، تنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android واجهة IBinder استنادًا إلى ملف .aidl وتحفظها في دليل gen/ للمشروع. يجب أن تنفّذ الخدمة واجهة IBinder على النحو المناسب. يمكن لتطبيقات العميل بعد ذلك الربط بالخدمة والطرق من IBinder لتنفيذ IPC.

لإنشاء خدمة محدودة باستخدام لغة تعريف واجهة نظام Android (AIDL)، اتّبِع الخطوات التالية الموضَّحة في الأقسام التالية:

  1. إنشاء ملف .aidl

    يعرّف هذا الملف واجهة البرمجة باستخدام توقيعات الطرق.

  2. تنفيذ الواجهة

    تنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android واجهة بلغة برمجة 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 لكل نوع إضافي غير مدرج مسبقًا حتى لو تم تحديده في الحزمة نفسها التي تتضمّن واجهتك.

عند تحديد واجهة الخدمة، يجب الانتباه إلى ما يلي:

  • يمكن أن تأخذ الطرق صفر أو أكثر من المعاملات ويمكن أن تعرض قيمة أو فراغًا.
  • تتطلّب جميع المَعلمات غير الأولية علامة اتجاهية تشير إلى الطريقة التي تتّبعها البيانات: in أو out أو inout (اطّلِع على المثال أدناه).

    واجهات برمجة التطبيقات الأساسية وString وIBinder وواجهة AIDL التي تم إنشاؤها هي in تلقائيًا، ولا يمكن أن تكون بخلاف ذلك.

    تنبيه: اختصِر الاتجاه بما هو مطلوب حقًا، لأن تنظيم المعلَمات باهظ الثمن.

  • ويتم تضمين جميع تعليقات الرموز المضمّنة في ملف .aidl في واجهة IBinder التي تم إنشاؤها باستثناء التعليقات التي تسبق بيانات الاستيراد والحزمة.
  • يمكن تحديد ثوابت السلسلة والعدد الصحيح في واجهة AIDL، مثل const int VERSION = 1;.
  • يتم إرسال استدعاءات الطريقة عن طريق رمز 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، سيتم إنشاء فئة الصنف Binder على الفور تقريبًا، وذلك من خلال الإصدار التزايدي. إذا كنت لا تستخدم "استوديو Android"، ستنشئ أداة Gradle فئة الصنف Binder عند إنشاء تطبيقك في المرة التالية. يمكنك إنشاء مشروعك باستخدام gradle assembleDebug أو gradle assembleRelease بعد الانتهاء من كتابة ملف .aidl، بحيث يمكن ربط الرمز الخاص بك بالفئة التي تم إنشاؤها.

تنفيذ الواجهة

عند إنشاء التطبيق، تنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android ملف واجهة .java يحمل اسم ملف .aidl. تتضمّن الواجهة التي يتم إنشاؤها فئة فرعية باسم Stub وهي تنفيذ مجرّد للواجهة الرئيسية، مثل YourInterface.Stub، وتتضمّن جميع الطرق من ملف .aidl.

ملاحظة: تحدد السمة Stub أيضًا بعض الطرق المساعدة، أبرزها asInterface() التي تأخذ IBinder، وعادةً ما يتم تمريرها إلى طريقة معاودة الاتصال onServiceConnected() للعميل، وتعرض مثيلاً من واجهة التوكيل. للحصول على مزيد من التفاصيل حول طريقة عمل هذا البث، راجِع القسم استدعاء طريقة 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 متزامنة. إذا كنت تعلم أن الخدمة تستغرق أكثر من بضع ثوانٍ لإكمال الطلب، لا تستدعيها من سلسلة التعليمات الرئيسية للنشاط. وقد يعلق التطبيق، مما يؤدي إلى عرض 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) لتحويل المعلمة المعروضة إلى النوع 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 (المستوى 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 التالي.

    إذا كنت تستخدم عملية إصدار مخصّصة، لا تضِف ملف .aidl إلى الإصدار الخاص بك. لا يتم تجميع ملف .aidl هذا، تمامًا مثل ملف العنوان باللغة C.

تستخدم تقنية AIDL هذه الطرق والحقول في الرمز البرمجي الذي تنشئه لتنظيم الكائنات وإلغاء تنظيمها.

على سبيل المثال، إليك ملف Rect.aidl لإنشاء فئة Rect قابلة للتقسيم:

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، ولكن الأمر متروك لك للتأكد من أنّ هذه الأرقام تقع ضمن النطاق المقبول لكل ما يحاول المتصل فعله. لمزيد من المعلومات حول كيفية حماية تطبيقك من البرامج الضارة، راجع نصائح الأمان.

الطرق التي تحتوي على وسيطات حزمة تحتوي على Parcelables

إذا قبلت إحدى الطرق كائن Bundle يُتوقع أن يحتوي على عناصر قابلة للفصل، احرص على ضبط أداة تحميل الفئة في 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.
    }
};

استدعاء طريقة 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 التي يتم طرحها إذا كانت العمليتان اللتان يتضمنتان في استدعاء طريقة IPC متعارضة في تعريفات AIDL.
  7. لقطع الاتصال، يمكنك الاتصال بـ Context.unbindService() مع إرسال نسخة افتراضية من واجهتك.

يجب أخذ النقاط التالية في الاعتبار عند طلب خدمة IPC:

  • العناصر هي مرجع يتم عدّه في كل العمليات.
  • يمكنك إرسال كائنات مجهولة كوسيطات للأسلوب.

للمزيد من المعلومات حول الربط بخدمة، اقرأ نظرة عامة على الخدمات المرتبطة.

في ما يلي بعض نماذج الرموز التي توضِّح طلب خدمة تم إنشاؤها باستخدام الذكاء الاصطناعي (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);
            }
        }
    }
}