لغة AIDL ثابتة

يُضيف Android 10 دعمًا للغة تعريف واجهة Android الثابتة (AIDL)، وهي طريقة جديدة لتتبُّع واجهة برنامج التطبيق (API)/الواجهة الثنائية للتطبيق (ABI) التي توفّرها واجهات AIDL. تتميّز لغة AIDL الثابتة بالاختلافات الرئيسية التالية عن لغة AIDL:

  • يتم تحديد الواجهات في نظام الإصدار باستخدام aidl_interfaces.
  • يمكن أن تحتوي الواجهات على بيانات منظَّمة فقط. يتم إنشاء قطع الأراضي التي تمثل الأنواع المطلوبة تلقائيًا بناءً على تعريف AIDL ويتم تنظيمها وترتيبها تلقائيًا.
  • يمكن تعريف الواجهات على أنّها مستقرة (متوافقة مع الأنظمة القديمة). وعندما يحدث ذلك، يتم تعقب واجهة برمجة التطبيقات الخاصة بهم وإصدارها في ملف بجوار واجهة AIDL.

لغة AIDL مهيكلة مقابل ثابتة

تشير لغة AIDL المركّبة إلى أنواع محدّدة في لغة AIDL فقط. على سبيل المثال، لا يكون تعريف الوحدات الأساسية (عنصر قابل للنقل) بترميز AIDL منظَّم. يُطلق على قطع الأراضي التي تم تعريف حقولها في AIDL اسم قِطع الأراضي المنظَّمة.

تتطلب واجهة AIDL الثابتة لغة AIDL منظَّمة كي يتمكّن نظام التصميم والمحول البرمجي من فهم ما إذا كانت التغييرات التي يتم إجراؤها على العناصر متوافقة مع الأنظمة القديمة. ومع ذلك، ليست كل الواجهات المنظَّمة مستقرة. لكي تكون الواجهة مستقرة، يجب أن تستخدم الأنواع المهيكلة فقط، كما يجب أن تستخدم ميزات الإصدارات التالية. في المقابل، تكون الواجهة غير مستقرة إذا تم استخدام نظام التصميم الأساسي لإنشائها أو إذا تم ضبط unstable:true.

تعريف واجهة AIDL

