خلفيات AIDL

تُعد الواجهة الخلفية AIDL هدفًا لإنشاء رموز التداخلات. عند استخدام ملفات AIDL، يجب استخدامها دائمًا بلغة معيّنة مع وقت تشغيل محدّد. استنادًا إلى السياق، يجب استخدام الخلفيات المختلفة لـ AIDL.

في الجدول التالي، يشير ثبات واجهة برمجة التطبيقات إلى إمكانية compiling code against this API surface (تجميع الرمز البرمجي وفقًا لواجهة برمجة التطبيقات هذه) بطريقة يمكن بها إرسال الرمز البرمجي بشكل مستقل عن ملف system.img libbinder.so الثنائي.

تتضمّن واجهة برمجة التطبيقات AIDL الخلفيات التالية:

الخلفية اللغة واجهة برمجة التطبيقات إنشاء الأنظمة
Java Java حزمة تطوير البرامج (SDK)/SystemApi (الإصدار الثابت*) الكل
NDK C++‎ libbinder_ndk (الإصدار الثابت*) aidl_interface
تكلفة المكالمة الهاتفية C++‎ libbinder (غير ثابت) الكل
الصدأ (Rust) الصدأ (Rust) libbinder_rs (إصدار ثابت*) aidl_interface
  • يُرجى العلم أنّ واجهات برمجة التطبيقات هذه مستقرة، ولكن العديد من واجهات برمجة التطبيقات، مثل تلك المخصّصة لإدارة الخدمة، تكون محجوزة لاستخدام النظام الأساسي الداخلي ولا تكون متاحة للتطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات، اطّلِع على مستندات المطوِّرين.
  • تم تقديم الخلفية Rust في الإصدار 12 من Android، وأصبحت ‎JDK الخلفية متاحة اعتبارًا من الإصدار 10 من Android.
  • تم إنشاء حزمة Rust على libbinder_ndk، ما يتيح لها أن تكون مستقرة وقابلة للنقل. تستخدم وحدات APEX حزمة الحِزم بالطريقة نفسها التي يستخدمها أي شخص آخر على جانب النظام. يتم تجميع جزء Rust في حزمة APEX وشحنه داخلها. يعتمد ذلك على libbinder_ndk.so في قسم النظام.

إنشاء الأنظمة

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

نظام التصميم الأساسي

في أي وحدة Android.bp من النوع cc_ أو java_ (أو في نظائرها من النوع Android.mk)، يمكن تحديد ملفات .aidl كملفات مصدر. في هذه الحالة، يتم استخدام الخلفيات Java/CPP لـ AIDL (وليس الخلفية NDK)، ويتم تلقائيًا إضافة الفصول لاستخدام ملفات AIDL المقابلة إلى الوحدة. الخيارات مثل local_include_dirs التي تُعلم نظام الإنشاء بالمسار الجذر إلىملفّات AIDL في هذه الوحدة يمكن تحديدها في هذه الوحدات ضمن مجموعة aidl: . يُرجى العِلم أنّ الخلفية في Rust مخصّصة للاستخدام مع Rust فقط. تتم معالجة وحدات rust_ بشكل مختلف لأنّه لا يتم تحديد ملفات AIDL كملفات مصدر. بدلاً من ذلك، تُنشئ وحدة aidl_interface rustlib تُسمى <aidl_interface name>-rust يمكن ربطها. لمزيد من التفاصيل، يُرجى الاطّلاع على مثال Rust AIDL.

aidl_interface

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

الأنواع

يمكنك اعتبار المحول البرمجي aidl بمثابة تنفيذ مرجعي للأنواع. عند إنشاء واجهة، يمكنك استدعاء aidl --lang=<backend> ... للاطّلاع على ملف الواجهة الناتج. عند استخدام وحدة aidl_interface، يمكنك عرض النتيجة في out/soong/.intermediates/<path to module>/.

