لغة AIDL لـ HALs

يوفّر Android 11 إمكانية استخدام لغة AIDL الخاصة بأنظمة HALs في نظام Android. هذا يجعل من إمكانية تنفيذ أجزاء من Android بدون تحديد HIDL. نقل HALs إلى استخدام AIDL حصريًا حيثما أمكن (عندما تستخدم HALs الأولية HIDL، يجب استخدام HIDL).

تستخدم HALs AIDL للتواصل بين مكونات إطار العمل، مثل تلك الموجودة في يجب أن يستخدم system.img ومكونات الأجهزة، مثل هذه المتوفرة في vendor.img إصدار AIDL ثابت. ومع ذلك، يمكن للاتصال داخل أحد التقسيمات، على سبيل المثال، من خلال قسم إلى جانب بروتوكول HAL، ليس هناك أي قيود على استخدام آلية IPC.

الحافز

كانت لغة AIDL أطول من HIDL، وتستخدم في العديد من الأماكن الأخرى، مثل بين مكونات إطار عمل Android أو في التطبيقات الآن بعد أن أصبحت لغة AIDL مستقرة من الممكن، تنفيذ حزمة كاملة باستخدام بيئة تشغيل IPC واحدة. لدى AIDL أيضًا نظام تحديد إصدارات أفضل من HIDL.

  • إن استخدام لغة IPC واحدة يعني وجود شيء واحد فقط يجب تعلمه وتصحيح الأخطاء والتحسين والأمان.
  • تتيح AIDL لمالكي الواجهات إمكانية تحديد الإصدارات في المكان نفسه:
    • يمكن للمالكين إضافة طرق إلى نهاية الواجهات أو حقول إلى العناصر. وهذا يعني أنه من الأسهل إنشاء رمز الإصدار على مرّ السنين، وكذلك التاريخ تقل التكلفة مقارنةً بالعام السابق (يمكن تعديل الأنواع في مكانها، ولا يوجد الحاجة إلى مكتبات إضافية لكل إصدار واجهة).
    • يمكن إرفاق واجهات الإضافة في وقت التشغيل وليس في النوع لذلك لا حاجة إلى إعادة تحديد الإضافات التي تطوّرها إلى الإصدارات الأحدث من الواجهات.
  • يمكن استخدام واجهة AIDL الحالية مباشرةً عندما يختار مالكها استقرارها. في السابق، كان يجب نسخ نسخة كاملة من الواجهة تم إنشاؤها في HIDL.

الإنشاء استنادًا إلى بيئة تشغيل AIDL

تحتوي لغة AIDL على ثلاث خلفيات مختلفة: Java وNDK وCPP. لاستخدام الإصدار الثابت AIDL، يجب استخدام نسخة النظام من libbinder دائمًا من خلال system/lib*/libbinder.so والتحدّث في /dev/binder. بالنسبة إلى الرمز البرمجي الظاهر على صورة المورّد، يعني ذلك أنّ libbinder (من VNDK) لا يمكن استخدامها: تحتوي هذه المكتبة على واجهة برمجة تطبيقات C++ غير مستقرة داخلية غير مستقرة. وبدلاً من ذلك، يجب أن يستخدم رمز البائع الأصلي الواجهة الخلفية NDK AIDL، رابط إلى libbinder_ndk (المدعوم من النظام libbinder.so) والارتباط بمكتبات NDK التي تم إنشاؤها بواسطة إدخالات aidl_interface. بالنسبة أسماء الوحدات بالضبط، راجع قواعد تسمية الوحدات.

كتابة واجهة AIDL HAL

لاستخدام واجهة AIDL بين النظام والمورد، يجب أن تغييرين:

  • يجب إضافة تعليقات توضيحية لكل نوع من التعريفات باستخدام @VintfStability.
  • يجب أن يتضمّن بيان aidl_interface السمة stability: "vintf",.

ولا يمكن لأحد إجراء هذه التغييرات سوى مالك الواجهة.

