يوفّر نظام التشغيل Android 10 إمكانية استخدام لغة تعريف واجهة Android الثابتة (AIDL)، وهي طريقة جديدة لتتبُّع واجهة برمجة التطبيقات (API) وواجهة التطبيق الثنائية (ABI) التي توفّرها واجهات AIDL. تعمل واجهة AIDL الثابتة تمامًا مثل واجهة AIDL، ولكن يتتبّع نظام الإنشاء توافق الواجهة، وهناك قيود على ما يمكنك فعله:
- يتم تحديد الواجهات في نظام الإنشاء باستخدام
aidl_interfaces
. - يمكن أن تحتوي الواجهات على بيانات منظَّمة فقط. يتم إنشاء عناصر Parcelable تمثّل الأنواع المفضّلة تلقائيًا استنادًا إلى تعريف AIDL الخاص بها، كما يتم تلقائيًا تحويلها إلى تنسيق قابل للنقل وتحويلها من هذا التنسيق.
- يمكن تعريف الواجهات على أنّها ثابتة (متوافقة مع الإصدارات القديمة). وعند حدوث ذلك، يتم تتبُّع واجهة برمجة التطبيقات هذه وتحديد إصدارها في ملف بجانب واجهة AIDL.
لغة تعريف واجهة نظام Android (AIDL) المنظَّمة مقابل لغة تعريف واجهة نظام Android (AIDL) الثابتة
يشير مصطلح Structured 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 التي تتألف منها الواجهة يجب أن يكون المسار لنوع AIDLFoo
محدّد في الحزمة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 غير مفعّل تلقائيًا حتى الإصدار 15 من نظام التشغيل Android. 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
، ينفّذ نظام الإنشاء عمليات التحقّق من التوافق بين الإصدارات المجمّدة، وكذلك بين أحدث إصدار مجمّد و"أحدث إصدار" (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 الثابت. ومع ذلك، يتطلّب تعديل هذه الأنواع تعديل جميع الخوادم والعملاء الذين يستخدمون إصدارًا سابقًا من الواجهة، وقد يحدث تعارض في بعض التطبيقات عند استخدام إصدارات مختلفة من الأنواع. وبشكل عام، يكون ذلك آمنًا بالنسبة إلى الحِزم التي تحتوي على أنواع فقط أو الحِزم الشائعة، لأنّه يجب كتابة الرمز البرمجي مسبقًا للتعامل مع الأنواع غير المعروفة من معاملات الاتصال بين العمليات.
في رمز منصة Android، يمثّل android.hardware.graphics.common
أكبر مثال على هذا النوع من ترقية الإصدار.
استخدام واجهات ذات إصدارات
طُرق الواجهة
عند التشغيل، وعند محاولة استدعاء طرق جديدة على خادم قديم، يتلقّى العملاء الجدد إما خطأ أو استثناءً، وذلك حسب الخلفية.
- يحصل النظام الخلفي على
::android::UNKNOWN_TRANSACTION
.cpp
- يحصل النظام الخلفي على
STATUS_UNKNOWN_TRANSACTION
.ndk
- يتلقّى الخلفية
java
الرمزandroid.os.RemoteException
مع رسالة تفيد بأنّ واجهة برمجة التطبيقات غير متاحة.
للاطّلاع على استراتيجيات التعامل مع هذا الأمر، راجِع طلب إصدارات واستخدام القيم التلقائية.
Parcelables
عند إضافة حقول جديدة إلى العناصر القابلة للتسلسل، تتجاهلها الخوادم والبرامج القديمة. عندما تتلقّى البرامج والخوادم الجديدة حزمًا قديمة قابلة للتسلسل، يتم تلقائيًا ملء القيم التلقائية للحقول الجديدة. وهذا يعني أنّه يجب تحديد القيم التلقائية لجميع الحقول الجديدة في عنصر قابل للتسلسل.
يجب ألا يتوقّع العملاء أن تستخدم الخوادم الحقول الجديدة إلا إذا كانوا يعلمون أنّ الخادم ينفّذ الإصدار الذي تم فيه تحديد الحقل (راجِع الاستعلام عن الإصدارات).
عمليات التعداد والثوابت
وبالمثل، يجب أن ترفض البرامج والخوادم القيم الثابتة والمعدودة غير المعروفة أو تتجاهلها حسب الاقتضاء، لأنّه قد تتم إضافة المزيد في المستقبل. على سبيل المثال، يجب ألا يتوقف الخادم عن العمل عند تلقّي أداة تعداد لا يعرفها. على الخادم إما تجاهل أداة التعداد أو عرض شيء ما ليعرف العميل أنّها غير متوافقة في هذا التنفيذ.
الاتحادات
إذا حاولت إرسال اتحاد مع حقل جديد، ستتعذّر العملية إذا كان المستلِم يستخدم إصدارًا قديمًا من البروتوكول ولا يعرف الحقل. لن يظهر الاتحاد مع الحقل الجديد في عملية التنفيذ. يتم تجاهل الخطأ إذا كان عبارة عن معاملة أحادية الاتجاه، وإلا سيتم عرض الخطأ BAD_VALUE
(لبرنامج C++ أو NDK الأساسي) أو IllegalArgumentException
(لبرنامج Java الأساسي). يحدث الخطأ إذا كان العميل يرسل مجموعة اتحادية إلى الحقل الجديد على خادم قديم، أو إذا كان عميل قديم يتلقّى المجموعة الاتحادية من خادم جديد.
إدارة نُسخ متعددة
يمكن أن يتضمّن مساحة اسم أداة الربط في 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) باستخدام التطوير المستند إلى العلامات هو شرط توافق عمليات تنفيذ طبقة تجريد الأجهزة مع الإصدار الأخير الثابت لكي تعمل عندما تكون قيمة RELEASE_AIDL_USE_UNFROZEN
هي false
.
يُعدّ التوافق مع الإصدارات القديمة في عمليات التنفيذ ورمز الجهاز ممارسة جديدة. اطّلِع على استخدام واجهات
متوافقة مع إصدارات متعددة.
تكون اعتبارات التوافق مع الإصدارات القديمة هي نفسها بشكل عام لكل من البرامج والخوادم، وكذلك بالنسبة إلى رمز إطار العمل ورمز المورّد، ولكن هناك اختلافات طفيفة يجب أن تكون على دراية بها، لأنّك الآن تنفّذ فعليًا إصدارَين يستخدمان رمز المصدر نفسه (الإصدار الحالي غير المجمّد).
مثال: تتضمّن الواجهة ثلاث نسخ مجمدة. يتم تعديل الواجهة بإضافة طريقة جديدة. يتم تعديل كل من العميل والخدمة لاستخدام الإصدار 4 الجديد من المكتبة. بما أنّ مكتبة الإصدار 4 تستند إلى إصدار غير ثابت من الواجهة، فإنّها تتصرف مثل الإصدار الثابت الأخير، أي الإصدار 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 الثابتة
لا تتضمّن واجهات الإضافات الجديدة إصدارًا سابقًا يمكن الرجوع إليه، وبما أنّها غير مسجّلة في 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 مختلفة لا تتضمّن التغيير في 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();
مثال باستخدام الخلفية ndk
(وndk_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 ثابتة.
تحديد جميع التبعيات في واجهتك بالنسبة إلى كل حزمة تعتمد عليها الواجهة، حدِّد ما إذا كانت الحزمة معرَّفة في AIDL ثابت. إذا لم يتم تحديدها، يجب تحويل الحزمة.
حوِّل جميع العناصر القابلة للتسلسل في واجهتك إلى عناصر قابلة للتسلسل ثابتة (يمكن أن تظل ملفات الواجهة نفسها بدون تغيير). يمكنك إجراء ذلك من خلال التعبير عن بنيتها مباشرةً في ملفات AIDL. يجب إعادة كتابة فئات الإدارة لاستخدام هذه الأنواع الجديدة. يمكن إجراء ذلك قبل إنشاء حزمة
aidl_interface
(في ما يلي).أنشئ حزمة
aidl_interface
(كما هو موضّح أعلاه) تحتوي على اسم الوحدة النمطية والعناصر التابعة لها وأي معلومات أخرى تحتاج إليها. ولكي يصبح مستقرًا (وليس منظَّمًا فقط)، يجب أيضًا أن يكون له إصدار. لمزيد من المعلومات، اطّلِع على تحديد إصدارات الواجهات.