لغة AIDL ثابتة

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

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

لغة تعريف واجهة نظام 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، وبدءًا من الإصدار 11 من نظام التشغيل Android، يتم تجميد 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 stub استنادًا إليها. القيمة التلقائية هي "system_current". ولا يجب ضبط هذا الإعداد عندما تكون قيمة backend.java.platform_apis في القيمة true.
  • backend.java.platform_apis: هي العلامة الاختيارية التي يجب ضبطها على true عندما تحتاج المكتبات التي تم إنشاؤها إلى إنشاء توافق مع واجهة برمجة تطبيقات النظام الأساسي بدلاً من حزمة تطوير البرامج (SDK).

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

كتابة ملفات AIDL

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

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

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

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

استخدام مكتبات العناصر المصغّرة

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

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

مثال في C++:

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

مثال في Java:

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

مثال في Rust:

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

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

يؤدي أيضًا الإعلان عن وحدة باسم foo إلى إنشاء هدف في نظام الإنشاء يمكنك استخدامه لإدارة واجهة برمجة التطبيقات للوحدة. عند إنشاء foo-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 الثابتة. ومع ذلك، يتطلّب تعديل هذه الأنواع تعديل جميع الخوادم والعملاء الذين يستخدمون إصدارًا سابقًا من الواجهة، وقد تواجه بعض التطبيقات بعض المشاكل عند خلط إصدارات مختلفة من الأنواع. بشكل عام، بالنسبة للأنواع فقط أو الحزم المشتركة، هذا أمر آمن لأن التعليمة البرمجية يجب أن تكون مكتوبة بالفعل للتعامل مع الأنواع غير المعروفة من معاملات IPC.

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

استخدام الواجهات التي تتضمّن إصدارات

طرق الواجهة

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

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

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

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

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

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

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

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

الاتحادات

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

علامة إنشاء لغة تعريف واجهة نظام Android

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

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

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

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

المصفوفات

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

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

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

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

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

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

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

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

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

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

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

مثال: تحتوي واجهة على ثلاثة إصدارات مجمّدة. يتم تحديث الواجهة بطريقة جديدة. تم تحديث كلّ من العميل والخدمة لاستخدام الإصدار 4 الجديد من مكتبة Billing Library. وبما أنّ مكتبة الإصدار 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_UNFROZENfalse. لا تستخدِم هذه الواجهات. عندما يكون RELEASE_AIDL_USE_UNFROZEN false، لن يسمح "مدير الخدمات" للخدمة بتسجيل الواجهة ولن يعثر عليها العملاء.

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

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

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

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

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

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

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

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

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

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

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

من الممكن أن يكون العميل قد تم تحديثه باستخدام الإصدار الأحدث من واجهة ملف تعريف واجهة نظام Android ‏(AIDL)، ولكن يستخدم الخادم واجهة ملف تعريف واجهة نظام Android القديمة. في هذه الحالات، يؤدي استدعاء طريقة على واجهة قديمة إلى عرض القيمة 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. لا يلزم إلغاء تنفيذ الطرق التي يُضمن تنفيذها في الجانب البعيد (لأنّك متأكد من أنّه تم إنشاء الجانب البعيد عندما كانت الطرق في وصف واجهة IDE) في فئة impl التلقائية.

تحويل ملف AIDL الحالي إلى ملف AIDL منظَّم أو ثابت

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

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

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

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