نوع Java/AIDL نوع C++ نوع حزمة NDK نوع الصدأ
قيمة منطقية قيمة منطقية (Bool) قيمة منطقية (Bool) قيمة منطقية (Bool)
بايت8 int8_t int8_t i8
char char16_t char16_t u16
تدخُّل دفاعي int32_t int32_t i32
طويلة int64_t int64_t i64
عائم عائم عائم f32
مزدوج مزدوج مزدوج f64
سلسلة android::String16 std::string سلسلة
android.os.Parcelable android::Parcelable لا ينطبق لا ينطبق
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::مؤشر <T> std::مؤشر <T> الإدخال: &[T]
الإخراج: Vec<T>
‫byte[] std::vector<uint8_t> std::vector<int8_t>1 الإدخال: &[u8]
الإخراج: Vec<u8>
List<T> std::الانتقال <T>2 std::vector<T>3 الإدخال: &[T]4
الإخراج: Vec<T>
FileDescriptor android::base::unique_fd لا ينطبق binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
نوع الواجهة (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
نوع parcelable (T) T T T
نوع الائتلاف (T)5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. في الإصدار 12 من نظام التشغيل Android أو الإصدارات الأحدث، تستخدم صفائف البايت ‎ uint8_t بدلاً من int8_t لأسباب التوافق.

2. تتوافق الخلفية في C++ مع List<T> حيث يكون T أحد String أو IBinder أو ParcelFileDescriptor أو parcelable. في Android 13 أو الإصدارات الأحدث، يمكن أن يكون T أي نوع غير أساسي (بما في ذلك أنواع الواجهات) باستثناء الصفائف. تنصح AOSP باستخدام أنواع الصفائف مثل T[]، لأنّها تعمل في جميع الخلفيات.

3. تتوافق الخلفية في NDK مع List<T> حيث يكون T أحد String أو ParcelFileDescriptor أو parcelable. في Android 13 أو الإصدارات الأحدث، يمكن أن يكون T أي نوع غير أساسي باستثناء المصفوفات.

4. يتم تمرير الأنواع بشكل مختلف في رمز Rust بناءً على ما إذا كانت إدخال (وسيطة) أو ناتج (قيمة معروضة).

5. تتوفّر أنواع الاتحاد في نظام التشغيل Android 12 والإصدارات الأحدث.

6- في نظام التشغيل Android 13 أو الإصدارات الأحدث، يمكن استخدام الصفائف ذات الحجم الثابت. يمكن أن تحتوي الصفائف ذات الحجم الثابت على أبعاد متعدّدة (مثل int[3][4]). في الخلفية في Java، يتم تمثيل الصفائف ذات الحجم الثابت كأنواع صفائف.

7- لإنشاء مثيل لكائن SharedRefBase رابط، استخدِم SharedRefBase::make\<My\>(... args ...). تنشئ هذه الدالة std::shared_ptr\<T\> std::shared_ptr\<T\> والتي تتم إدارتها أيضًا داخليًا، في حال كانت عملية الربط مملوكة لعملية أخرى. يؤدي إنشاء الكائن بطرق أخرى إلى إنشاء ملكية مزدوجة.

8. راجِع أيضًا Java/AIDL من النوع byte[].

الاتجاهية (in/out/inout)

عند تحديد أنواع الوسيطات للدوالّ، يمكنك تحديد ها على النحو التالي: in أو out أو inout. يتحكّم هذا الخيار في الاتجاه الذي يتم فيه تمرير المعلومات لمكالمة IPC. in هو الاتجاه التلقائي، ويشير إلى أنّه يتم تمرير البيانات من المُتصل إلى المُتّصل به. يشير الرمز out إلى أنّه يتم تمرير البيانات من المكوّن المُستدعى إلى المكوّن المُتصل. inout هي تركيبة من السمتَين. ومع ذلك، ينصح فريق Android بتجنّب استخدام محدّد الوسيطات inout. في حال استخدام inout مع واجهة ذات إصدارات ومستلم مكالمة قديم، تتم إعادة ضبط الحقول الإضافية المتوفّرة في جهة الاتصال فقط على قيمها التلقائية. في Rust، يتلقّى نوع inout العادي &mut Vec<T>، ويتلقّى نوع inout لقائمة &mut Vec<T>.

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF8/UTF16

باستخدام الخلفية في لغة C++، يمكنك اختيار ما إذا كانت السلاسل بترميز utf-8 أو utf-16. يمكنك تعريف السلاسل مثل @utf8InCpp String في AIDL لتحويلها تلقائيًا إلى utf-8. تستخدم الخلفيات في NDK وRust دائمًا سلاسل utf-8. لمزيد من المعلومات حول annotation utf8InCpp، اطّلِع على التعليقات التوضيحية في AIDL.

إمكانية قبول القيم الفارغة

يمكنك إضافة تعليقات توضيحية إلى الأنواع التي يمكن أن تكون فارغة باستخدام @nullable. لمزيد من المعلومات عن التعليق التوضيحي nullable، يُرجى الاطّلاع على التعليقات التوضيحية في AIDL.

السلع المخصّصة للتوصيل في طرد

العنصر المخصّص الذي يمكن تقسيمه هو عنصر يمكن تقسيمه يتم تنفيذه يدويًا في خلفي ة مستهدفة. لا يمكنك استخدام بيانات حول قِطع قِطع مخصّصة إلا عند محاولة إضافة لغات أخرى إلى عنصر حالي لا يمكن تغييره.

لتعريف عنصر قابل للنقل مخصّص كي يتعرّف عليه AIDL، يبدو تعريف AIDL للعنصر القابل للنقل على النحو التالي:

    package my.pack.age;
    parcelable Foo;

يُعلن هذا الإجراء تلقائيًا عن عنصر قابل للنقل في Java حيث يكون my.pack.age.Foo فئة Java تنفِّذ واجهة Parcelable.

لتعريف واجهة برمجة تطبيقات مخصّصة لنظام التشغيل ‎CPP في AIDL، استخدِم cpp_header:

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

يبدو تنفيذ C++ في my/pack/age/Foo.h على النحو التالي:

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

لتعريف عنصر قابل للتقسيم مخصّص في حزمة NDK في ملف AIDL، استخدِم ndk_header:

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

يبدو تنفيذ NDK في android/pack/age/Foo.h على النحو التالي:

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

في Android 15، للإشارة إلى منتج Rust المخصّص في AIDL، استخدِم rust_type:

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

يظهر تنفيذ Rust في rust_crate/src/lib.rs على النحو التالي:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

بعد ذلك، يمكنك استخدام هذا العنصر السرّي كنوع في ملفات AIDL، ولكن لن يتم إنشاؤه باستخدام لغة AIDL. قدِّم عاملَي التشغيل < و== لخلفية CPP/NDK وParcelable المخصّصة لاستخدامها في union.

القيم التلقائية

يمكن أن تحدِّد العناصر القابلة للتقسيم إلى أجزاء قيمًا تلقائية لكل حقل للعناصر الأساسية وString والمصفوفات من هذه الأنواع.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

عندما لا تكون القيم التلقائية متوفرة في الواجهة الخلفية لـ Java، يتم إعداد الحقول كقيم صفرية للأنواع الأولية وnull للأنواع غير الأساسية.

في الخلفيات الأخرى، يتمّ إعداد الحقول باستخدام قيم تلقائية تمّ إعدادها عندما لا يتمّ تحديد القيم التلقائية. على سبيل المثال، في الخلفية C++، تتم معالجة حقول String كسلسلة فارغة ويتم معالجة حقول List<T> كvector<T> فارغ. تم إعداد @nullable حقل كحقول ذات قيمة فارغة.

الاتحادات

يتم وضع علامة على مجموعات AIDL وتُعدّ ميزاتها متشابهة في جميع الخلفيات. ويتم إنشاؤها افتراضيًا وفقًا للقيمة التلقائية للحقل الأول، ولها طريقة خاصة باللغة للتفاعل معها.

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

مثال على Java

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setSringField("abc");                // setter

مثال على C++ وNDK

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

مثال على الصدأ

في Rust، يتم تنفيذ النقابات كتعدادات ولا تحتوي على إحصائيات ومحددات صريحة.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

خطأ أثناء المعالجة

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

مَعلمات الإخراج التي تتضمّن أخطاء

عندما تُبلغ دالة AIDL عن خطأ، قد لا تبدأ الدالة في إعداد مَعلمات الإخراج أو تعديلها. وعلى وجه التحديد، قد يتم تعديل مَعلمات الإخراج إذا حدث الخطأ أثناء إزالة الحزمة بدلاً من حدوثه أثناء معالجة المعاملة نفسها. بشكل عام، عند ظهور خطأ من دالة AIDL، يجب اعتبار جميع مَعلمات inout وout بالإضافة إلى القيمة المعروضة (التي تعمل مثل مَعلمة out في بعض الخلفيات) في حالة غير محدّدة.

قيم الأخطاء التي يجب استخدامها

يمكن استخدام العديد من قيم الأخطاء المضمّنة في أي واجهات AIDL، ولكن يتم التعامل مع بعض القيم بطريقة خاصة. على سبيل المثال، يمكن استخدام EX_UNSUPPORTED_OPERATION و EX_ILLEGAL_ARGUMENT عندما يصفَان حالة الخطأ، ولكن يجب عدم استخدام EX_TRANSACTION_FAILED لأنّ البنية الأساسية تتعامل معه بشكل خاص. اطّلِع على التعريفات الخاصة بالخلفية للحصول على مزيد من المعلومات عن هذه القيم المضمّنة.

إذا كانت واجهة AIDL تتطلّب قيم خطأ إضافية لا تغطّيها أنواع الأخطاء المضمّنة، يمكنها استخدام الخطأ المضمّن الخاص بالخدمة والذي يسمح بتضمين قيمة خطأ خاصة بالخدمة يحدّدها المستخدم. يتم عادةً تحديد هذه الأخطاء الخاصة بالخدمة في واجهة AIDL باعتبارها ملفات enum مستندة إلى const int أو int، ولا يتم تحليلها من خلال أداة الربط.

في Java، يتم ربط الأخطاء بالاستثناءات، مثل android.os.RemoteException. بالنسبة إلى الاستثناءات المتعلّقة بالخدمة، تستخدم Java android.os.ServiceSpecificException بالإضافة إلى الخطأ الذي يحدّده المستخدم.

لا يستخدم الرمز الأصلي في Android الاستثناءات. تستخدم الواجهة الخلفية لـ CPP android::binder::Status. تستخدم الواجهة الخلفية لـ NDK ndk::ScopedAStatus. تُعرِض كل متدّاة أنشأها AIDL أحد هذه القيم، ما يمثّل حالة المَعلمة. يستخدم الجزء الخلفي من Rust قيم رموز الاستثناءات نفسها المستخدَمة في حزمة NDK، ولكنه يحوّلها إلى أخطاء Rust الأصلية (StatusCode وExceptionCode) قبل إرسالها إلى المستخدم. بالنسبة إلى الأخطاء المتعلّقة بالخدمة، يستخدم الرمزان Status أو ScopedAStatus الرمزEX_SERVICE_SPECIFIC مع الخطأ الذي يحدّده المستخدم.

يمكن العثور على أنواع الأخطاء المضمّنة في الملفات التالية:

الخلفية التعريف
Java android/os/Parcel.java
تكلفة المكالمة الهاتفية binder/Status.h
كرونة دنماركية android/binder_status.h
الصدأ (Rust) android/binder_status.h

استخدام الخلفيات المختلفة

هذه التعليمات خاصة برمز نظام التشغيل Android. تستخدِم هذه الأمثلة نوعًا محدّدًا هو my.package.IFoo. للحصول على تعليمات حول كيفية استخدام الواجهة الخلفية Rust، يمكنك الاطّلاع على مثال Rust AIDL على صفحة Android Rust Patterns.

استيراد الأنواع

سواء كان النوع المحدّد واجهة أو قابلاً للتقسيم أو اتحادًا، يمكنك استيراده في Java:

import my.package.IFoo;

أو في الخلفية في "شركاء المحتوى في خرائط Google":

#include <my/package/IFoo.h>

أو في الخلفية في NDK (لاحظ مساحة الاسم aidl الإضافية):

#include <aidl/my/package/IFoo.h>

أو في الخلفية في Rust:

use my_package::aidl::my::package::IFoo;

على الرغم من أنّه يمكنك استيراد نوع مُدمَج في Java، عليك تضمين الرأس لنوع الجذر في الخلفيات CPP/NDK. على سبيل المثال، عند استيراد نوع مُدمَج Bar محدّد في my/package/IFoo.aidl (IFoo هو النوع الجذر لملف my/package/IFoo.aidl)، يجب تضمين <my/package/IFoo.h> لنظام التشغيل الخلفي لـ CPP (أو <aidl/my/package/IFoo.h> لنظام التشغيل الخلفي لـ NDK).

تنفيذ الخدمات

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

    package my.package;
    interface IFoo {
        int doFoo();
    }

في Java، يجب أن تمتد من هذه الفئة:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

في الخلفية في NDK (لاحظ مساحة الاسم aidl الإضافية):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

في خلفية Rust:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

أو باستخدام Rust غير المتزامنة:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

التسجيل والحصول على الخدمات

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

في Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

في خلفية CPP:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

في الخلفية في NDK (لاحظ مساحة الاسم aidl الإضافية):

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

في الخلفية في Rust:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

في الواجهة الخلفية Rust غير المتزامنة التي تتضمن بيئة تشغيل ذات سلسلة محادثات واحدة:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks will run on this thread.
    std::future::pending().await
}

