لغة تعريف واجهة نظام Android الثابتة

يوفّر نظام التشغيل Android 10 إمكانية استخدام لغة تعريف واجهة Android الثابتة (AIDL)، وهي طريقة جديدة لتتبُّع واجهة برمجة التطبيقات (API) وواجهة التطبيق الثنائية (ABI) التي توفّرها واجهات AIDL. تعمل واجهة AIDL الثابتة تمامًا مثل واجهة AIDL، ولكن يتتبّع نظام التصميم توافق الواجهة، وهناك قيود على ما يمكنك فعله:

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

لغة تعريف واجهة نظام Android ‏(AIDL) المنظَّمة مقابل لغة تعريف واجهة نظام Android ‏(AIDL) الثابتة

تشير بنية AIDL إلى الأنواع المحدّدة في AIDL فقط. على سبيل المثال، لا يُعدّ تعريف Parcelable (وهو Parcelable مخصّص) من 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 التي تتألف منها الواجهة. يجب أن يكون المسار لنوع AIDL Foo محدّد في الحزمة 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. بدءًا من Android 11، يتم تجميد versions ضمن aidl_api/name. في حال عدم توفّر أي إصدارات ثابتة لواجهة، يجب عدم تحديد ذلك، ولن يتم إجراء عمليات تحقّق من التوافق. تم استبدال هذا الحقل بـ versions_with_info في الإصدار 13 من نظام التشغيل Android والإصدارات الأحدث.
  • 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.
  • 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 هي true.
  • backend.java.platform_apis: العلامة الاختيارية التي يجب ضبطها على true عندما تحتاج المكتبات التي تم إنشاؤها إلى إنشاء إصدارات متوافقة مع واجهة برمجة التطبيقات الخاصة بالمنصة بدلاً من حزمة تطوير البرامج (SDK).

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

كتابة ملفات 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 حتى إذا لم يكن هناك قيمة تعدادية تساوي صفرًا.

استخدام مكتبات التعليمات البرمجية الصورية

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

cc_... {
    name: ...,
    // use `shared_libs:` to load your library and its transitive dependencies
    // dynamically
    shared_libs: ["my-module-name-cpp"],
    // use `static_libs:` to include the library in this binary and drop
    // transitive dependencies
    static_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // use `static_libs:` to add all jars and classes to this jar
    static_libs: ["my-module-name-java"],
    // use `libs:` to make these classes available during build time, but
    // not add them to the jar, in case the classes are already present on the
    // boot classpath (such as if it's in framework.jar) or another jar.
    libs: ["my-module-name-java"],
    // use `srcs:` with `-java-sources` if you want to add classes in this
    // library jar directly, but you get transitive dependencies from
    // somewhere else, such as the boot classpath or another jar.
    srcs: ["my-module-name-java-source", ...],
    ...
}
# 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-freeze-api تعريفًا جديدًا لواجهة برمجة التطبيقات ضمن api_dir أو aidl_api/name، استنادًا إلى إصدار Android، ويضيف ملف .hash، وكلاهما يمثّل الإصدار الجديد المجمد من الواجهة. يعدّل foo-freeze-api أيضًا السمة versions_with_info لتعكس الإصدار الإضافي وimports للإصدار. بشكل أساسي، يتم نسخ imports في versions_with_info من الحقل imports. ولكن تم تحديد أحدث إصدار ثابت في imports ضمن versions_with_info لعملية الاستيراد، وهو لا يتضمّن إصدارًا صريحًا. بعد تحديد السمة versions_with_info، ينفّذ نظام التصميم عمليات التحقّق من التوافق بين الإصدارات الثابتة، وكذلك بين إصدار Top of Tree (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 مع رسالة تفيد بأنّ واجهة برمجة التطبيقات غير متاحة.

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

Parcelables

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

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

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

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

اتحادات

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

عنوان الإصدار على مستوى الوحدة (C++‎ / NDK)

بالنسبة إلى وحدات AIDL الثابتة التي تتضمّن إصدارات وتستخدم الخلفيتَين cpp أو ndk، تنشئ أداة Soong تلقائيًا عنوانًا اصطناعيًا على مستوى الوحدة:

  • الخادم الخلفي للغة C++‎: #include "<module-name>.h"
  • الخادم الخلفي لحزمة NDK: #include "aidl/<module-name>.h"

