خلفيات AIDL

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

في الجدول التالي، يشير ثبات سطح واجهة برمجة التطبيقات إلى إمكانية تجميع الرموز البرمجية مقابل مساحة عرض واجهة برمجة التطبيقات هذه بطريقة يمكن إرسال الرمز بها بشكل مستقل عن برنامج system.img libbinder.so الثنائي.

تحتوي لغة AIDL على الخلفيات التالية:

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

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

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

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

في أي وحدة من وحدات Android.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 مع واجهة AIDL الثابتة، يمكنك الاطّلاع على واجهة AIDL المنظَّمة مقابل AIDL.

الأنواع

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

نوع Java/AIDL النوع C++ نوع NDK نوع الصدأ
منطقية قيمة منطقية (Bool) قيمة منطقية (Bool) قيمة منطقية (Bool)
بايت int8_t int8_t i8
حرف 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 ما مِن مدة ما مِن مدة
بيندر android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::ect<T> std::ect<T> في: &[T]
الخروج: Vec<T>
بايت[] std::ect<uint8_t> std::vector<int8_t>1 في: &[u8]
الخروج: Vec<u8>
قائمة<T> std::الانتقال<T>2 std::الانتقال<T>3 في: &[T]4
الخروج: Vec<T>
واصف الملفات android::base::unique_fd ما مِن مدة binder::parcel::ParcelFileDescriptor
واصف ملف ParcelFile android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
نوع الواجهة (T) android::sp<T> std::shared_ptr<T>7 binder::قوي
نوع قطعة الأرض (T) T T T
نوع الاتحاد (T)5 T T T
ث[N] 6 std::array<T, N> std::array<T, N> [الشمال، "ن"]

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

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

3. تتوافق خلفية NDK مع List<T> حيث يكون T واحدًا من String أو ParcelFileDescriptor أو قابل للقطع. في 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\> تتم إدارته داخليًا أيضًا في حال كان الحافظ مملوكًا لعملية أخرى. ويؤدي إنشاء الكائن بطرق أخرى إلى الملكية المزدوجة.

الاتجاه (للداخل/الخارج/الداخل)

عند تحديد أنواع الوسيطات في الدوال، يمكنك تحديدها على النحو التالي: 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

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

القابلية للإلغاء

يمكنك إضافة تعليقات توضيحية إلى الأنواع التي يمكن أن تكون فارغة في واجهة Java باستخدام @nullable لعرض القيم الفارغة للواجهة الخلفية لكل من CPP وNDK. في خلفية Rust، تظهر هذه الأنواع من @nullable باسم Option<T>. ترفض الخوادم الأصلية القيم الفارغة بشكل افتراضي. الاستثناءات الوحيدة لذلك هي النوعان interface وIBinder، اللذَين يمكن أن يكونا دائمًا قيمة خالية لقراءات NDK وعمليات كتابة CPP/NDK. لمزيد من المعلومات حول تعليق 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 (الإصدار التجريبي من AOSP)، للإعلان عن منتج 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 لاستخدامها في union.

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

يمكن للعناصر الأساسية المنظَّمة تعريف القيم التلقائية لكل حقل للمواد الأوّلية وString والصفائف من هذه الأنواع.

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

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

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

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

يوفر نظام التشغيل 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;

أو في الواجهة الخلفية لخدمة CPP:

#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.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() { ... }
    }

في خلفية CPP:

    #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.
  • في خلفية CPP، استخدِم 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();

في خلفية CPP:

    ... = 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 للحصول على معرّف PID لخدمة أثناء تصحيح الأخطاء.

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

في خلفية Java:

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

في خلفية CPP:

    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، يمكنك الحصول على واصف الواجهة مع رمز مثل:

    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

في خلفية CPP:

    #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، يتم توفير التكرار على النحو التالي.

في خلفية CPP:

    ::android::enum_range<MyEnum>()

في خلفية NDK:

   ::ndk::enum_range<MyEnum>()

في خلفية Rust:

    MyEnum::enum_values()

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

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

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

    BinderInternal.setMaxThreads(<new larger value>);

بالنسبة إلى الواجهة الخلفية لخدمة CPP، تتوفّر العمليات التالية:

    // 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. ويعني هذا أنّ التطبيقات التي تستخدم Rust غير المتزامن بحاجة إلى اعتبارات خاصة، خاصةً في ما يتعلّق باستخدام join_thread_pool. راجِع القسم الخاص بتسجيل الخدمات للحصول على مزيد من المعلومات عن هذا الموضوع.

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

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

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

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

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