يتمثل أحد الاختلافات المهمة عن الخيارات الأخرى في أنّنا لا نطلق join_thread_pool عند استخدام Rust غير المتزامن ووقت التشغيل المتعدّد المواضيع. وذلك لأنك تحتاج إلى إعطاء Tokio سلسلة محادثات يمكنه فيها تنفيذ المهام التي تم إنتاجها. في هذا المثال، ستؤدي سلسلة التعليمات الرئيسية إلى تحقيق هذا الغرض. سيتم تنفيذ أي مهام ناشئة باستخدام tokio::spawn في سلسلة المحادثات الرئيسية.

في الخلفية غير المتزامنة في Rust، مع وقت تشغيل متعدّد المواضيع:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

باستخدام وحدة التشغيل Tokio المتعدّدة المواضيع، لا يتم تنفيذ المهام التي تم إنشاؤها على السلسلة الرئيسية. لذلك، من المنطقي أكثر استدعاء join_thread_pool في السلسلة الرئيسية لكي لا تكون السلسلة الرئيسية غير نشِطة. يجب إنهاء المكالمة في block_in_place لمغادرة السياق غير المتزامن.

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

  • في Java، استخدِم android.os.IBinder::linkToDeath.
  • في الخلفية في "الإعلانات الصورية على شبكة البحث"، استخدِم android::IBinder::linkToDeath.
  • في خلفية NDK، استخدِم AIBinder_linkToDeath.
  • في الخلفية في Rust، أنشئ عنصرًا من النوع DeathRecipient، ثم استخدِم my_binder.link_to_death(&mut my_death_recipient). ملاحظة: بما أنّ DeathRecipient يملك طلب إعادة الاتصال، يجب إبقاء هذا الكائن نشطًا ما دمت تريد تلقّي الإشعارات.