يحدّد هذا العنوان ماكرو AIDL_VERSION_<MODULE_NAME> مع نقاط . وشرطات - في اسم الوحدة تم تحويلها إلى شرطات سفلية كبيرة _. يتم تعيين رقم إصدار وحدة واجهة برمجة التطبيقات إلى الماكرو استنادًا إلى إصدار المكتبة المستخدَمة.

على سبيل المثال، يمكنك استخدام الماكرو من android.hardware.foo-V2-ndk:

#include <aidl/android.hardware.foo.h>

#if AIDL_VERSION_ANDROID_HARDWARE_FOO >= 2
// Code for version 2 or higher
#endif

إدارة نُسخ متعددة

يمكن أن يتضمّن مساحة اسم الرابط في Android إصدارًا واحدًا فقط من واجهة aidl محدّدة لتجنُّب الحالات التي تتضمّن فيها أنواع aidl التي تم إنشاؤها تعريفات متعدّدة. تتضمّن لغة C++ قاعدة التعريف الواحد التي تتطلّب تعريفًا واحدًا فقط لكل رمز.

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

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

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

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

يمكن استخدام aidl_interfaces_defaults للاحتفاظ بتعريف واحد لأحدث إصدارات التبعيات الخاصة بـ aidl_interface يمكن تعديلها في مكان واحد، ويمكن استخدامها من خلال جميع وحدات aidl_interface التي تريد استيراد هذه الواجهة المشتركة.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

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

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

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

علامة إنشاء AIDL

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

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

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

لا تستهدف معظم أجهزة Cuttlefish مصفوفة التوافق الأحدث إلا بعد تجميد الواجهات، لذا لا يوجد اختلاف في مكتبات 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، تم إجراء تغيير في 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، لن يسمح Service Manager للخدمة بتسجيل الواجهة، ولن تتمكّن التطبيقات من العثور عليها.

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

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

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

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

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

يمكن استخدام المتغيّر RELEASE_AIDL_USE_UNFROZEN لتحديد ما إذا كان سيتم ربط واجهة الإضافة الجديدة غير المجمّدة بالواجهة الحالية لتجنُّب استخدامها على الأجهزة التي تم طرحها. يجب تجميد الواجهة لاستخدامها على الأجهزة التي تم طرحها.

ترصد اختبارات vts_treble_vintf_vendor_test وvts_treble_vintf_framework_test الخاصة بـ VTS الحالات التي يتم فيها استخدام واجهة إضافة غير مجمدة في جهاز تم إصداره، وتُظهر خطأً في هذه الحالة.

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

‫Cuttlefish كأداة تطوير

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

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

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

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

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

  • ifacename: اسم وحدة aidl_interface
  • version هو أيّ من
    • Vversion-number للإصدارات المجمَّدة
    • Vlatest-frozen-version-number + 1 لإصدار tip-of-tree (الذي لم يتم تجميده بعد)
  • backend هو أيّ من
    • java لخادم Java الخلفي
    • cpp لخادم C++‎ الخلفي،
    • ndk أو ndk_platform لخادم NDK الخلفي يُستخدم الخيار الأول للتطبيقات، بينما يُستخدم الخيار الثاني لتحديد استخدام المنصة حتى الإصدار 13 من نظام التشغيل Android. في الإصدار 13 من نظام التشغيل Android والإصدارات الأحدث، استخدِم 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)
  • استنادًا إلى إصدار ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

بالمقارنة مع الإصدار 11 من نظام التشغيل Android:

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

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

  • استنادًا إلى الإصدار 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • استنادًا إلى الإصدار 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • استنادًا إلى إصدار ToT: 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 (كما هو موضّح أعلاه) تحتوي على اسم الوحدة النمطية والتبعيات وأي معلومات أخرى تحتاج إليها. ولجعلها مستقرة (وليست منظَّمة فقط)، يجب أيضًا أن تكون متوافقة مع إصدارات مختلفة. لمزيد من المعلومات، اطّلِع على تحديد إصدارات الواجهات.