‫AIDL پایدار

‫Android 10 از «زبان تعریف رابط Android پایدار» (AIDL) پشتیبانی می‌کند. این زبان روشی جدید برای پیگیری میانای برنامه‌سازی کاربردی (API) و میانای دودویی کاربردی (ABI) ارائه‌شده توسط میاناهای AIDL است. ‫AIDL پایدار دقیقاً مثل AIDL کار می‌کند، اما سیستم ساخت سازگاری میانای را ردیابی می‌کند و محدودیت‌هایی در کارهایی که می‌توانید انجام دهید وجود دارد:

  • میان‌ها در سیستم ساخت با aidl_interfaces تعریف می‌شوند.
  • میان‌ها فقط می‌توانند حاوی داده‌های ساختاریافته باشند. Parcelableهایی که نشان‌دهنده انواع ترجیحی هستند براساس تعریف AIDL آن‌ها به‌طور خودکار ایجاد می‌شوند و به‌طور خودکار دسته‌بندی و دسته‌بندی‌نشده می‌شوند.
  • میان‌ها را می‌توان به‌عنوان پایدار (سازگار با نسخه قدیمی) اعلام کرد. وقتی این اتفاق می‌افتد، API آن‌ها در فایلی در کنار میانای AIDL ردیابی و نسخه‌بندی می‌شود.

‫AIDL ساختارمند دربرابر پایدار

AIDL ساختاریافته به انواع تعریف‌شده صرفاً در AIDL اشاره دارد. برای مثال، یک اعلان parcelable (یک parcelable سفارشی) AIDL ساختاریافته نیست. Parcelableهایی که فیلدهای آن‌ها در AIDL تعریف شده است parcelable ساختاریافته نامیده می‌شوند.

AIDL پایدار به AIDL ساختاریافته نیاز دارد تا سیستم ساخت و کامپایلر بتوانند تشخیص دهند که آیا تغییرات اعمال‌شده در parcelableها سازگار با نسخه‌های قدیمی هستند یا نه. بااین‌حال، همه میاناهای ساختاریافته پایدار نیستند. برای پایدار بودن، واسط باید فقط از انواع ساختاریافته استفاده کند، و همچنین باید از ویژگی‌های نسخه‌بندی زیر استفاده کند. برعکس، اگر از سیستم ساخت هسته برای ساختن میانای موردنظر استفاده شود یا اگر 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 شما از میانایی یا parcelable از aidl_interface دیگری استفاده می‌کند، نام آن را اینجا وارد کنید. این می‌تواند نام به‌تنهایی باشد، برای اشاره به جدیدترین نسخه، یا نام با پسوند نسخه (مثل -V1) برای اشاره به نسخه‌ای خاص. مشخص کردن نسخه از Android 12 پشتیبانی می‌شود
  • ‫versions: نسخه‌های قبلی میانایی که تحت api_dir ثابت شده‌اند، از Android 11 شروع می‌شود، versions تحت aidl_api/name ثابت شده است. اگر نسخه ثابتی از یک میان‌کار وجود نداشته باشد، این مورد نباید مشخص شود و بررسی سازگاری انجام نخواهد شد. این فیلد با versions_with_info برای Android نسخه ۱۳ و بالاتر جایگزین شده است.
  • versions_with_info: فهرست تاپل‌ها که هرکدام حاوی نام نسخه ثابت و فهرستی با واردات نسخه از دیگر واحدهای aidl_interface است که این نسخه از aidl_interface وارد کرده است. تعریف نسخه V از میانای AIDL به نام IFACE در aidl_api/IFACE/V قرار دارد. این فیلد در Android 13 معرفی شده است و نباید مستقیماً در Android.bp تغییر کند. این فیلد با فراخوانی *-update-api یا *-freeze-api اضافه یا به‌روزرسانی می‌شود. همچنین، وقتی کاربر *-update-api یا *-freeze-api را فرا می‌خواند، versions فیلد به‌طور خودکار به versions_with_info منتقل می‌شود
  • ‫stability: پرچم اختیاری برای تعهد پایداری این میان‌گیری. این فقط از "vintf" پشتیبانی می‌کند. اگر stability تنظیم نشده باشد، سیستم ساخت بررسی می‌کند که میانای مربوطه با نسخه‌های قدیمی سازگار باشد، مگراینکه unstable مشخص شده باشد. تنظیم‌نشده بودن با واسطی مطابقت دارد که در این بافت تدوین پایداری دارد (بنابراین یا همه موارد سیستم، برای مثال، موارد موجود در system.img و بخش‌های مرتبط، یا همه موارد فروشنده، برای مثال، موارد موجود در vendor.img و بخش‌های مرتبط). اگر stability روی "vintf" تنظیم شده باشد، این با وعده پایداری مطابقت دارد: میانجی باید تا زمانی که استفاده می‌شود پایدار نگه داشته شود.
  • gen_trace: پرچم اختیاری برای روشن یا خاموش کردن ردیابی. از Android 14، مقدار پیش‌فرض برای پساهای cpp و java، true است.
  • ‫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 به‌طور پیش‌فرض فعال هستند. اگر به هریک از این سه زیرینه نیاز نیست، باید به‌طور صریح غیرفعال شود. تا قبل‌از Android 15، زبان Rust به‌طور پیش‌فرض غیرفعال است.
  • backend.<type>.apex_available: فهرست نام‌های APEX که کتابخانه جای‌خالی تولیدشده برای آن‌ها دردسترس است.
  • ‫backend.[cpp|java].gen_log: پرچم اختیاری که کنترل می‌کند آیا کد اضافی برای جمع‌آوری اطلاعات درباره تراکنش تولید شود یا نه.
  • backend.[cpp|java].vndk.enabled: پرچم اختیاری برای تبدیل این میانای بخشی از VNDK. پیش‌فرض false است.
  • backend.[cpp|ndk].additional_shared_libraries: این پرچم که در Android 14 معرفی شده است، وابستگی‌هایی را به کتابخانه‌های بومی اضافه می‌کند. این پرچم با ndk_header و cpp_header مفید است.
  • ‫backend.java.sdk_version: پرچم اختیاری برای مشخص کردن نسخه کیت توسعه نرم‌افزار که کتابخانه کدهای ساختاری Java براساس آن ساخته شده است. پیش‌فرض "system_current" است. وقتی backend.java.platform_apis روی true تنظیم شده است، نباید تنظیم شود.
  • backend.java.platform_apis: پرچم اختیاری که باید روی true تنظیم شود وقتی کتابخانه‌های تولیدشده باید به‌جای کیت توسعه نرم‌افزار، درمقابل میانای برنامه‌سازی کاربردی پلاتفرم ساخته شوند.

برای هر ترکیبی از نسخه‌ها و پس‌زمینه‌های فعال‌شده، یک کتابخانه ته چک ایجاد می‌شود. برای نحوه ارجاع به نسخه خاص کتابخانه چسبان برای یک زیرینه خاص، به قوانین نام‌گذاری واحد مراجعه کنید.

نوشتن فایل‌های AIDL

میاناهای موجود در AIDL پایدار مشابه میاناهای سنتی هستند، با این تفاوت که مجاز به استفاده از بسته‌بندی‌های بدون ساختار نیستند (زیرا این‌ها پایدار نیستند! به ساختاریافته در مقابل AIDL پایدار مراجعه کنید). تفاوت اصلی در AIDL پایدار نحوه تعریف parcelable است. قبلاً، parcelableها اعلان پیش‌رونده داشتند؛ در AIDL پایدار (و بنابراین ساختاریافته)، فیلدها و متغیرهای parcelable به‌طور صریح تعریف می‌شوند.

// 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، مقادیر پیش‌فرض برای شمارش‌های تعریف‌شده توسط کاربر نیز پشتیبانی می‌شود. وقتی پیش‌فرضی مشخص نشده باشد، از مقدار ۰ یا مقدار خالی استفاده می‌شود. شمارش‌های بدون مقدار پیش‌فرض به ۰ مقداردهی اولیه می‌شوند، حتی اگر شمارنده صفر وجود نداشته باشد.

استفاده از کتابخانه‌های ته چک