عند إجراء هذه التغييرات، يجب أن تكون الواجهة في بيان VINTF لكي يعمل. اختبار هذا (وعبارات ذات صلة) مثل التحقق من تجميد الواجهات التي تم إصدارها) باستخدام اختبار VTS vts_treble_vintf_vendor_test. يمكنك استخدام @VintfStability بدون هذه المتطلبات من خلال استدعاء أي AIBinder_forceDowngradeToLocalStability في الواجهة الخلفية لـ NDK، android::Stability::forceDowngradeToLocalStability في خلفية C++، أو android.os.Binder#forceDowngradeToSystemStability في خلفية Java على كائن مُربط قبل إرساله إلى عملية أخرى. الرجوع إلى إصدار سابق من خدمة إلى استقرار عمل البائع غير مدعوم في Java، لأن جميع التطبيقات تعمل في نظام السياق.

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

تجدر الإشارة إلى أنّ استخدام backends في مثال الرمز الوارد أدناه صحيح، حيث إنّه هي ثلاث خلفيات (Java وNDK وCPP). يوضح الكود أدناه كيفية تحديد تعمل خلفية CPP على وجه التحديد لإيقافها.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

العثور على واجهات AIDL HAL

تتوفّر واجهات AOSP الثابتة AIDL الخاصة بـ HAL في الأدلة الأساسية نفسها مثل واجهات HIDL، في aidl مجلد.

  • الأجهزة/الواجهات
  • أُطر العمل/الأجهزة/الواجهات
  • النظام/الأجهزة/الواجهات

يجب وضع واجهات الإضافات في hardware/interfaces أخرى. الأدلة الفرعية في vendor أو hardware.

واجهات الإضافات

يوفّر Android مجموعة من واجهات AOSP الرسمية مع كل إصدار. عندما يعمل Android الشركاء لإضافة وظائف إلى هذه الواجهات، فيجب ألا تتغير مباشرةً لأن هذا يعني أن وقت تشغيل Android غير متوافق مع وقت تشغيل Android AOSP. بالنسبة إلى أجهزة GMS، قد يؤدي تجنُّب تغيير كما أن هذه الواجهات هي ما يضمن استمرار عمل صورة GSI.

يمكن تسجيل الإضافات بطريقتين مختلفتين:

  • في وقت التشغيل، يُرجى الاطّلاع على الإضافات المرفقة.
  • مستقلّة، ومسجّلة عالميًا وفي VINTF.