معلومات المتصل

عند تلقي طلب تثبيت kernel، تتوفر معلومات المتصل في العديد من واجهات برمجة التطبيقات. يشير معرِّف العملية (PID) إلى معرّف العملية في Linux للعملية التي يتم إرسال معاملة إليها. يشير المعرّف الفريد (UID) إلى معرّف مستخدم Linux. عند تلقّي مكالمة أحادية الاتجاه، يكون معرّف PID للمتصل هو 0. عندما تكون خارج سياق معاملة الربط، تعرض هذه الدوال رقمَي تعريف PID وUID للعملية الحالية.

في الخلفية في Java:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

في الخلفية في NDK:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

في الخلفية في Rust، عند تنفيذ الواجهة، حدِّد ما يلي (بدلاً من السماح بالإعداد التلقائي):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

واجهة برمجة التطبيقات لتقارير الأخطاء وتصحيحها في الخدمات

عند تشغيل تقارير الأخطاء (على سبيل المثال، باستخدام adb bugreport)، يتم جمع المعلومات من جميع أنحاء النظام للمساعدة في تصحيح أخطاء المشاكل المختلفة. بالنسبة إلى خدمات AIDL، تستخدم تقارير الأخطاء الثنائي dumpsys على جميع الخدمات المسجَّلة في خدمة "مدير الخدمات" لتفريغ معلوماتها في تقرير الأخطاء. يمكنك أيضًا استخدام dumpsys في سطر الأوامر للحصول على معلومات من خدمة تتضمّن dumpsys SERVICE [ARGS]. في الخلفيات C++ وJava، يمكنك التحكّم في ترتيب تفريغ الخدمات باستخدام مزيد من الوسيطات لـ addService. يمكنك أيضًا استخدام dumpsys --pid SERVICE للحصول على رقم تعريف عملية تدبير الخدمة أثناء تصحيح الأخطاء.