يبدو تعريف aidl_interface على النحو التالي:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name: هو اسم وحدة واجهة AIDL التي تعرّف واجهة AIDL بشكل فريد.
  • srcs: قائمة ملفات مصدر AIDL التي تنشئ الواجهة يجب أن يكون مسار النوع Foo AIDL المحدد في الحزمة com.acme هو <base_path>/com/acme/Foo.aidl، حيث يمكن أن يكون <base_path> أي دليل مرتبط بالدليل الذي يتضمن Android.bp. وفي المثال أعلاه، <base_path> هو srcs/aidl.
  • local_include_dir: المسار الذي يبدأ منه اسم الحزمة ويتوافق مع <base_path> الموضّحة أعلاه.
  • imports: قائمة تضم aidl_interface وحدة تستخدمها هذه الوحدة. إذا كانت إحدى واجهات AIDL تستخدم واجهة أو جزءًا من عنصر aidl_interface آخر، يمكنك إضافة اسمها هنا. ويمكن أن يكون هذا الاسم هو نفسه للإشارة إلى أحدث نسخة، أو الاسم الذي يحتوي على لاحقة الإصدار (مثل -V1) للإشارة إلى نسخة معيّنة. أصبح تحديد إصدار متاحًا بدءًا من Android 12
  • versions: الإصدارات السابقة من الواجهة التي تم تجميدها ضمن api_dir، وبدءًا من الإصدار 11 من نظام التشغيل Android، يتم تجميد versions ضمن aidl_api/name. وإذا لم تكن هناك إصدارات مجمّدة من الواجهة، لا ينبغي تحديد ذلك، ولن تكون هناك عمليات تحقق من التوافق. تم استبدال هذا الحقل بـ versions_with_info للنطاق (13) أو أعلى.
  • versions_with_info: قائمة الصفوف، يحتوي كلٌ منها على اسم النسخة المجمدة وقائمة مع نُسخ من نُسخ وحدات aidl_interface الأخرى التي استوردها هذا الإصدار من aidl_interface. يمكنك العثور على تعريف الإصدار V من واجهة AIDL من IFACE على aidl_api/IFACE/V. تم تقديم هذا الحقل في نظام التشغيل Android 13، ولا يُفترض أن يتم تعديله في Android.bp مباشرةً. تتم إضافة الحقل أو تعديله من خلال استدعاء *-update-api أو *-freeze-api. بالإضافة إلى ذلك، يتم نقل حقول versions تلقائيًا إلى versions_with_info عندما يستدعي المستخدم *-update-api أو *-freeze-api.
  • stability: العلامة الاختيارية التي توفر وعدًا بالثبات لهذه الواجهة. يمكن حاليًا استخدام "vintf" فقط. في حال ترك سياسة stability بدون ضبط، سيتحقّق نظام التصميم من أنّ الواجهة متوافقة مع الإصدارات القديمة ما لم يتم تحديد unstable. فعدم ضبط البيانات يتجاوب مع واجهة تتميّز بثبات في سياق التجميع هذا (أي كل عناصر النظام أو على المثيل في system.img والأقسام ذات الصلة) أو جميع عناصر المورِّدين، مثل العناصر في vendor.img والأقسام ذات الصلة. يتجاوب هذا مع وعد ضبط stability على "vintf"، إذ إنّه يجب الحفاظ على استقرار الواجهة ما دامت مُستخدمة.
  • gen_trace: العلامة الاختيارية لتفعيل التتبُّع أو إيقافه. بدءًا من نظام التشغيل Android 14، يكون الإعداد التلقائي هو true للخلفية cpp وjava.
  • host_supported: هي علامة اختيارية تتيح عند ضبط هذه السياسة على true أن تصبح المكتبات التي تم إنشاؤها متاحة للبيئة المضيفة.
  • unstable: العلامة الاختيارية المستخدمة للإشارة إلى أنّ هذه الواجهة لا تحتاج إلى أن تكون مستقرة. عند ضبط هذه السياسة على true، لا ينشئ نظام التصميم تفريغ واجهة برمجة التطبيقات للواجهة ولا يتطلّب تحديثها.
  • frozen: تشير العلامة الاختيارية التي عند ضبطها على true إلى أنّ الواجهة لا تتضمّن أي تغييرات منذ الإصدار السابق للواجهة. يتيح ذلك المزيد من عمليات التحقق في وقت الإصدار. عند ضبط هذه السياسة على false، يعني ذلك أنّ الواجهة قيد التطوير وتتضمّن تغييرات جديدة، لذا سيؤدي تشغيل foo-freeze-api إلى إنشاء إصدار جديد وتغيير القيمة تلقائيًا إلى true. تم طرح هذه الميزة في Android 14
  • backend.<type>.enabled: تعمل هذه العلامات على تبديل كل من الخلفيات التي ينشئ المحول البرمجي AIDL رموزًا لها. في الوقت الحالي، يتم دعم أربع خلفيات: Java وC++ وNDK وRust. يتم تمكين خلفيات Java وC++ وNDK بشكل افتراضي. إذا لم تكن هناك حاجة إلى أي من هذه الخلفيات الثلاث، يجب إيقافها صراحةً. يتم إيقاف Rust تلقائيًا إلى أن يعمل نظام Android 15 (إصدار AOSP التجريبي).
  • backend.<type>.apex_available: قائمة بأسماء APEX التي تتوفر لها مكتبة الرموز الموجزة التي تم إنشاؤها.
  • backend.[cpp|java].gen_log: العلامة الاختيارية التي تتحكّم في ما إذا كان سيتم إنشاء رمز إضافي لجمع المعلومات حول المعاملة.
  • backend.[cpp|java].vndk.enabled: العلامة الاختيارية لجعل هذه الواجهة جزءًا من VNDK. القيمة التلقائية هي false.
  • backend.[cpp|ndk].additional_shared_libraries: تم طرح هذه العلامة في الإصدار 14 من نظام التشغيل Android، وتضيف تبعيات إلى المكتبات الأصلية. هذه العلامة مفيدة مع ndk_header وcpp_header.
  • backend.java.sdk_version: العلامة الاختيارية لتحديد إصدار حزمة SDK التي تم إنشاء مكتبة كعب Java استنادًا إليها. والطريقة التلقائية هي "system_current". ولا يجب ضبط هذه السمة في حال ضبط السمة backend.java.platform_apis على "صحيح".
  • backend.java.platform_apis: هي العلامة الاختيارية التي يجب ضبطها على true عندما تتطلّب المكتبات التي تم إنشاؤها إنشاءها وفق واجهة برمجة تطبيقات النظام الأساسي بدلاً من حزمة تطوير البرامج (SDK).

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

