يضيف Android 10 دعمًا للغة تعريف واجهة Android المستقرة (AIDL) ، وهي طريقة جديدة لتتبع واجهة برنامج التطبيق (API) / الواجهة الثنائية للتطبيق (ABI) التي توفرها واجهات AIDL. يحتوي AIDL المستقر على الاختلافات الرئيسية التالية عن AIDL:
- يتم تعريف الواجهات في نظام البناء مع
aidl_interfaces
. - يمكن أن تحتوي الواجهات على بيانات منظمة فقط. يتم إنشاء الأجزاء التي تمثل الأنواع المرغوبة تلقائيًا بناءً على تعريف AIDL ويتم تنظيمها وإلغاء تنظيمها تلقائيًا.
- يمكن إعلان الواجهات على أنها مستقرة (متوافقة مع الإصدارات السابقة). عندما يحدث هذا ، يتم تتبع واجهة برمجة التطبيقات الخاصة بهم وتحويلها إلى إصدار في ملف بجوار واجهة AIDL.
تحديد واجهة AIDL
يبدو تعريف aidl_interface
كما يلي:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions: ["1", "2"],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
},
}
-
name
: اسم وحدة واجهة AIDL التي تعرّف بشكل فريد واجهة AIDL. -
srcs
: قائمة ملفات مصدر AIDL التي تؤلف الواجهة. يجب أن يكون مسار AIDL typeFoo
المحدد في حزمة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
. إذا لم تكن هناك إصدارات مجمدة للواجهة ، فلا يجب تحديد ذلك ، ولن يتم إجراء فحوصات التوافق. -
stability
: العلامة الاختيارية لوعد استقرار هذه الواجهة. حاليا يدعم فقط"vintf"
. إذا لم يتم ضبط هذا ، فهذا يتوافق مع واجهة ذات ثبات ضمن سياق التجميع هذا (لذلك لا يمكن استخدام الواجهة المحملة هنا إلا مع الأشياء المجمعة معًا ، على سبيل المثال على system.img). إذا تم تعيين هذا على"vintf"
، فهذا يتوافق مع وعد الاستقرار: يجب أن تظل الواجهة مستقرة طالما يتم استخدامها. -
gen_trace
: العلامة الاختيارية لتشغيل التتبع أو إيقاف تشغيله. الافتراضي هوfalse
. -
host_supported
: العلامة الاختيارية التي عند ضبطها على "true
" تجعل المكتبات المُنشأة متاحة للبيئة المضيفة. -
unstable
: العلامة الاختيارية المستخدمة للإشارة إلى أن هذه الواجهة لا تحتاج إلى أن تكون مستقرة. عند تعيين هذا على "true
" ، لا يقوم نظام الإنشاء بإنشاء تفريغ واجهة برمجة التطبيقات للواجهة ولا يتطلب تحديثها. -
backend.<type>.enabled
: تعمل هذه العلامات على تبديل كل من الواجهات الخلفية التي سينشئ مترجم AIDL رمزًا لها. حاليًا ، يتم دعم ثلاثة خلفيات خلفية:java
وcpp
وndk
. يتم تمكين جميع الخلفيات بشكل افتراضي. عندما لا تكون هناك حاجة إلى خلفية محددة ، يجب تعطيلها بشكل صريح. -
backend.<type>.apex_available
: قائمة أسماء APEX التي تتوفر لها مكتبة stub التي تم إنشاؤها. -
backend.[cpp|java].gen_log
: العلامة الاختيارية التي تتحكم في إنشاء رمز إضافي لجمع المعلومات حول المعاملة. -
backend.[cpp|java].vndk.enabled
: العلامة الاختيارية لجعل هذه الواجهة جزءًا من VNDK. الافتراضي هوfalse
. -
backend.java.platform_apis
: العلامة الاختيارية التي تتحكم في ما إذا كانت مكتبة Java stub مبنية على واجهات برمجة التطبيقات الخاصة من النظام الأساسي. يجب ضبط هذا على"true"
عند ضبطstability
على"vintf"
. -
backend.java.sdk_version
: العلامة الاختيارية لتحديد إصدار SDK الذي تم بناء مكتبة Java stub عليه. الافتراضي هو"system_current"
. لا ينبغي تعيين هذا عندما يكونbackend.java.platform_apis
صحيحًا. -
backend.java.platform_apis
: العلامة الاختيارية التي يجب تعيينها على "true
" عندما تحتاج المكتبات التي تم إنشاؤها إلى البناء على واجهة API للنظام الأساسي بدلاً من SDK.
لكل مجموعة من versions
والخلفيات الممكنة ، يتم إنشاء مكتبة كعب. راجع قواعد تسمية الوحدة النمطية لمعرفة كيفية الرجوع إلى الإصدار المحدد من مكتبة كعب الروتين لواجهة خلفية محددة.
كتابة ملفات 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 حتى إذا لم يكن هناك عداد صفري.
استخدام مكتبات كعب الروتين
بعد إضافة مكتبات كعب تبعية إلى الوحدة النمطية الخاصة بك ، يمكنك تضمينها في ملفاتك. فيما يلي أمثلة على مكتبات stub في نظام الإنشاء (يمكن أيضًا استخدام 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"],
...
}
المثال في 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
واجهات الإصدار
يؤدي إعلان وحدة باسم foo أيضًا إلى إنشاء هدف في نظام الإنشاء يمكنك استخدامه لإدارة واجهة برمجة التطبيقات الخاصة بالوحدة. عند الإنشاء ، يضيف foo-freeze-api تعريفًا جديدًا لواجهة برمجة التطبيقات تحت api_dir
أو aidl_api/ name
، اعتمادًا على إصدار Android ، ويضيف ملف .hash
، يمثل كلاهما الإصدار المجمد حديثًا من الواجهة. يؤدي إنشاء هذا أيضًا إلى تحديث خاصية versions
لتعكس الإصدار الإضافي. بمجرد تحديد خاصية versions
، يقوم نظام الإنشاء بتشغيل فحوصات التوافق بين الإصدارات المجمدة وأيضًا بين Top of Tree (ToT) وأحدث إصدار مجمّد.
بالإضافة إلى ذلك ، تحتاج إلى إدارة تعريف API الخاص بإصدار ToT. عندما يتم تحديث API ، قم بتشغيل foo-update-api لتحديث aidl_api/ name /current
الذي يحتوي على تعريف API لإصدار ToT.
للحفاظ على استقرار الواجهة ، يمكن للمالكين إضافة:
- الطرق حتى نهاية الواجهة (أو الطرق ذات المسلسلات الجديدة المحددة بوضوح)
- عناصر في نهاية الجزء القابل للتقسيم (يتطلب إضافة افتراضي لكل عنصر)
- قيم ثابتة
- في Android 11 ، العدادين
- في Android 12 ، الحقول حتى نهاية الاتحاد
لا يُسمح بأي إجراءات أخرى ، ولا يمكن لأي شخص آخر تعديل الواجهة (وإلا فإنها تخاطر بالتعارض مع التغييرات التي يجريها المالك).
استخدام واجهات ذات إصدارات
طرق الواجهة
في وقت التشغيل ، عند محاولة استدعاء طرق جديدة على خادم قديم ، يحصل العملاء الجدد إما على خطأ أو استثناء ، اعتمادًا على الخلفية.
-
cpp
backend يحصل على::android::UNKNOWN_TRANSACTION
. - تحصل الخلفية
ndk
علىSTATUS_UNKNOWN_TRANSACTION
. -
java
backend تحصل علىandroid.os.RemoteException
مع رسالة تقول لم يتم تنفيذ API.
لاستراتيجيات التعامل مع هذا راجع الاستعلام عن الإصدارات واستخدام الإعدادات الافتراضية .
الطرود
عند إضافة حقول جديدة إلى الطرود ، يقوم العملاء والخوادم القدامى بإسقاطها. عندما يتلقى العملاء والخوادم الجديدة عناصر قديمة ، يتم ملء القيم الافتراضية للحقول الجديدة تلقائيًا. وهذا يعني أنه يجب تحديد الإعدادات الافتراضية لجميع الحقول الجديدة في الطرد.
يجب ألا يتوقع العملاء أن تستخدم الخوادم الحقول الجديدة ما لم يعلموا أن الخادم يقوم بتنفيذ الإصدار الذي تم تحديد الحقل (راجع الاستعلام عن الإصدارات ).
التعداد والثوابت
وبالمثل ، يجب على العملاء والخوادم إما رفض أو تجاهل القيم الثابتة غير المعترف بها والعدادات حسب الاقتضاء ، حيث يمكن إضافة المزيد في المستقبل. على سبيل المثال ، يجب ألا يُجهض الخادم عندما يتلقى عدادًا لا يعرفه. يجب إما تجاهله ، أو إرجاع شيء ما حتى يعرف العميل أنه غير مدعوم في هذا التنفيذ.
النقابات
فشلت محاولة إرسال اتحاد بحقل جديد إذا كان المستلم قديمًا ولا يعرف شيئًا عن الحقل. لن يرى التنفيذ أبدًا الاتحاد مع الحقل الجديد. يتم تجاهل الفشل إذا كانت صفقة في اتجاه واحد ؛ وإلا فإن الخطأ هو BAD_VALUE
(للخلفية C ++ أو NDK) أو IllegalArgumentException
(لواجهة Java الخلفية). يتم تلقي الخطأ إذا كان العميل يرسل مجموعة اتحاد إلى الحقل الجديد إلى خادم قديم ، أو عندما يكون عميلاً قديمًا يتلقى الاتحاد من خادم جديد.
قواعد تسمية الوحدة النمطية
في Android 11 ، لكل مجموعة من الإصدارات والخلفيات الممكّنة ، يتم إنشاء وحدة مكتبة كعب الروتين تلقائيًا. للإشارة إلى وحدة مكتبة كعب الروتين المحدد للربط ، لا تستخدم اسم الوحدة النمطية aidl_interface
، ولكن اسم وحدة مكتبة كعب الروتين ، وهو ifacename - version - backend ، حيث
-
ifacename
: اسم الوحدة النمطيةaidl_interface
-
version
إما-
V version-number
للنسخ المجمدة -
V latest-frozen-version-number + 1
لإصدار طرف الشجرة (لم يتم تجميده بعد)
-
-
backend
هي إما-
java
للواجهة الخلفية لجافا ، -
cpp
للواجهة الخلفية C ++ ، -
ndk
أوndk_platform
NDK. الأول مخصص للتطبيقات ، والأخير لاستخدام النظام الأساسي.
-
افترض أن هناك وحدة باسم foo وأحدث إصدار لها هو 2 ، وهي تدعم كلاً من NDK و C ++. في هذه الحالة ، يُنشئ AIDL الوحدات النمطية التالية:
- بناءً على الإصدار 1
-
foo-V1-(java|cpp|ndk|ndk_platform)
-
- بناءً على الإصدار 2 (أحدث إصدار ثابت)
-
foo-V2-(java|cpp|ndk|ndk_platform)
-
- بناءً على إصدار ToT
-
foo-V3-(java|cpp|ndk|ndk_platform)
-
مقارنة بـ Android 11 ،
-
foo- backend
، الذي يشير إلى أحدث إصدار مستقر يصبحfoo- V2 - backend
-
foo-unstable- backend
، والذي يشير إلى إصدار ToT يصبحfoo- V3 - backend
أسماء ملفات الإخراج هي دائمًا نفس أسماء الوحدات النمطية.
- بناءً على الإصدار 1:
foo-V1-(cpp|ndk|ndk_platform).so
- بناءً على الإصدار 2:
foo-V2-(cpp|ndk|ndk_platform).so
- بناءً على إصدار ToT:
foo-V3-(cpp|ndk|ndk_platform).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()
على النحو التالي:
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return IFoo.VERSION; }
@Override
public final String getInterfaceHash() { return IFoo.HASH; }
}
هذا بسبب مشاركة الفئات التي تم إنشاؤها ( IFoo
، IFoo.Stub
، إلخ) بين العميل والخادم (على سبيل المثال ، يمكن أن تكون الفئات في مسار فئة التمهيد). عند مشاركة الفئات ، يتم ربط الخادم أيضًا بأحدث إصدار من الفئات على الرغم من أنه ربما تم إنشاؤه باستخدام إصدار أقدم من الواجهة. إذا تم تنفيذ واجهة التعريف هذه في الفصل الدراسي المشترك ، فإنها تُرجع دائمًا الإصدار الأحدث. ومع ذلك ، من خلال تنفيذ الطريقة على النحو الوارد أعلاه ، يتم تضمين رقم إصدار الواجهة في رمز الخادم (لأن IFoo.VERSION
عبارة عن مجموعة static final int
مضمنة عند الرجوع إليها) وبالتالي يمكن للطريقة إرجاع الإصدار الدقيق الذي تم إنشاؤه للخادم مع.
التعامل مع الواجهات القديمة
من الممكن أن يتم تحديث العميل بإصدار أحدث من واجهة AIDL ولكن الخادم يستخدم واجهة AIDL القديمة. في مثل هذه الحالات ، يؤدي استدعاء طريقة على واجهة قديمة إلى إرجاع UNKNOWN_TRANSACTION
.
مع AIDL المستقر ، يتمتع العملاء بمزيد من التحكم. في جانب العميل ، يمكنك تعيين تطبيق افتراضي لواجهة AIDL. يتم استدعاء طريقة في التطبيق الافتراضي فقط عندما لا يتم تنفيذ الطريقة في الجانب البعيد (لأنها تم إنشاؤها باستخدام إصدار أقدم من الواجهة). نظرًا لأنه يتم تعيين الإعدادات الافتراضية بشكل عام ، فلا ينبغي استخدامها من السياقات المشتركة المحتملة.
مثال في C ++ في Android T (تجريبي AOSP) والإصدارات الأحدث:
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
(كما هو موضح أعلاه) تحتوي على اسم الوحدة النمطية الخاصة بك وتبعياتها وأي معلومات أخرى تحتاجها. لجعله مستقرًا (وليس منظمًا فقط) ، يجب أيضًا إصداره. لمزيد من المعلومات ، راجع واجهات تعيين الإصدار .