لغة تعريف واجهة 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. تنفيذ الواجهة

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

  3. عرض الواجهة للعملاء

    تنفيذ Service وإلغاء onBind() لعرض عملية تنفيذ Stub الصف.

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

إنشاء ملف .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"، سينشئ الإصدار التزايدي فئة الرابط على الفور تقريبًا. إذا كنت لا تستخدم "استوديو Android"، ستنشئ أداة 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 هي مثيل للفئة StubBinder)، التي تحدد واجهة 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 إلى على غرار ملف العنوان بلغة C، لا يتم تجميع ملف .aidl هذا.

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

الطرق التي تتضمّن وسيطات الحزمة التي تحتوي على قطع قطع

إذا قبلت الطريقة كائن 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);
            }
        }
    }
}