لإضافة إخراج مخصّص إلى خدمتك، يمكنك إلغاء طريقة dump في عنصر الخادم كما لو كنت تنفّذ أي طريقة أخرى لبروتوكول IPC محددة في ملف AIDL. عند إجراء ذلك، يجب حصر عملية تفريغ البيانات في إذن android.permission.DUMP الخاص بالتطبيق أو حصرها في أرقام تعريف مستخدمين معيّنة.

في الخلفية في Java:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

في الخلفية في NDK:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

في الخلفية في Rust، عند تنفيذ الواجهة، حدِّد ما يلي (بدلاً من السماح بالإعداد التلقائي):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

استخدام مؤشرات ضعيفة

يمكنك الاحتفاظ بإشارة ضعيفة إلى عنصر رابط.

على الرغم من أنّ Java تتيح استخدام WeakReference، إلا أنّها لا تتيح استخدام مراجع الربط الضعيف في الطبقة الأصلية.

النوع الضعيف هو wp<IFoo> في الواجهة الخلفية لخدمة CPP.

في خلفية NDK، استخدم ScopedAIBinder_Weak:

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

في الخلفية في Rust، يمكنك استخدام WpIBinder أو Weak<IFoo>:

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

الحصول على وصف الواجهة ديناميكيًا

يحدّد واصف الواجهة نوع الواجهة. يكون ذلك مفيدًا عند debugging أو عند استخدام رابط غير معروف.

