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

وتشبه لغة تعريف واجهة Android (AIDL) معرّفات IDL أخرى، فهي تتيح لك تحديد واجهة البرمجة التي يتّفق عليها كلّ من العميل والخدمة من أجل التواصل مع بعضهما باستخدام تقنية الاتصال البيني للعمليات (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.

لإنشاء خدمة محدودة باستخدام AIDL، اتّبِع الخطوات التالية الموضّحة في الأقسام التالية:

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

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

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

    تُنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android واجهة بلغة البرمجة Java استنادًا إلى ملف .aidl. تحتوي هذه الواجهة على فئة مجردة داخلية باسم Stub تمتد Binder وتنفِّذ طرقًا من واجهة AIDL. يجب توسيع نطاق فئة Stub وتنفيذ الطرق.

  3. إتاحة الواجهة للعملاء

    عليك تنفيذ Service وإلغاء السمة onBind() لإظهار عملية تنفيذ الفئة Stub.

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

إنشاء ملف ‎ .aidl

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

يجب إنشاء ملف .aidl باستخدام لغة البرمجة Java. يجب أن يحدِّد كل ملف .aidl واجهة واحدة ولا يتطلّب سوى تعريف الواجهة وتوقيعات methods.

يتيح AIDL تلقائيًا أنواع البيانات التالية:

  • جميع الأنواع الأساسية في لغة برمجة Java (مثل int وlong char وboolean وما إلى ذلك)
  • صفائف من أي أنواع، مثل int[] أو MyParcelable[]
  • 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 في directorygen/ لمشروعك. يتطابق اسم الملف الذي تم إنشاؤه مع اسم ملف .aidl، ولكن مع إضافة .java. على سبيل المثال، يؤدي استخدام IRemoteService.aidl باللغة IRemoteService.java.

إذا كنت تستخدم "استوديو Android"، سينشئ الإصدار التزايدي فئة الرابط على الفور تقريبًا. إذا كنت لا تستخدم Android Studio، تنشئ أداة Gradle فئة الربط في المرة التالية التي تُنشئ فيها تطبيقك. يمكنك إنشاء مشروعك باستخدام 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 (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

في الإصدار 10 من نظام التشغيل Android (المستوى 29 من واجهة برمجة التطبيقات أو مستوى أعلى)، يمكنك تحديد عناصر Parcelable مباشرةً فيملف IDEAL. يمكن أيضًا استخدام الأنواع المتوافقة كوسيطات لواجهة AIDL وأنواع parcelable الأخرى. ويؤدي ذلك إلى تجنُّب العمل الإضافي المتعلق بكتابة رمز تنسيق يدويًا وأحد فئات المخصّصة. ومع ذلك، يؤدي ذلك أيضًا إلى إنشاء بنية فارغة. إذا أردت استخدام وظائف أخرى أو عناصر وصول مخصّصة، يمكنك تنفيذ 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 يعرِض فئة parcelable، كما هو موضّح في ملف 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 methods الأخرى في Parcel للاطّلاع على الأنواع الأخرى من القيم التي يمكنك كتابتها في Parcel.

تحذير: تذكَّر الآثار الأمنية لتلقّي البيانات من عمليات أخرى. في هذه الحالة، يقرأ Rect أربعة أرقام من Parcel، ولكن عليك التأكّد من أنّ هذه الأرقام ضمن النطاق المقبول للقيم بغض النظر عمّا يحاول المتصل فعله. لمزيد من المعلومات حول كيفية الحفاظ على أمان تطبيقك من البرامج الضارة، اطّلِع على نصائح حول الأمان.

الطرق التي تحتوي على وسيطات حِزم تحتوي على عناصر Parcelable

إذا كانت إحدى الطرق تقبل عنصر Bundle من المفترض أن يحتوي على عناصر قابلة للتقسيم، تأكَّد من ضبط أداة تحميل الفئات لعنصر Bundle من خلال استدعاء Bundle.setClassLoader(ClassLoader) قبل محاولة القراءة من Bundle. بخلاف ذلك، ستواجه الخطأ ClassNotFoundException على الرغم من أنّ العنصر parcelable تم تحديده بشكل صحيح في تطبيقك.

على سبيل المثال، إليك النموذج التالي لملف .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 التي يتم إنشاؤها استنادًا إلى IDE.
  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);
            }
        }
    }
}