كتابة ملفات AIDL

تشبه الواجهات في AIDL الثابتة الواجهات التقليدية، باستثناء أنه لا يُسمح لها باستخدام عناصر غير منظَّمة (لأنّ هذه العناصر غير مستقرة. يمكنك الاطّلاع على واجهة AIDL المنظَّمة مقابل AIDL). الاختلاف الأساسي في لغة AIDL الثابتة هو في كيفية تعريف العناصر. في السابق، كان يتم الإعلان عن الوحدات السكنية للأمام، أمّا في لغة AIDL الثابتة (وبالتالي المهيكلة)، فيتم تحديد حقول والمتغيّرات الخاصة بالعقارات بشكل صريح.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

يتوفّر حاليًا خيار تلقائي (ولكنه غير مطلوب) لكل من boolean وchar وfloat وdouble وbyte وint وlong وString. في نظام Android 12، تتوفر أيضًا الإعدادات التلقائية للتعدادات التي يحددها المستخدم. عند عدم تحديد قيمة تلقائية، يتم استخدام قيمة تشبه 0 أو فارغة. يتم إعداد عمليات التعداد التي لا تتضمن قيمة تلقائية على 0 حتى في حال عدم وجود تعداد صفري.

استخدام مكتبات الكعب

بعد إضافة مكتبات التوازي كتبعية إلى الوحدة النمطية الخاصة بك، يمكنك تضمينها في ملفاتك. في ما يلي أمثلة لمكتبات التنويهات الموجزة في نظام التصميم (يمكن استخدام Android.mk أيضًا لتعريفات الوحدات القديمة):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

مثال في لغة C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

مثال في Java:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

مثال في Rust:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

واجهات تحديد الإصدارات

يؤدي تعريف وحدة باسم foo إلى إنشاء هدف في نظام الإصدار يمكنك استخدامه لإدارة واجهة برمجة التطبيقات للوحدة. عند إنشاء واجهة foo-تجميد-api، تتم إضافة تعريف جديد لواجهة برمجة التطبيقات ضمن api_dir أو aidl_api/name، بناءً على إصدار Android، كما ستضيف ملف .hash يمثل الإصدار المجمّد حديثًا من الواجهة. كما تعدّل foo-free-api السمة versions_with_info لعرض الإصدار الإضافي وimports للإصدار. بشكل أساسي، يتم نسخ imports في versions_with_info من الحقل imports. ولكن تم تحديد أحدث إصدار ثابت في imports في versions_with_info لعملية الاستيراد التي لا تتضمّن إصدارًا صريحًا. بعد تحديد السمة versions_with_info، ينفِّذ نظام الإصدار عمليات التحقّق من التوافق بين الإصدارات المجمّدة وأيضًا بين "أعلى شجرة" (ToT) وأحدث إصدار مجمّد.

بالإضافة إلى ذلك، عليك إدارة تعريف واجهة برمجة التطبيقات لإصدار ToT. عند تعديل واجهة برمجة التطبيقات، شغِّل foo-update-api للتحديث aidl_api/name/current الذي يحتوي على تعريف واجهة برمجة التطبيقات لإصدار ToT.

وللحفاظ على استقرار الواجهة، يمكن للمالكين إضافة عناصر جديدة:

  • الطُرق حتى نهاية الواجهة (أو الطرق ذات التسلسلات التسلسلية الجديدة المحددة بوضوح)
  • العناصر في نهاية عنصر مكون من قِطع (تتطلّب إضافة عنصر تلقائي إلى كل عنصر)
  • القيم الثابتة
  • في Android 11، أدوات التعداد
  • في Android 12، الحقول التي تصل إلى نهاية الاتحاد

ولا يُسمح بأي إجراءات أخرى، ولا يمكن لأي شخص آخر تعديل الواجهة (وإلا قد يخاطر بالاصطدام بالتغييرات التي يجريها المالك).