في Java، يمكنك الحصول على وصف الواجهة باستخدام رمز مثل:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

في خلفية CPP:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

لا تتيح الخلفيات في NDK وRust هذه الميزة.

الحصول على واصف الواجهة بشكل ثابت

قد تحتاج أحيانًا (مثلاً عند تسجيل خدمات @VintfStability) إلى معرفة ماهية واصف الواجهة بشكل ثابت. في Java، يمكنك الحصول على الموصّف عن طريق إضافة رمز مثل:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

في الخلفية في NDK (لاحظ مساحة الاسم aidl الإضافية):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

في الخلفية في Rust:

    aidl::my::package::BnFoo::get_descriptor()

نطاق التعداد

في الخلفيات الأصلية، يمكنك تكرار القيم المحتملة التي يمكن أن تتّخذها التعداد. لا تتوفّر هذه الميزة في Java بسبب اعتبارات تتعلّق بحجم الرمز البرمجي.

بالنسبة إلى مصنّف MyEnum محدّد في AIDL، يتم توفير التكرار على النحو التالي.

في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:

    ::android::enum_range<MyEnum>()

في الخلفية في NDK:

   ::ndk::enum_range<MyEnum>()

في الخلفية في Rust:

    MyEnum::enum_values()

إدارة سلاسل المحادثات

تحافظ كلّ نسخة من libbinder في عملية على مجموعة واحدة من سلاسل المهام. في معظم حالات الاستخدام، يجب أن يكون هذا التجمع عبارة عن تجمع مؤشرات تسلسل واحد بالضبط، ويتمّت مشاركته في جميع الخلفيات. الاستثناء الوحيد هو عندما قد يحمِّل رمز المورّد نسخة أخرى من libbinder للتحدّث إلى /dev/vndbinder. ونظرًا لأن هذا موجود على عقدة رابط منفصلة، فلا تتم مشاركة threadpool.

بالنسبة إلى الخلفية في Java، يمكن فقط زيادة حجم تجمع المواضيع (لأنه قد بدأ من قبل):

    BinderInternal.setMaxThreads(<new larger value>);

بالنسبة إلى الخلفية في "شركاء المحتوى في خرائط Google"، تتوفّر العمليات التالية:

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

وبالمثل، في الخلفية في NDK:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

في الخلفية في Rust:

    binder::ProcessState::start_thread_pool();
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    binder::ProcessState::join_thread_pool();

باستخدام الخلفية غير المتزامنة في Rust، تحتاج إلى مجموعتَي خيوط: binder وTokio. وهذا يعني أنّ التطبيقات التي تستخدم async Rust تحتاج إلى اعتبارات خاصة، خاصةً عندما يتعلق الأمر باستخدام join_thread_pool. يُرجى الاطّلاع على القسم المخصص لتسجيل الخدمات للحصول على مزيد من المعلومات حول هذا الموضوع.

الأسماء المحجوزة

تحتفظ C++ وJava وRust بعض الأسماء ككلمات رئيسية أو للاستخدام الخاص بكل لغة. على الرغم من أنّ واجهة برمجة التطبيقات AIDL لا تفرض قيودًا استنادًا إلى قواعد اللغة، إلا أنّ استخدام اسم حقل أو نوع يتطابق مع اسم محجوز قد يؤدي إلى تعذُّر compiling لـ C++ أو Java. في Rust، تتم إعادة تسمية الحقل أو النوع باستخدام بنية نحو "المعرّف الأوّلي"، والتي يمكن الوصول إليها باستخدام البادئة r#.

ننصحك بتجنُّب استخدام الأسماء المحجوزة في تعريفات AIDL حيثما أمكن ذلك لتجنُّب عمليات الربط غير المريحة أو تعذُّر الترجمة تمامًا.

إذا كانت لديك أسماء محجوزة في تعريفات AIDL، يمكنك إعادة تسمية الحقول بأمان مع الحفاظ على التوافق مع البروتوكول. قد تحتاج إلى تعديل الرمز البرمجي لمواصلة عملية الإنشاء، ولكن ستستمر أي برامج تم إنشاؤها في التفاعل مع بعضها.

الأسماء التي يجب تجنُّبها: