AIDL مستقر

تنظيم صفحاتك في مجموعات يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.

يضيف 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_with_info: [
        {
            version: "1",
            imports: ["ohter-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 type 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 وما فوق.
  • versions_with_info : قائمة المجموعات ، كل منها يحتوي على اسم نسخة مجمدة وقائمة بإصدارات واردات من وحدات نمطية helpl_interface أخرى تم استيرادها من هذا الإصدار من helpl_interface. يقع تعريف الإصدار الخامس لواجهة AIDL IFACE في aidl_api/ IFACE / V تم تقديم هذا الحقل في Android 13 ، ولا يُفترض أن يتم تعديله في Android.bp مباشرةً. يُضاف الحقل أو يُحدَّث باستدعاء *-update-api أو *-freeze-api . أيضًا ، يتم ترحيل حقول versions تلقائيًا إلى versions_with_info عندما يستدعي المستخدم *-update-api أو *-freeze-api .
  • stability : العلامة الاختيارية لوعد استقرار هذه الواجهة. حاليا يدعم فقط "vintf" . إذا لم يتم ضبط هذا ، فهذا يتوافق مع واجهة ذات ثبات ضمن سياق التجميع هذا (لذلك لا يمكن استخدام الواجهة المحملة هنا إلا مع الأشياء المجمعة معًا ، على سبيل المثال على system.img). إذا تم تعيين هذا على "vintf" ، فهذا يتوافق مع وعد الاستقرار: يجب أن تظل الواجهة مستقرة طالما يتم استخدامها.
  • gen_trace : العلامة الاختيارية لتشغيل التتبع أو إيقاف تشغيله. الافتراضي هو false .
  • host_supported : العلامة الاختيارية التي عند ضبطها على true تجعل المكتبات المُنشأة متاحة للبيئة المضيفة.
  • unstable : العلامة الاختيارية المستخدمة للإشارة إلى أن هذه الواجهة لا تحتاج إلى أن تكون مستقرة. عند تعيين هذا على " true " ، لا يقوم نظام الإنشاء بإنشاء تفريغ واجهة برمجة التطبيقات للواجهة ولا يتطلب تحديثها.
  • backend.<type>.enabled : تعمل هذه العلامات على تبديل كل من الواجهات الخلفية التي يولد مترجم AIDL رمزًا لها. حاليًا ، يتم دعم أربعة واجهات خلفية: Java و C ++ و NDK و Rust. يتم تمكين واجهات Java و C ++ و NDK بشكل افتراضي. إذا لم تكن هناك حاجة إلى أي من هذه الخلفيات الثلاثة ، فيجب تعطيلها بشكل صريح. يتم تعطيل الصدأ افتراضيًا.
  • backend.<type>.apex_available : قائمة أسماء APEX التي تتوفر لها مكتبة كعب روتين تم إنشاؤها.
  • 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.

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

كتابة ملفات 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"],
    ...
}
# or
rust_... {
    name: ...,
    rust_libs: ["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

مثال في الصدأ:

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 أيضًا بتحديث خاصية version_with_info لتعكس versions_with_info الإضافي imports الخاصة بالإصدار. بشكل أساسي ، يتم versions_with_info imports في version_with_info من حقل imports . ولكن تم تحديد أحدث إصدار مستقر في عمليات imports في versions_with_info للاستيراد الذي لا يحتوي على إصدار صريح. بمجرد تحديد خاصية version_with_info ، يقوم نظام الإنشاء بإجراء فحوصات التوافق بين versions_with_info المجمدة وأيضًا بين Top of Tree (ToT) وأحدث إصدار مجمّد.

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

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

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

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

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

  • AIDL_FROZEN_REL=true m ... - يتطلب البناء تجميد جميع واجهات AIDL المستقرة التي ليس لها owner: تم تحديد الحقل.
  • AIDL_FROZEN_OWNERS="aosp test" - يتطلب البناء تجميد جميع واجهات AIDL الثابتة مع owner: الحقل المحدد كـ "aosp" أو "test".

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

طرق الواجهة

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

  • cpp backend يحصل على ::android::UNKNOWN_TRANSACTION .
  • تحصل الخلفية ndk على STATUS_UNKNOWN_TRANSACTION .
  • java backend تحصل على android.os.RemoteException مع رسالة تفيد بعدم تنفيذ API.

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

الطرود

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

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

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

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

النقابات

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

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

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

  • ifacename : اسم الوحدة النمطية aidl_interface
  • version إما
    • V version-number للنسخ المجمدة
    • V latest-frozen-version-number + 1 لإصدار طرف الشجرة (لم يتم تجميده بعد)
  • backend هي إما
    • java للواجهة الخلفية لجافا ،
    • cpp C ++ ،
    • ndk أو ndk_platform NDK. الأول مخصص للتطبيقات ، والأخير لاستخدام النظام الأساسي ،
    • 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)

مقارنة بـ Android 11 ،

  • 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() على النحو التالي:

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 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 (كما هو موضح أعلاه) تحتوي على اسم الوحدة النمطية الخاصة بك وتبعياتها وأي معلومات أخرى تحتاجها. لجعله مستقرًا (وليس منظمًا فقط) ، يجب أيضًا إصداره. لمزيد من المعلومات ، راجع واجهات تعيين الإصدار .