الخلفيات المستندة إلى لغة تعريف واجهة نظام Android

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

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

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

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

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

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

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

في أي cc_ أو java_ Android.bp module (أو في Android.mk المكافئ لهما)، يمكنك تحديد ملفات AIDL (.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_interface منظَّمة. لكي تكون الحزم قابلة للتحويل إلى تنسيق منظَّم، يجب أن تحتوي على حقول مباشرةً وألا تكون تعريفات لأنواع محدَّدة مباشرةً في اللغات المستهدَفة. لمعرفة كيفية توافق AIDL المنظَّمة مع AIDL الثابتة، يمكنك الاطّلاع على الفرق بين AIDL المنظَّمة وAIDL الثابتة.

الأنواع

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

نوع Java أو AIDL نوع C++‎ نوع NDK نوع الصدأ
boolean bool bool bool
byte8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string في: &str
خارج: 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 std::vector1 في: &[u8]
خارج: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 في: In: &[T]4
النتيجة: Vec<T>
FileDescriptor android::base::unique_fd لا ينطبق لا ينطبق
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. في نظام التشغيل 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 من نوع binder، استخدِم SharedRefBase::make\<My\>(... args ...). تنشئ هذه الدالة عنصر std::shared_ptr\<T\>، تتم إدارته داخليًا أيضًا، في حال كان binder مملوكًا لعملية أخرى. يؤدي إنشاء الكائن بطرق أخرى إلى ملكية مزدوجة.

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

الاتجاه (in وout وinout)

عند تحديد أنواع وسيطات الدوال، يمكنك تحديدها على أنّها in أو out أو inout. يتحكّم هذا الخيار في اتجاه تمرير المعلومات عند إجراء عملية استدعاء بين العمليات.

  • يشير محدّد وسيطة in إلى أنّه يتم تمرير البيانات من المتصل إلى المتصل به. المحدّد in هو الاتجاه التلقائي، ولكن إذا كان بإمكان أنواع البيانات أن تكون out أيضًا، عليك تحديد الاتجاه.

  • يعني محدّد وسيطة out أنّه يتم تمرير البيانات من الدالة المستدعاة إلى الدالة المستدعية.

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

UTF-8 وUTF-16

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

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

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

Custom parcelables

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

في ما يلي مثال على تعريف 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 على أنّها حقول ذات قيمة فارغة.

الاتحادات

يتم وضع علامات على اتحادات 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.setStringField("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

في 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 كـ const int أو intenum ولا يتم تحليلها بواسطة 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، راجِع مثال AIDL المستند إلى Rust في أنماط Android Rust.

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

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

import my.package.IFoo;

أو في الخلفية الخاصة ببرنامج "شركاء المحتوى المفضّلون":

#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).

تنفيذ واجهة

لتنفيذ واجهة، يجب أن ترث من فئة العنصر النائب الأصلية. يُطلق على تنفيذ واجهة اسم خدمة غالبًا عند تسجيلها لدى مدير الخدمة أو android.app.ActivityManager، ويُطلق عليها اسم دالة رد الاتصال عند تسجيلها من قِبل عميل إحدى الخدمات. ومع ذلك، يتم استخدام مجموعة متنوعة من الأسماء لوصف عمليات تنفيذ الواجهة، وذلك حسب الاستخدام الدقيق. تقرأ فئة العنصر النائب الأوامر من برنامج تشغيل الرابط وتنفّذ الطرق التي تنفّذها. لنفترض أنّ لديك ملف AIDL على النحو التالي:

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

في Java، يجب أن توسّع من فئة Stub التي تم إنشاؤها:

    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;
    }

في الخلفية الأصلية (لاحظ مساحة الاسم الإضافية 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"));

في الخلفية الأصلية (لاحظ مساحة الاسم الإضافية 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 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 يملك وظيفة معاودة الاتصال، عليك إبقاء هذا العنصر نشطًا طالما أردت تلقّي الإشعارات.

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

عند تلقّي طلب ربط في النواة، تتوفّر معلومات المتّصل في عدة واجهات برمجة تطبيقات. يشير معرّف العملية (PID) إلى معرّف عملية Linux التي ترسل معاملة. يشير معرّف المستخدم (UI) إلى معرّف مستخدم 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 أو حصرها على معرّفات UID معيّنة.

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

في الخلفية الخاصة بلغة CPP، يكون النوع الضعيف هو wp<IFoo>.

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

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

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

في 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

في الخلفية الأصلية (لاحظ مساحة الاسم الإضافية 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>);

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

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

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

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