لاختبار تجميد جميع الواجهات لإصدارها، يمكنك الإنشاء باستخدام مجموعة المتغيرات البيئية التالية:

  • AIDL_FROZEN_REL=true m ...: يتطلب الإصدار تجميد جميع واجهات AIDL الثابتة التي لا تتضمّن حقل owner: محدّدًا.
  • AIDL_FROZEN_OWNERS="aosp test": يتطلب الإصدار تجميد جميع واجهات AIDL الثابتة باستخدام الحقل owner: المحدَّد على أنّه "aosp" أو "test".

استقرار الواردات

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

في رمز نظام Android الأساسي، يُعد android.hardware.graphics.common أكبر مثال على هذا النوع من ترقية الإصدار.

استخدام واجهات ذات إصدارات

طرق الواجهة

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

  • خلفية cpp تحصل على ::android::UNKNOWN_TRANSACTION.
  • خلفية ndk تحصل على STATUS_UNKNOWN_TRANSACTION.
  • تحصل الواجهة الخلفية java على android.os.RemoteException مع رسالة تفيد بأنّه لم يتم تنفيذ واجهة برمجة التطبيقات.

للحصول على استراتيجيات للتعامل مع هذا الأمر، راجِع إصدارات طلب البحث واستخدام الإعدادات التلقائية.

قطع قِطع الأراضي

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

من المفترَض ألّا يتوقع العملاء أن تستخدم الخوادم الحقول الجديدة ما لم يعلموا أنّ الخادم ينفّذ الإصدار المحدّد في الحقل (يمكنك الاطّلاع على إصدارات طلب البحث).

قيم التعداد والثوابت

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

الاتحادات

تخفق محاولة إرسال اتحاد مع حقل جديد إذا كان المُستلِم قديمًا ولا يعرف الحقل. لن يشهد التنفيذ أبدًا اتحاد الحقل الجديد. يتم تجاهل عملية الإخفاق إذا كانت معاملة أحادية الاتجاه، وبخلاف ذلك، سيكون الخطأ BAD_VALUE(بالنسبة إلى الواجهة الخلفية لـ C++ أو NDK) أو IllegalArgumentException(لخلفية Java). يظهر الخطأ إذا كان العميل يرسل اتحادًا تم تعيينه إلى الحقل الجديد إلى خادم قديم، أو عندما يكون برنامجًا قديمًا يتلقى الاتحاد من خادم جديد.

التطوير المستند إلى العلم

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

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

علامة إصدار AIDL

العلامة التي تتحكم في هذا السلوك هي RELEASE_AIDL_USE_UNFROZEN محددة في build/release/build_flags.bzl. تعني العلامة true أنّ النسخة غير المجمدة من الواجهة يتم استخدامها في وقت التشغيل، وfalse تعني أنّ كل مكتبات الإصدارات غير المجمّدة تعمل بالطريقة نفسها التي تتّبعها آخر نسخة مجمّدة. يمكنك إلغاء العلامة true للتطوير المحلي، ولكن عليك إعادتها إلى false قبل إصدارها. تتم عادةً عملية التطوير من خلال إعدادات تم ضبط العلامة على true فيها.

مصفوفة التوافق وبيان التوافق

تحدد كائنات واجهة البائع (كائنات VINTF) الإصدارات المتوقعة والإصدارات المتوفرة على أي من جانبي واجهة البائع.

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

المصفوفات

تتم إضافة الواجهات التي يملكها الشركاء إلى مصفوفة التوافق الخاصة بالجهاز أو الخاصة بالمنتج والتي يستهدفها الجهاز أثناء التطوير. وبالتالي، عند إضافة إصدار جديد غير مجمّد من الواجهة إلى مصفوفة توافق، يجب أن تظل الإصدارات المجمّدة السابقة متوفّرة في RELEASE_AIDL_USE_UNFROZEN=false. ويمكنك التعامل مع ذلك باستخدام ملفات مصفوفة توافق مختلفة لتهيئات RELEASE_AIDL_USE_UNFROZEN مختلفة أو السماح بكلا الإصدارين في ملف مصفوفة توافق واحد يُستخدم في جميع الإعدادات.

على سبيل المثال، عند إضافة إصدار 4 غير مجمّد، استخدِم <version>3-4</version>.

عند تجميد الإصدار 4، يمكنك إزالة الإصدار 3 من مصفوفة التوافق لأنّ الإصدار 4 المجمّد يُستخدم عندما تكون قيمة RELEASE_AIDL_USE_UNFROZEN هي false.

ملفات البيانات