پس‌از افزودن کتابخانه‌های تهی به‌عنوان وابستگی به واحدتان، می‌توانید آن‌ها را در فایل‌هایتان بگنجانید. در اینجا نمونه‌هایی از کتابخانه‌های تهی در سیستم ساخت (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

مثال در جاوا:

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 همچنین هدفی در سیستم ساخت ایجاد می‌کند که می‌توانید از آن برای مدیریت میانای برنامه‌سازی کاربردی واحد استفاده کنید. When built, foo-freeze-api adds a new API definition under api_dir or aidl_api/name, depending on the Android version, and adds a .hash file, both representing the newly frozen version of the interface. foo-freeze-api also updates the versions_with_info property to reflect the additional version and imports for the version. اساساً، imports در versions_with_info از فیلد imports کپی شده است. اما جدیدترین نسخه پایدار در imports در versions_with_info برای وارد کردن مشخص شده است که نسخه صریحی ندارد. پس‌از مشخص شدن دارایی versions_with_info، سیستم ساخت بررسی‌های سازگاری بین نسخه‌های ثابت و همچنین بین «بالاترین شاخه» (ToT) و جدیدترین نسخه ثابت را اجرا می‌کند.

علاوه‌براین، باید تعریف API نسخه 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» ثابت شوند.

پایداری واردات

به‌روزرسانی نسخه‌های واردات برای نسخه‌های ثابت یک میانای سازگار با عقب در لایه Stable AIDL است. بااین‌حال، به‌روزرسانی این موارد مستلزم به‌روزرسانی همه سرورها و کارخواهانی است که از نسخه قبلی میانای کاربری استفاده می‌کنند، و برخی‌از برنامه‌ها ممکن است هنگام ترکیب نسخه‌های مختلف انواع دچار مشکل شوند. به‌طورکلی، برای بسته‌های فقط نوع یا بسته‌های رایج، این کار ایمن است زیرا کد باید ازقبل برای مدیریت انواع ناشناخته از تراکنش‌های IPC نوشته شده باشد.

در کد پلاتفرم Android،‏ android.hardware.graphics.common بزرگ‌ترین نمونه از این نوع ارتقای نسخه است.

استفاده از میاناهای نسخه‌دار

روش‌های میانا

در زمان اجرا، هنگام تلاش برای فراخوانی روش‌های جدید در سرور قدیمی، کارخواه‌های جدید بسته به زیرینه، یا خطا یا استثنا دریافت می‌کنند.

  • cpp backend gets ::android::UNKNOWN_TRANSACTION.
  • ndk backend gets STATUS_UNKNOWN_TRANSACTION.
  • java پشت صحنه android.os.RemoteException را با پیامی دریافت می‌کند که می‌گوید میانای برنامه‌سازی کاربردی اجرا نشده است.

برای استراتژی‌های مدیریت این موضوع، نسخه‌های پُرسمان و استفاده از پیش‌فرض‌ها را ببینید.

Parcelables

وقتی فیلدهای جدیدی به parcelable اضافه می‌شود، کارخواه‌ها و سرورهای قدیمی آن‌ها را حذف می‌کنند. وقتی کارخواهان و سرورهای جدید بسته‌های قدیمی را دریافت می‌کنند، مقادیر پیش‌فرض برای فیلدهای جدید به‌طور خودکار تکمیل می‌شود. این یعنی پیش‌فرض‌ها باید برای همه فیلدهای جدید در یک بسته قابل انتقال مشخص شوند.

مشتریان نباید انتظار داشته باشند که سرورها از فیلدهای جدید استفاده کنند، مگراینکه بدانند سرور درحال پیاده‌سازی نسخه‌ای است که فیلد در آن تعریف شده است (به پرس‌وجو کردن نسخه‌ها مراجعه کنید).

تعداد ثابت‌ها و شمارش‌ها

به‌همین ترتیب، کارخواه‌ها و سرورها باید مقادیر ثابت و شمارشگرهای ناشناخته را درصورت لزوم رد یا نادیده بگیرند، زیرا ممکن است در آینده موارد بیشتری اضافه شود. برای مثال، وقتی سرور شمارنده‌ای را دریافت می‌کند که با آن آشنا نیست، نباید آن را متوقف کند. سرور باید شمارشگر را نادیده بگیرد یا چیزی برگرداند تا کارخواه بداند که در این پیاده‌سازی پشتیبانی نمی‌شود.

اتحادیه‌ها

اگر گیرنده قدیمی باشد و از فیلد جدید اطلاعی نداشته باشد، تلاش برای ارسال اتحاد با فیلد جدید ناموفق خواهد بود. پیاده‌سازی هرگز اتحاد با فیلد جدید را نخواهد دید. اگر تراکنش یک‌طرفه باشد، خرابی نادیده گرفته می‌شود؛ درغیراین‌صورت، خطا 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 فقط پس‌از ثابت شدن میان‌ها، جدیدترین ماتریس سازگاری را هدف‌یابی می‌کنند، بنابراین براساس RELEASE_AIDL_USE_UNFROZEN هیچ تفاوتی در کتابخانه‌های AIDL وجود ندارد.

ماتریس‌ها

میاناهای متعلق به شریک به ماتریس‌های سازگاری مختص دستگاه یا مختص محصول اضافه می‌شوند که دستگاه درطول توسعه هدف‌یابی می‌کند. بنابراین وقتی نسخه جدید و منجمدنشده‌ای از میانای کاربری به ماتریس سازگاری اضافه می‌شود، نسخه‌های منجمد قبلی باید برای RELEASE_AIDL_USE_UNFROZEN=false باقی بمانند. می‌توانید این کار را بااستفاده از فایل‌های مختلف ماتریس سازگاری برای پیکربندی‌های مختلف RELEASE_AIDL_USE_UNFROZEN یا مجاز کردن هر دو نسخه در یک فایل ماتریس سازگاری که در همه پیکربندی‌ها استفاده می‌شود انجام دهید.

برای مثال، هنگام افزودن نسخه ۴ رفع انجمادشده، از <version>3-4</version> استفاده کنید.

وقتی نسخه ۴ ثابت شد می‌توانید نسخه ۳ را از ماتریس سازگاری بردارید زیرا وقتی 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

The largest difference in HAL development with flag-based development is the requirement for HAL implementations to be backward compatible with the last frozen version to work when RELEASE_AIDL_USE_UNFROZEN is false. درنظر گرفتن سازگاری با نسخه قدیمی در پیاده‌سازی‌ها و کد دستگاه تمرین جدیدی است. استفاده از میاناهای نسخه‌بندی‌شده را ببینید.

ملاحظات سازگاری با نسخه‌های قبلی معمولاً برای کارخواه‌ها و سرورها، و برای کد چارچوب و کد فروشنده یکسان است، اما تفاوت‌های ظریفی وجود دارد که باید از آن‌ها آگاه باشید، زیرا اکنون عملاً دو نسخه را پیاده‌سازی می‌کنید که از کد منبع یکسانی استفاده می‌کنند (نسخه فعلی و منجمدنشده).

مثال: یک میانای کاربری سه نسخه منجمد دارد. میانای کاربری با روش جدیدی به‌روزرسانی شده است. کارخواه و سرویس هر دو برای استفاده از کتابخانه نسخه ۴ جدید به‌روزرسانی می‌شوند. ازآنجایی‌که کتابخانه V4 براساس نسخه منجمدنشده‌ای از میانای کاربری است، وقتی 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 در فایل ساخت دستگاه اضافه کنید:

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 vts_treble_vintf_vendor_test و vts_treble_vintf_framework_test تشخیص می‌دهند که چه زمانی از میانای افزونه غیرمنجمد در دستگاه منتشرشده استفاده می‌شود و خطا نشان می‌دهند.

اگر رابط افزونه جدید نباشد و نسخه قبلاً منجمدشده‌ای داشته باشد، به آن نسخه قبلاً منجمدشده برمی‌گردد و مراحل اضافی لازم نیست.

‫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 قبلی است تا دستگاه انتشار را منعکس کند. در Android 14 و نسخه‌های پایین‌تر، این تمایز با شاخه‌های 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 این واحدها را تولید می‌کند:

  • براساس نسخه ۱
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • براساس نسخه ۲ (جدیدترین نسخه پایدار)
    • 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 تبدیل می‌شود

نام‌های فایل خروجی همیشه با نام‌های ماژول یکسان است.

  • براساس نسخه ۱: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • براساس نسخه ۲: 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();

برای زبان جاوا، طرف دور باید 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

مثال در جاوا:

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 پایدار استفاده کنید.

  1. همه وابستگی‌های میان‌کارتان را شناسایی کنید. برای هر بسته‌ای که واسط به آن وابسته است، تعیین کنید که آیا بسته در AIDL پایدار تعریف شده است یا نه. اگر تعریف نشده باشد، بسته باید تبدیل شود.

  2. همه بسته‌های قابل انتقال در میانه‌تان را به بسته‌های قابل انتقال پایدار تبدیل کنید (فایل‌های میانه‌تان می‌توانند بدون تغییر باقی بمانند). این کار را با بیان مستقیم ساختار آن‌ها در فایل‌های AIDL انجام دهید. کلاس‌های مدیریت باید بازنویسی شوند تا از این انواع جدید استفاده کنند. این کار را می‌توانید قبل‌از ایجاد بسته aidl_interface (در زیر) انجام دهید.

  3. بسته aidl_interface (طبق توضیحات بالا) ایجاد کنید که حاوی نام واحد، وابستگی‌های آن، و هر اطلاعات دیگری باشد که نیاز دارید. برای اینکه پایدار شود (نه فقط ساختاریافته)، باید نسخه‌بندی هم شود. برای اطلاعات بیشتر، واسط‌های نسخه‌بندی را ببینید.