خلفيات AIDL

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

لدى AIDL الواجهات الخلفية التالية:

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

بناء الأنظمة

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

نظام البناء الأساسي

في أي وحدة cc_ أو java_ Android.bp (أو في معادلاتها 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 --lang=<backend> ... لرؤية ملف الواجهة الناتج. عند استخدام وحدة aidl_interface ، يمكنك عرض المخرجات في out/soong/.intermediates/<path to module>/ .

نوع جافا/AIDL النوع سي++ نوع إن دي كيه نوع الصدأ
منطقية منطقي منطقي منطقي
بايت int8_t int8_t i8
شار char16_t char16_t u16
كثافة العمليات int32_t int32_t i32
طويل int64_t int64_t i64
يطفو يطفو يطفو f32
مزدوج مزدوج مزدوج f64
خيط الروبوت::سلسلة16 الأمراض المنقولة جنسيا::سلسلة خيط
android.os.Parcelable الروبوت::لا يتجزأ لا يوجد لا يوجد
IBinder الروبوت::IBinder ndk::SpAIBinder الموثق::SpIBinder
ت[] الأمراض المنقولة جنسيا::ناقل<T> الأمراض المنقولة جنسيا::ناقل<T> في: &[ر]
خارج: فيك <T>
بايت[] الأمراض المنقولة جنسيا::ناقل<uint8_t> الأمراض المنقولة جنسيا::vector<int8_t> 1 في: &[u8]
الخروج: فيك <u8>
القائمة<T> الأمراض المنقولة جنسيا::ناقل<T> 2 الأمراض المنقولة جنسيا::ناقل<T> 3 في: &[ت] 4
خارج: فيك <T>
واصف الملف android::base::unique_fd لا يوجد Binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor Binder::parcel::ParcelFileDescriptor
نوع الواجهة (T) الروبوت::SP<T> الأمراض المنقولة جنسيا::shared_ptr<T> الموثق::قوي
نوع الطرود (T) ت ت ت
نوع الاتحاد (T) 5 ت ت ت
ت[ن] 6 الأمراض المنقولة جنسيا::صفيف<T, N> الأمراض المنقولة جنسيا::صفيف<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، يتم تمثيل المصفوفات ذات الحجم الثابت كأنواع مصفوفات.

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

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

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 مدعوم ولا يتم تحليلها بواسطة 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 مع الخطأ المحدد من قبل المستخدم.

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

الخلفية تعريف
جافا android/os/Parcel.java
تكلفة المكالمة الهاتفية binder/Status.h
NDK android/binder_status.h
الصدأ 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;
    }

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

    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(())
        }
    }

أو مع الصدأ غير المتزامن:

    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 الأساسي.

في جافا:

    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")));

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

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 عند استخدام async 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 لترك السياق غير المتزامن.

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

  • في 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 Binder، تتوفر معلومات المتصل في العديد من واجهات برمجة التطبيقات. يشير PID (أو معرف العملية) إلى معرف عملية Linux للعملية التي ترسل المعاملة. يشير UID (أو معرف المستخدم) إلى معرف مستخدم Linux. عند تلقي مكالمة أحادية الاتجاه، يكون معرف PID الخاص بالاستدعاء هو 0. وعندما تكون هذه الوظائف خارج سياق معاملة الموثق، فإنها ترجع معرف PID وUID للعملية الحالية.

في الواجهة الخلفية لجافا:

    ... = 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();

تقارير الأخطاء وتصحيح الأخطاء API للخدمات

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

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

في الواجهة الخلفية لجافا:

    @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

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

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

نطاق التعداد

في الواجهات الخلفية الأصلية، يمكنك تكرار القيم المحتملة التي يمكن أن يأخذها التعداد. نظرًا لاعتبارات حجم الكود، فإن هذا غير مدعوم في Java حاليًا.

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

في الواجهة الخلفية لـ CPP:

    ::android::enum_range<MyEnum>()

في الواجهة الخلفية لـ NDK:

   ::ndk::enum_range<MyEnum>()

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

    MyEnum::enum_values()

إدارة الموضوع

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

بالنسبة للواجهة الخلفية لـ 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();

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

    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 لا يفرض قيودًا بناءً على قواعد اللغة، فإن استخدام أسماء الحقول أو الأنواع التي تطابق اسمًا محجوزًا قد يؤدي إلى فشل التحويل البرمجي لـ C++ أو Java. بالنسبة لـ Rust، تتم إعادة تسمية الحقل أو النوع باستخدام بناء جملة "المعرف الأولي"، الذي يمكن الوصول إليه باستخدام البادئة r# .

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

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

الأسماء التي يجب تجنبها: * الكلمات الرئيسية لـ C++ * الكلمات الرئيسية لـ Java * الكلمات الرئيسية الصدأ