في Android 15 (الإصدار التجريبي من AOSP)، تم إجراء تغيير في libvintf لتعديل ملفات البيان في وقت الإصدار بناءً على قيمة RELEASE_AIDL_USE_UNFROZEN.

توضّح البيانات وأجزاء البيان إصدار الواجهة التي تنفّذها الخدمة. عند استخدام أحدث إصدار غير مجمّد من الواجهة، يجب تحديث البيان ليعكس هذا الإصدار الجديد. عند ضبط RELEASE_AIDL_USE_UNFROZEN=false، يتم تعديل إدخالات البيان من خلال libvintf لتعكس التغيير في مكتبة AIDL التي تم إنشاؤها. تم تعديل الإصدار من الإصدار غير المجمّد، N، إلى آخر إصدار مجمد N - 1. وبالتالي، لا يحتاج المستخدمون إلى إدارة عدة ملفات بيانات أو أجزاء من البيانات لكل خدمة من خدماتهم.

التغييرات في برنامج HAL

يجب أن يكون رمز عميل HAL متوافقًا مع الأنظمة القديمة مع كل إصدار مجمد متوافق سابق. عندما تكون قيمة الحقل "RELEASE_AIDL_USE_UNFROZEN" هي false، تكون الخدمات دائمًا شبيهة بآخر نسخة ثابتة أو أقدم (على سبيل المثال، يؤدي استدعاء طرق جديدة غير مجمّدة إلى عرض UNKNOWN_TRANSACTION، أو أن تكون للحقول parcelable الجديدة القيم التلقائية). يجب أن تكون برامج إطار عمل Android متوافقة مع الإصدارات السابقة الإضافية، ولكن يجب أن نوضّح هذه التفاصيل الجديدة لعملاء المورّدين وعملاء الواجهات التي يملكها الشركاء.

التغييرات في تنفيذ HAL

يتمثل الاختلاف الأكبر في تطوير HAL مع التطوير المستند إلى العلامات في ضرورة أن تكون عمليات تنفيذ HAL متوافقة مع الإصدارات القديمة من آخر نسخة مجمّدة لتعمل عند ضبط RELEASE_AIDL_USE_UNFROZEN على false. يعد التفكير في التوافق مع الأنظمة القديمة في عمليات التنفيذ ورمز الجهاز تمرينًا جديدًا. راجِع استخدام الواجهات ذات الإصدارات.

اعتبارات التوافق مع الأنظمة القديمة هي نفسها بشكل عام بالنسبة إلى العملاء والخوادم، ولرمز إطار العمل ورمز المورد، ولكن هناك اختلافات طفيفة يجب أن تكون على دراية بها، نظرًا لأنك تنفِّذ الآن بشكل فعّال نسختين تستخدمان رمز المصدر نفسه (الإصدار الحالي غير المجمّد).

مثال: للواجهة ثلاثة إصدارات مجمّدة. يتم تحديث الواجهة بطريقة جديدة. ويتم تحديث كل من البرنامج والخدمة لاستخدام مكتبة الإصدار 4 الجديد. إنّ مكتبة V4 تستند إلى إصدار غير مجمّد من الواجهة، فهي تتصرف مثل آخر إصدار مجمّد، أي الإصدار 3، عندما تكون قيمة RELEASE_AIDL_USE_UNFROZEN هي false، وتمنع استخدام الطريقة الجديدة.

عند تجميد الواجهة، تستخدم جميع قيم RELEASE_AIDL_USE_UNFROZEN تلك النسخة المجمدة، ويمكن إزالة الرمز الذي يعالج التوافق مع الأنظمة القديمة.

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

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

قد لا تتوفّر الحقول الجديدة في الأنواع الحالية (parcelable وenum وunion) أو قد تحتوي على قيمها التلقائية عندما تكون قيمة RELEASE_AIDL_USE_UNFROZEN هي false ويتم تجاهل قيم الحقول الجديدة التي تحاول الخدمة إرسالها أثناء الخروج من العملية.

لا يمكن إرسال أو استلام الأنواع الجديدة المضافة في هذا الإصدار غير المجمّد من خلال الواجهة.

لا تطلب عملية التنفيذ أبدًا استخدام طرق جديدة من أي برنامج عندما تكون قيمة RELEASE_AIDL_USE_UNFROZEN هي false.

