خلفيات AIDL

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

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

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

الخلفية اللغة مساحة عرض واجهة برمجة التطبيقات إنشاء الأنظمة
Java Java حزمة تطوير البرامج (SDK)/SystemApi (الإصدار الثابت*) الكل
كرونة دنماركية C++‎ libbinder_ndk (ثابت*) واجهة المساعد
تكلفة المكالمة الهاتفية C++‎ libbinder (غير ثابت) الكل
الصدأ (Rust) الصدأ (Rust) libbinder_rs (ثابت*) aidl_interface
  • مساحات عرض واجهة برمجة التطبيقات هذه مستقرة، ولكن العديد من واجهات برمجة التطبيقات، مثل تلك الخاصة بالخدمة إلا أنها مخصّصة لاستخدام النظام الأساسي الداخلي التطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات، يُرجى الاطّلاع على مستندات المطوّرين.
  • تم تقديم الخلفية Rust في الإصدار 12 من Android، وأصبحت ‎JDK الخلفية متاحة اعتبارًا من الإصدار 10 من Android.
  • تم بناء القفص الصدأ فوق "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 ثابتة، اطّلِع على واجهة 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 لا ينطبق لا ينطبق
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> الإدخال: &[T]
الإخراج: Vec<T>
‫byte[] std::vector<uint8_t> std::vector<int8_t>1 في: &[u8]
الخروج: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 الإدخال: &[T]4
الإخراج: Vec<T>
واصف الملفات 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::قوي
نوع parcelable (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 أو قطعة أرض. في الإصدار 13 من Android أو الإصدارات الأحدث، يمكن أن يكون T أي نوع غير أساسي باستثناء الصفائف.

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

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

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

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

الاتجاهية (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. لمزيد من المعلومات عن التعليق التوضيحي 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 قطع طرود مخصّصة لاستخدامها في 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 ولا يتم تحليلها بواسطة binder.

في 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
NDK android/binder_status.h
الصدأ (Rust) android/binder_status.h

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

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

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

سواء كان النوع المحدد واجهة أو قطعة أو اتحاد، يمكنك استيراد في 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;

على الرغم من أنه يمكنك استيراد نوع متداخل في جافا، إلا أنه في خلفيات 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() { ... }
    }

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

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

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

في الخلفية في 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<()>

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

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

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

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

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

    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. ونظرًا لأن هذا موجود على عقدة رابط منفصلة، فإن لم تتم مشاركة سلسلة محادثات.

بالنسبة إلى الخلفية في 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، يمكنك إجراء ذلك بأمان إعادة تسمية الحقول مع الاستمرار في التوافق مع البروتوكولات قد تحتاج إلى تحديث التعليمات البرمجية لمواصلة البناء، ولكن سيستمر أي برامج تم إنشاؤها التشغيل التفاعلي.

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