ومع ذلك، يتم تسجيل الامتداد، عندما يكون النطاق خاصًا بالمورّد (أي ليس جزءًا من (AOSP) التي تستخدم الواجهة، وليس هناك إمكانية للدمج نزاع. مع ذلك، عند إجراء تعديلات على المراحل الأولية لمكوّنات AOSP الأولية التي يمكن أن تنشأ، ويمكن أن تنشأ تعارضات الدمج، ويُنصَح باستخدام الإستراتيجيات التالية:

  • يمكن تطبيق إضافات الواجهة على AOSP في الإصدار التالي
  • إضافات واجهات توفر مزيدًا من المرونة، بدون تعارض عمليات الدمج، يمكن بثه في الإصدار التالي

عناصر الإضافات: ParcelableHolder

ParcelableHolder هو Parcelable ويمكن أن يحتوي على Parcelable آخر. إنّ حالة الاستخدام الرئيسية للسمة ParcelableHolder هي جعل Parcelable قابلاً للتوسُّع. على سبيل المثال، الصورة التي يتوقع القائمون على تنفيذ الأجهزة أن يتمكنوا من توسيع نطاق Parcelable AOSP الذي يتم تحديده، AospDefinedParcelable، لتضمين القيمة المضافة الجديدة.

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

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

كما هو موضح في التعليمة البرمجية السابقة، هذه الممارسة معطلة لأن الحقول التي أضافها مسؤول تنفيذ الجهاز في حال حدوث تعارض عندما يكون بالإمكان التي تمت مراجعتها في الإصدارات التالية من Android.

باستخدام ParcelableHolder، يمكن لمالك قطعة العنصر تعريف امتداد في Parcelable.

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

بعد ذلك، يمكن لجهات التنفيذ على الأجهزة تحديد Parcelable الخاصة بها. الإضافة.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

أخيرًا، يمكن إرفاق Parcelable الجديد بالعنصر Parcelable الأصلي باستخدام الحقل ParcelableHolder.


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

أسماء مثيلات خادم AIDL HAL

حسب الاصطلاح، تتضمَّن خدمات AIDL HAL اسم مثيل بالتنسيق $package.$type/$instance فعلى سبيل المثال، هناك مثال على HAL للهزاز تم التسجيل باسم android.hardware.vibrator.IVibrator/default.

كتابة خادم AIDL HAL

يجب الإعلان عن @VintfStability خوادم AIDL في بيان VINTF، من أجل مثال مثل هذا:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

وبخلاف ذلك، يجب عليهم تسجيل خدمة AIDL بشكل طبيعي. عند تشغيل VTS من المتوقع أن تكون جميع درجات HALs المُعلَن عنها في AIDL.

كتابة عميل AIDL

يجب أن تعلن عملاء AIDL عن أنفسهم في مصفوفة التوافق، على سبيل المثال: النحو التالي:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

تحويل HAL حالي من HIDL إلى AIDL

استخدِم أداة hidl2aidl لتحويل واجهة HIDL إلى تنسيق AIDL.

hidl2aidl ميزات:

  • أنشئ ملفات .aidl استنادًا إلى ملفات .hal للحزمة المحدّدة.
  • إنشاء قواعد إصدار لحزمة AIDL التي تم إنشاؤها حديثًا مع جميع الخلفيات مفعّل
  • إنشاء طرق ترجمة في خلفيات Java وCPP وNDK للترجمة من أنواع HIDL إلى أنواع AIDL
  • إنشاء قواعد تصميم لترجمة المكتبات ذات التبعيات المطلوبة
  • إنشاء تأكيدات ثابتة للتأكد من أنّ أدوات عدّاد HIDL وAIDL القيم نفسها في الخلفيات الخاصة بكل من CPP وNDK

اتّبِع الخطوات التالية لتحويل حزمة من ملفات .hal إلى ملفات .aidl:

  1. أنشئ الأداة المتوفرة في system/tools/hidl/hidl2aidl.

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

    m hidl2aidl
    
  2. نفذ الأداة باستخدام دليل إخراج متبوعًا بالحزمة المطلوب تم تحويله.

    يمكنك استخدام الوسيطة -l بشكل اختياري لإضافة محتوى ملف ترخيص جديد. في أعلى كل الملفات التي تم إنشاؤها. تأكَّد من استخدام الترخيص والتاريخ الصحيحَين.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    مثلاً:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. اقرأ الملفات التي تم إنشاؤها وأصلح أي مشاكل في الإحالة الناجحة.

    • يحتوي "conversion.log" على أي مشاكل لم يتم حلّها أولاً.
    • قد تحتوي الملفات التي تم إنشاؤها (.aidl) على تحذيرات واقتراحات قد تكون بحاجة إلى اتخاذ إجراء. تبدأ هذه التعليقات بـ //.
    • اغتنم الفرصة لتنظيف وإجراء تحسينات على الحزمة.
    • اطّلِع على @JavaDerive. التعليق التوضيحي للميزات التي قد تكون مطلوبة، مثل toString أو equals
  4. أنشئ الأهداف التي تحتاجها فقط.

    • إيقاف الواجهات الخلفية التي لن يتم استخدامها تفضيل الواجهة الخلفية NDK على صفحة المنتج في خدمة مقارنة الأسعار الخلفية، راجع اختيار بيئة التشغيل.
    • إزالة مكتبات الترجمة أو أي رمز برمجي تم إنشاؤه ولن يتم استخدامه
  5. اطّلِع على الاختلافات الرئيسية في AIDL/HIDL.

    • يؤدي عادةً استخدام ميزة "Status" المضمّنة في AIDL والاستثناءات إلى تحسين من واجهة المستخدم وعدم الحاجة إلى نوع حالة آخر متعلق بالواجهة.
    • إنّ وسيطات واجهة AIDL في الطرق ليست @nullable تلقائيًا كما هي. كانت في HIDL.

سياسة SEPolicy لـ AIDL HALs

إنّ نوع خدمة AIDL المرئي لرمز المورّد يجب أن يتضمّن hal_service_type. وبخلاف ذلك، تكون تهيئة sepolicy هي نفسها مثل أي خدمة AIDL أخرى (على الرغم من وجود سمات خاصة لـ HALs). هنا فيما يلي مثال على تعريف سياق خدمة HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

بالنسبة إلى معظم الخدمات التي تحدّدها المنصة، يكون سياق الخدمة صحيحًا النوع مضاف بالفعل (على سبيل المثال، قد يكون android.hardware.foo.IFoo/default سبق أن تم وضع علامة hal_foo_service عليها). ولكن إذا كان عميل إطار العمل يدعم أسماء مثيلات متعددة، يجب إضافة أسماء المثيلات الإضافية في service_contexts الخاصة بالجهاز.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

يجب إضافة سمات HAL عند إنشاء نوع جديد من HAL. طبقة تجريد الأجهزة (HAL) محددة بأنواع خدمات متعددة (قد يرتبط كل منها بأنواع خدمات) حالات متعددة كما ناقشنا للتو). بالنسبة إلى HAL، foo، لدينا hal_attribute(foo) تحدِّد وحدة الماكرو هذه السمات hal_foo_client hal_foo_server بالنسبة إلى نطاق معيّن، لا يمكن تغيير hal_client_domain تربط وحدات ماكرو hal_server_domain أحد النطاقات بسمة HAL معيّنة. بالنسبة على سبيل المثال، يتجاوب خادم النظام كونه عميلاً لـ HAL هذا مع السياسة hal_client_domain(system_server, hal_foo) يتضمن خادم HAL بالمثل hal_server_domain(my_hal_domain, hal_foo) عادةً، بالنسبة إلى HAL معين، ننشئ أيضًا نطاقًا مثل hal_foo_default كمرجع أو مثل HALs. ومع ذلك، تستخدم بعض الأجهزة هذه النطاقات لخوادمها الخاصة. يُعد التمييز بين النطاقات للخوادم المتعددة أمرًا مهمًا فقط إذا كان لدينا خوادم متعددة تعمل على الواجهة نفسها وتحتاج إلى إذن مختلف محددة في آلياتها. في جميع وحدات الماكرو هذه، لا يعتبر hal_foo كائن sepolicy. وبدلاً من ذلك، تستخدم وحدات الماكرو هذه هذا الرمز للإشارة إلى مجموعة السمات المرتبطة بزوج من خادم العميل.

ومع ذلك، لم نربط hal_foo_service وhal_foo حتى الآن. (زوج السمة من hal_attribute(foo)). يتم ربط سمة HAL. مع خدمات AIDL HAL باستخدام وحدة ماكرو hal_attribute_service (تستخدم HIDL HALs وحدة الماكرو hal_attribute_hwservice). على سبيل المثال: hal_attribute_service(hal_foo, hal_foo_service) هذا يعني أنّ بإمكان عمليات hal_foo_client الاحتفاظ بـ HAL، وhal_foo_server العمليات تسجيل HAL. يُعد تنفيذ قواعد التسجيل هذه تم إجراؤها بواسطة مدير السياق (servicemanager). لاحظ أن أسماء الخدمات قد لا تتطابق دائمًا مع سمات HAL. على سبيل المثال، قد نرى hal_attribute_service(hal_foo, hal_foo2_service) ومع ذلك، بشكل عام، نظرًا يشير ذلك إلى أنّ الخدمات يتم استخدامها معًا دائمًا، فيمكننا التفكير في إزالة hal_foo2_service واستخدام hal_foo_service لكل خدمتنا والسياقات. يعود سبب معظم قيم HALs التي تضبط عناصر hal_attribute_service المتعددة إلى أنّ: اسم تصنيف HAL الأصلي ليس عامًا بشكلٍ كافٍ ولا يمكن تغييره.