احرص على عدم استخدام التعداد الجديد إلا مع الإصدار الذي تم تقديمه فيه، وليس الإصدار السابق.

يتم عادةً استخدام foo->getInterfaceVersion() لمعرفة الإصدار الذي تستخدمه الواجهة البعيدة. ومع ذلك، مع دعم الإصدارات المستندة إلى العلامات، يتم تنفيذ إصدارين مختلفين، لذا قد ترغب في الحصول على إصدار الواجهة الحالية. يمكنك إجراء ذلك من خلال الحصول على إصدار الواجهة من العنصر الحالي، مثل this->getInterfaceVersion()، أو الطرق الأخرى الخاصة بـ my_ver. راجِع الاستعلام عن إصدار الواجهة للعنصر البعيد للحصول على مزيد من المعلومات.

واجهات VINTF الثابتة الجديدة

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

يمكنك إضافة الخدمات بشكل مشروط استنادًا إلى قيمة العلامة RELEASE_AIDL_USE_UNFROZEN في ملف makefile الخاص بالجهاز:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

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

الحبار كأداة للتنمية

بعد تجميد VINTF كل عام، يتم تعديل مصفوفة توافق إطار العمل (FCM) target-level وPRODUCT_SHIPPING_API_LEVEL of Cuttlefish لكي تعكس الأجهزة التي سيتم إطلاقها مع إصدار العام المقبل. سنعدّل target-level وPRODUCT_SHIPPING_API_LEVEL للتأكّد من توفُّر بعض الأجهزة التي يتم إطلاقها والتي تم اختبارها ومن استيفائها المتطلبات الجديدة لإصدار العام المقبل.

عندما يكون قيمة الحقل "RELEASE_AIDL_USE_UNFROZEN" true، يتم استخدام الحبَّار لتطوير إصدارات Android المستقبلية. وتستهدف هذه السياسة مستوى إصدار Android من خدمة "المراسلة عبر السحابة الإلكترونية من Firebase" وPRODUCT_SHIPPING_API_LEVEL للعام المقبل، ما يتطلب من خلالها استيفاء متطلبات برنامج المورّدين (VSR) في الإصدار القادم.

عندما تكون قيمة RELEASE_AIDL_USE_UNFROZEN هي false، تكون قيمة target-level وPRODUCT_SHIPPING_API_LEVEL السابقة في سمكة حبَّاد لتعكس جهاز الإصدار. في الإصدار Android 14 والإصدارات الأقدم، يتم تحقيق هذا التميّز من خلال فروع Git المختلفة التي لا تتيح التغيير إلى target-level في خدمة "المراسلة عبر السحابة الإلكترونية من Firebase" أو مستوى واجهة برمجة تطبيقات الشحن أو أي رمز آخر يستهدف الإصدار التالي.

قواعد تسمية الوحدات

في Android 11، يتم تلقائيًا إنشاء وحدة مكتبة بديلة لكل مجموعة من الإصدارات والخلفيات المفعَّلة. للإشارة إلى وحدة معيّنة لمكتبة جِز من أجل الربط، لا تستخدم اسم الوحدة aidl_interface بل اسم وحدة مكتبة قسائم التعريف، أي ifacename-version-backend حيث يشير ذلك إلى

  • ifacename: اسم الوحدة aidl_interface
  • version إما من
    • Vversion-number للنُسخ الثابتة
    • Vlatest-frozen-version-number + 1 للحصول على إصدار رأس شجرة (لم يتم تجميده بعد)
  • backend إما من
    • java لخلفية Java،
    • cpp لخلفية C++ ،
    • ndk أو ndk_platform للواجهة الخلفية NDK. الأول مخصص للتطبيقات، وثانيًا لاستخدام النظام الأساسي،
    • rust للواجهة الخلفية Rust.

لنفترض أنّ هناك وحدة تحمل الاسم foo وأحدث إصدار منها هو 2، وتتوافق مع كل من NDK وC++. في هذه الحالة، تنشئ AIDL هذه الوحدات:

  • استنادًا إلى الإصدار 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • استنادًا إلى الإصدار 2 (أحدث إصدار ثابت)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • استنادًا إلى إصدار بنود الخدمة
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

بالمقارنة مع نظام التشغيل Android 11،

  • foo-backend، الذي أشار إلى أحدث إصدار ثابت يصبح foo-V2-backend
  • foo-unstable-backend، الذي أشار إلى إصدار "بنود الاستخدام" يصبح foo-V3-backend