بوضع كل ذلك معًا، يبدو لنا مثال على HAL كما يلي:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

واجهات الإضافات المرفقة

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

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

لضبط إضافة على المربط، استخدم واجهات برمجة التطبيقات التالية:

  • في خلفية NDK: AIBinder_setExtension
  • في خلفية Java: android.os.Binder.setExtension
  • في خلفية CPP: android::Binder::setExtension
  • في خلفية Rust: binder::Binder::set_extension

للحصول على إضافة على ملف ربط، استخدِم واجهات برمجة التطبيقات التالية:

  • في خلفية NDK: AIBinder_getExtension
  • في خلفية Java: android.os.IBinder.getExtension
  • في خلفية CPP: android::IBinder::getExtension
  • في خلفية Rust: binder::Binder::get_extension

يمكنك العثور على مزيد من المعلومات حول واجهات برمجة التطبيقات هذه في وثائق getExtension في الواجهة الخلفية المقابلة. مثال على كيفية استخدام يمكن العثور على الإضافات في الأجهزة/الواجهات/الاختبارات/الإضافة/الهزاز.

الاختلافات الرئيسية في AIDL وHIDL

عند استخدام AIDL HALs أو استخدام واجهات AIDL HAL، يجب الانتباه إلى الاختلافات مقارنة بكتابة HIDL HALs.

  • بنية لغة AIDL أقرب إلى Java. بناء جملة HIDL مشابه لـ C++.
  • تحتوي جميع واجهات AIDL على حالات خطأ مضمّنة. بدلاً من إنشاء واجهة برمجة تطبيقات مخصَّصة أنواع الحالات، وإنشاء عدد صحيحات حالة ثابتة في ملفات الواجهة واستخدام EX_SERVICE_SPECIFIC في خلفيات CPP/NDK وServiceSpecificException في الواجهة الخلفية لـ Java. عرض الخطأ التعامل مع الجهاز:
  • لا تبدأ AIDL تلقائيًا مجموعات سلاسل المحادثات عند إرسال عناصر الربط. يجب بدء تشغيلها يدويًا (راجع سلسلة محادثات إدارة المشروعات).
  • لا يتم إيقاف AIDL في أخطاء النقل التي لم يتم التحقّق منها (يتم إيقاف HIDL Return في الأخطاء التي لم يتم التحقق منها).
  • يمكن لـ AIDL تعريف نوع واحد فقط لكل ملف.
  • يمكن تحديد وسيطات AIDL مثل "داخل/خارج/خارج" بالإضافة إلى الناتج (لا توجد "استدعاءات متزامنة").
  • تستخدم AIDL fd كنوع أساسي بدلاً من الاسم المعرِّف.
  • يستخدم HIDL الإصدارات الرئيسية للتغييرات غير المتوافقة والإصدارات الثانوية التغييرات المتوافقة. في AIDL، يتم تنفيذ التغييرات المتوافقة مع الإصدارات القديمة. لا تتضمّن لغة AIDL مفهومًا صريحًا للإصدارات الرئيسية. بدلاً من ذلك، تكون مدمجة في أسماء الحزم. على سبيل المثال، قد تستخدم AIDL اسم الحزمة. bluetooth2
  • لا تكتسب لغة AIDL الأولوية تلقائيًا في الوقت الفعلي. setInheritRt يجب استخدام الدالة لكل مُجلِّد لتفعيل اكتساب الأولوية في الوقت الفعلي.