أسماء ملفات الإخراج دائمًا هي نفسها أسماء الوحدات.

  • استنادًا إلى الإصدار 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • استنادًا إلى الإصدار 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • استنادًا إلى إصدار بنود الخدمة: foo-V3-(cpp|ndk|ndk_platform|rust).so

يُرجى العلم أنّ برنامج التحويل البرمجي AIDL لا ينشئ وحدة إصدار unstable أو وحدة غير ذات إصدار لواجهة AIDL ثابتة. اعتبارًا من Android 12، سيتضمّن دائمًا اسم الوحدة الذي تم إنشاؤه من واجهة AIDL الثابتة الإصدار الخاص بها.

طرق الواجهة الوصفية الجديدة

يضيف Android 10 العديد من طرق الواجهة الوصفية لإصدار AIDL الثابت.

الاستعلام عن إصدار الواجهة للعنصر البعيد

يمكن للعملاء الاستعلام عن إصدار وتجزئة الواجهة التي ينفّذها الكائن البعيد ومقارنة القيم المعروضة بقيم الواجهة التي يستخدمها العميل.

مثال مع الواجهة الخلفية cpp:

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

مثال مع الواجهة الخلفية ndkndk_platform):

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

مثال مع الواجهة الخلفية java:

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

في لغة Java، يجب تنفيذ الترميزَين getInterfaceVersion() وgetInterfaceHash() على النحو التالي (يتم استخدام super بدلاً من IFoo لتجنُّب أخطاء النسخ واللصق. قد تكون هناك حاجة إلى التعليق التوضيحي @SuppressWarnings("static") لإيقاف التحذيرات، وذلك استنادًا إلى إعدادات javac):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

يرجع ذلك إلى أنّه يتم مشاركة الفئات التي تم إنشاؤها (IFoo وIFoo.Stub وما إلى ذلك) بين العميل والخادم (على سبيل المثال، يمكن أن تكون الفئات في مسار فئة التمهيد). وعند مشاركة الفئات، يرتبط الخادم أيضًا بالإصدار الأحدث من الفئات على الرغم من أنه قد تم إنشاؤه باستخدام إصدار قديم من الواجهة. إذا تم تنفيذ هذه الواجهة الوصفية في الفئة المشتركة، فسيتم دائمًا عرض أحدث إصدار. ومع تنفيذ الطريقة المذكورة أعلاه، يتم تضمين رقم إصدار الواجهة في الرمز البرمجي للخادم (لأنّ IFoo.VERSION عبارة عن static final int مضمَّنة عند الإشارة إليه) وبالتالي يمكن أن تعرض الطريقة الإصدار الدقيق الذي تم إنشاء الخادم باستخدامه.

التعامل مع الواجهات القديمة

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

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

مثال في لغة C++ في نظام التشغيل Android 13 والإصدارات الأحدث:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

مثال في Java:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process


foo.anAddedMethod(...);

لن تحتاج إلى توفير التنفيذ التلقائي لجميع الطرق في واجهة AIDL. لا يلزم إلغاء الطرق التي يضمن تنفيذها في الجانب البعيد (لأنك على يقين من أن وحدة التحكم عن بُعد قد تم إنشاؤها عندما كانت الطرق في وصف واجهة AIDL) لا تحتاج إلى تجاوزها في الفئة impl التلقائية.

تحويل لغة AIDL الحالية إلى تنسيق AIDL المنظَّم/المستقر

إذا كانت لديك واجهة AIDL حالية ورمز برمجي يستخدمونها، اتّبِع الخطوات التالية لتحويل الواجهة إلى واجهة AIDL ثابتة.

  1. تحديد جميع تبعيات واجهتك. لكل حزمة تعتمد عليها الواجهة، حدد ما إذا كانت الحزمة محددة في AIDL الثابتة. وإذا لم يتم تعريفها، فيجب تحويل الحزمة.

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

  3. أنشِئ حزمة aidl_interface (كما هو موضّح أعلاه) تحتوي على اسم الوحدة وتبعياتها وأي معلومات أخرى تحتاج إليها. لتحقيق استقرار (وليس منظمًا فقط)، يجب أيضًا تحديد نُسخ مختلفة منه. لمزيد من المعلومات، اطّلِع على واجهات تحديد الإصدارات.