AIDL پایدار

اندروید ۱۰ پشتیبانی از زبان تعریف رابط کاربری پایدار اندروید (AIDL) را اضافه می‌کند، روشی جدید برای پیگیری رابط برنامه کاربردی (API) و رابط دودویی برنامه (ABI) که توسط رابط‌های AIDL ارائه می‌شود. AIDL پایدار دقیقاً مانند AIDL کار می‌کند، اما سیستم ساخت، سازگاری رابط کاربری را پیگیری می‌کند و محدودیت‌هایی در مورد کارهایی که می‌توانید انجام دهید وجود دارد:

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

AIDL ساختاریافته در مقابل پایدار

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

AIDL پایدار به AIDL ساختاریافته نیاز دارد تا سیستم ساخت و کامپایلر بتوانند بفهمند که آیا تغییرات ایجاد شده در parcelables با نسخه‌های قبلی سازگار هستند یا خیر. با این حال، همه رابط‌های ساختاریافته پایدار نیستند. برای پایدار بودن، یک رابط باید فقط از انواع ساختاریافته استفاده کند و همچنین باید از ویژگی‌های نسخه‌بندی زیر استفاده کند. برعکس، اگر از سیستم ساخت اصلی برای ساخت آن استفاده شود یا اگر 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 که رابط را تشکیل می‌دهند. مسیر برای یک Foo از نوع AIDL که در بسته 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 ) برای اشاره به یک نسخه خاص. مشخص کردن یک نسخه از اندروید ۱۲ پشتیبانی می‌شود.
  • versions : نسخه‌های قبلی رابط که در api_dir فریز شده‌اند، از اندروید ۱۱ به بعد، versions در aidl_api/ name فریز می‌شوند. اگر هیچ نسخه فریز شده‌ای از یک رابط وجود نداشته باشد، این مورد نباید مشخص شود و بررسی‌های سازگاری انجام نخواهد شد. این فیلد برای اندروید ۱۳ و بالاتر با versions_with_info جایگزین شده است.
  • versions_with_info : فهرستی از تاپل‌ها که هر کدام شامل نام یک نسخه فریز شده و فهرستی از نسخه‌های وارد شده از سایر ماژول‌های aidl_interface است که این نسخه از aidl_interface وارد کرده است. تعریف نسخه V یک رابط AIDL IFACE در aidl_api/ IFACE / V قرار دارد. این فیلد در اندروید ۱۳ معرفی شد و قرار نیست مستقیماً در 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 : پرچم اختیاری برای روشن یا خاموش کردن ردیابی. از اندروید ۱۴ به بعد، پیش‌فرض برای بک‌اندهای cpp و java true است.
  • host_supported : پرچم اختیاری که وقتی روی true تنظیم شود، کتابخانه‌های تولید شده را در دسترس محیط میزبان قرار می‌دهد.
  • unstable : پرچم اختیاری که برای مشخص کردن عدم نیاز به پایدار بودن این رابط استفاده می‌شود. وقتی این مقدار روی true تنظیم شده باشد، سیستم ساخت نه نسخه پشتیبان API را برای رابط ایجاد می‌کند و نه نیازی به به‌روزرسانی آن دارد.
  • frozen : پرچم اختیاری که وقتی روی true تنظیم می‌شود به این معنی است که رابط از نسخه قبلی رابط هیچ تغییری نکرده است. این امکان بررسی‌های بیشتر در زمان ساخت را فراهم می‌کند. وقتی روی false تنظیم می‌شود، به این معنی است که رابط در حال توسعه است و تغییرات جدیدی دارد، بنابراین اجرای foo-freeze-api یک نسخه جدید تولید می‌کند و به طور خودکار مقدار آن را به true تغییر می‌دهد. معرفی شده در اندروید ۱۴.
  • backend.<type>.enabled : این پرچم‌ها هر یک از backendهایی را که کامپایلر AIDL برای آنها کد تولید می‌کند، فعال یا غیرفعال می‌کنند. چهار backend پشتیبانی می‌شوند: Java، C++، NDK و Rust. backendهای Java، C++ و NDK به طور پیش‌فرض فعال هستند. اگر به هر یک از این سه backend نیازی نباشد، باید صریحاً غیرفعال شود. Rust به طور پیش‌فرض تا اندروید ۱۵ غیرفعال است.
  • backend.<type>.apex_available : فهرست نام‌های APEX که کتابخانه‌ی خرد تولید شده برای آنها در دسترس است.
  • backend.[cpp|java].gen_log : پرچم اختیاری که کنترل می‌کند آیا کد اضافی برای جمع‌آوری اطلاعات در مورد تراکنش تولید شود یا خیر.
  • backend.[cpp|java].vndk.enabled : پرچم اختیاری برای تبدیل این رابط به بخشی از VNDK. مقدار پیش‌فرض false است.
  • backend.[cpp|ndk].additional_shared_libraries : این فلگ که در اندروید ۱۴ معرفی شد، وابستگی‌هایی را به کتابخانه‌های بومی اضافه می‌کند. این فلگ با ndk_header و cpp_header مفید است.
  • backend.java.sdk_version : پرچم اختیاری برای مشخص کردن نسخه SDK که کتابخانه خرد جاوا بر اساس آن ساخته شده است. مقدار پیش‌فرض "system_current" است. وقتی backend.java.platform_apis true است، این پرچم نباید تنظیم شود.
  • backend.java.platform_apis : پرچم اختیاری که باید روی true تنظیم شود، زمانی که کتابخانه‌های تولید شده نیاز به ساخت در برابر API پلتفرم به جای SDK دارند.

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

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

رابط‌ها در AIDL پایدار مشابه رابط‌های سنتی هستند، با این تفاوت که اجازه استفاده از parcelableهای بدون ساختار را ندارند (زیرا این‌ها پایدار نیستند! به بخش 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.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"],
    ...
}

مثال در زبان سی پلاس پلاس:

#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 همچنین یک هدف در سیستم ساخت ایجاد می‌کند که می‌توانید از آن برای مدیریت API ماژول استفاده کنید. foo-freeze-api پس از ساخت، بسته به نسخه اندروید، یک تعریف API جدید در api_dir یا aidl_api/ name اضافه می‌کند و یک فایل .hash اضافه می‌کند که هر دو نشان‌دهنده نسخه تازه فریز شده رابط هستند. foo-freeze-api همچنین ویژگی versions_with_info را به‌روزرسانی می‌کند تا نسخه اضافی و imports مربوط به نسخه را منعکس کند. اساساً، imports در versions_with_info از فیلد imports کپی می‌شوند. اما آخرین نسخه پایدار در imports در versions_with_info برای import مشخص شده است که نسخه صریحی ندارد. پس از مشخص شدن ویژگی versions_with_info ، سیستم ساخت، بررسی‌های سازگاری بین نسخه‌های فریز شده و همچنین بین Top of Tree (ToT) و آخرین نسخه فریز شده را اجرا می‌کند.

علاوه بر این، شما باید تعریف API نسخه ToT را مدیریت کنید. هر زمان که یک API به‌روزرسانی می‌شود، foo-update-api را اجرا کنید تا aidl_api/ name /current که شامل تعریف API نسخه ToT است، به‌روزرسانی شود.

برای حفظ پایداری یک رابط، مالکان می‌توانند موارد جدیدی اضافه کنند:

  • متدهای انتهای یک رابط (یا متدهایی با سریال‌های جدید که به صراحت تعریف شده‌اند)
  • عناصر انتهای یک parcelable (نیاز به اضافه کردن یک مقدار پیش‌فرض برای هر عنصر دارد)
  • مقادیر ثابت
  • در اندروید ۱۱، شمارنده‌ها
  • در اندروید ۱۲، فیلدها تا انتهای یک union

هیچ اقدام دیگری مجاز نیست و هیچ کس دیگری نمی‌تواند یک رابط را تغییر دهد (در غیر این صورت، خطر تداخل با تغییراتی که مالک ایجاد می‌کند، وجود دارد).

برای آزمایش اینکه همه رابط‌ها برای انتشار مسدود شده‌اند، می‌توانید با تنظیم متغیرهای محیطی زیر، برنامه را بسازید:

  • AIDL_FROZEN_REL=true m ... - ساخت مستلزم آن است که تمام رابط‌های پایدار AIDL که owner: فیلد مشخص شده.
  • AIDL_FROZEN_OWNERS="aosp test" - ساخت مستلزم آن است که تمام رابط‌های پایدار AIDL با فیلد owner: که به عنوان "aosp" یا "test" مشخص شده است، مسدود شوند.

ثبات واردات

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

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

استفاده از رابط‌های نسخه‌بندی‌شده

روش‌های رابط

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

  • تابع backend مربوط به cpp تابع ::android::UNKNOWN_TRANSACTION را دریافت می‌کند.
  • ndk backend STATUS_UNKNOWN_TRANSACTION را دریافت می‌کند.
  • خطای android.os.RemoteException در بک‌اند java رخ می‌دهد و پیامی مبنی بر عدم پیاده‌سازی API نمایش داده می‌شود.

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

بسته‌های قابل بسته‌بندی

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

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

انوم‌ها و ثابت‌ها

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

اتحادیه‌ها

تلاش برای ارسال یک union با یک فیلد جدید در صورتی که گیرنده قدیمی باشد و از وجود فیلد اطلاعی نداشته باشد، با شکست مواجه می‌شود. پیاده‌سازی هرگز union با فیلد جدید را نمی‌بیند. اگر تراکنش یک طرفه باشد، این شکست نادیده گرفته می‌شود؛ در غیر این صورت خطای BAD_VALUE (برای بک‌اند C++ یا NDK) یا IllegalArgumentException (برای بک‌اند جاوا) رخ می‌دهد. این خطا در صورتی دریافت می‌شود که کلاینت در حال ارسال یک مجموعه union به فیلد جدید به یک سرور قدیمی باشد، یا زمانی که یک کلاینت قدیمی union را از یک سرور جدید دریافت کند.

مدیریت نسخه‌های متعدد

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

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

اگر کتابخانه رابط توسط ماژول‌های مختلف زیادی استفاده می‌شود، ایجاد cc_defaults ، java_defaults و rust_defaults برای هر گروه از کتابخانه‌ها و فرآیندهایی که نیاز به استفاده از یک نسخه دارند، می‌تواند مفید باشد. هنگام معرفی نسخه جدیدی از رابط، می‌توان این پیش‌فرض‌ها را به‌روزرسانی کرد و همه ماژول‌هایی که از آنها استفاده می‌کنند، با هم به‌روزرسانی می‌شوند و اطمینان حاصل می‌شود که از نسخه‌های مختلف رابط استفاده نمی‌کنند.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

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

aidl_interfaces_defaults می‌تواند برای نگه‌داشتن یک تعریف از آخرین نسخه‌های وابستگی‌ها برای یک aidl_interface استفاده شود که می‌تواند در یک مکان به‌روزرسانی شود و توسط همه ماژول‌های aidl_interface که می‌خواهند آن رابط مشترک را وارد کنند، استفاده شود.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

توسعه مبتنی بر پرچم

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

AIDL از نسخه پشتیبان زمان اجرا برای این کتابخانه‌های رابط کاربریِ از حالت انجماد خارج‌شده پشتیبانی می‌کند تا کد بتواند بر اساس آخرین نسخه از حالت انجماد خارج‌شده نوشته شود و همچنان در دستگاه‌های منتشرشده مورد استفاده قرار گیرد. رفتار سازگار با نسخه‌های قبلی کلاینت‌ها مشابه رفتار موجود است و با وجود نسخه پشتیبان، پیاده‌سازی‌ها نیز باید از این رفتارها پیروی کنند. به بخش «استفاده از رابط‌های نسخه‌بندی‌شده» مراجعه کنید.

پرچم ساخت AIDL

پرچمی که این رفتار را کنترل می‌کند RELEASE_AIDL_USE_UNFROZEN است که در build/release/build_flags.bzl تعریف شده است. true به این معنی است که نسخه غیرمتمرکز رابط در زمان اجرا استفاده می‌شود و false به این معنی است که کتابخانه‌های نسخه‌های غیرمتمرکز همگی مانند آخرین نسخه غیرمتمرکز خود رفتار می‌کنند. می‌توانید برای توسعه محلی، پرچم را به true تغییر دهید، اما قبل از انتشار باید آن را به false برگردانید. معمولاً توسعه با پیکربندی انجام می‌شود که پرچم روی true تنظیم شده است.

ماتریس سازگاری و مانیفست‌ها

اشیاء رابط فروشنده (اشیاء VINTF) نسخه‌های مورد انتظار و نسخه‌های ارائه شده در هر دو طرف رابط فروشنده را تعریف می‌کنند.

بیشتر دستگاه‌های غیر Cuttlefish تنها پس از ثابت شدن رابط‌ها، جدیدترین ماتریس سازگاری را هدف قرار می‌دهند، بنابراین هیچ تفاوتی در کتابخانه‌های AIDL مبتنی بر RELEASE_AIDL_USE_UNFROZEN وجود ندارد.

ماتریس‌ها

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

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

وقتی نسخه ۴ مسدود شده است، می‌توانید نسخه ۳ را از ماتریس سازگاری حذف کنید زیرا نسخه ۴ مسدود شده زمانی استفاده می‌شود که RELEASE_AIDL_USE_UNFROZEN false باشد.

مانیفست‌ها

در اندروید ۱۵، تغییری در 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 مقادیر پیش‌فرض خود را دارند). کلاینت‌های چارچوب اندروید ملزم به سازگاری به عقب با نسخه‌های قبلی اضافی هستند، اما این یک جزئیات جدید برای کلاینت‌های فروشنده و کلاینت‌های رابط‌های متعلق به شریک است.

تغییرات پیاده‌سازی HAL

بزرگترین تفاوت در توسعه HAL با توسعه مبتنی بر پرچم، الزام سازگاری پیاده‌سازی‌های HAL با آخرین نسخه فریز شده برای کار کردن در زمانی است که RELEASE_AIDL_USE_UNFROZEN false باشد. در نظر گرفتن سازگاری با نسخه‌های قبلی در پیاده‌سازی‌ها و کد دستگاه، یک تمرین جدید است. به بخش «استفاده از رابط‌های نسخه‌بندی شده» مراجعه کنید.

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

مثال: یک رابط کاربری سه نسخه فریز شده دارد. رابط کاربری با یک متد جدید به‌روزرسانی می‌شود. کلاینت و سرویس هر دو برای استفاده از کتابخانه جدید نسخه ۴ به‌روزرسانی می‌شوند. از آنجایی که کتابخانه V4 مبتنی بر یک نسخه فریز نشده از رابط کاربری است، وقتی RELEASE_AIDL_USE_UNFROZEN برابر false باشد، مانند آخرین نسخه فریز شده، یعنی نسخه ۳، رفتار می‌کند و از استفاده از متد جدید جلوگیری می‌کند.

وقتی رابط کاربری قفل شده باشد، تمام مقادیر RELEASE_AIDL_USE_UNFROZEN از آن نسخه قفل شده استفاده می‌کنند و کدی که سازگاری معکوس را مدیریت می‌کند، می‌تواند حذف شود.

هنگام فراخوانی متدها در callbackها، باید به طور مناسب حالتی را که UNKNOWN_TRANSACTION برگردانده می‌شود، مدیریت کنید. کلاینت‌ها ممکن است دو نسخه مختلف از یک callback را بر اساس پیکربندی انتشار پیاده‌سازی کنند، بنابراین نمی‌توانید فرض کنید که کلاینت جدیدترین نسخه را ارسال می‌کند و متدهای جدید ممکن است آن را برگردانند. این مشابه نحوه حفظ سازگاری رو به عقب کلاینت‌های AIDL پایدار با سرورها است که در Use versioned interfaces توضیح داده شده است.

// 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_treble_vintf_vendor_test و vts_treble_vintf_framework_test VTS تشخیص می‌دهند که چه زمانی یک رابط افزونه‌ی غیرمتحرک در یک دستگاه آزاد شده استفاده می‌شود و خطا می‌دهند.

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

ده پا به عنوان ابزاری برای توسعه

هر سال پس از اینکه VINTF مسدود می‌شود، ما ماتریس سازگاری چارچوب (FCM) target-level و PRODUCT_SHIPPING_API_LEVEL مربوط به Cuttlefish را تنظیم می‌کنیم تا منعکس‌کننده دستگاه‌هایی باشند که با نسخه سال آینده عرضه می‌شوند. ما target-level و PRODUCT_SHIPPING_API_LEVEL را تنظیم می‌کنیم تا مطمئن شویم که یک دستگاه پرتاب وجود دارد که آزمایش شده و الزامات جدید برای نسخه سال آینده را برآورده می‌کند.

وقتی RELEASE_AIDL_USE_UNFROZEN true باشد، Cuttlefish برای توسعه نسخه‌های آینده اندروید استفاده می‌شود. این زبان سطح FCM و PRODUCT_SHIPPING_API_LEVEL نسخه اندروید سال آینده را هدف قرار می‌دهد و مستلزم آن است که الزامات نرم‌افزار فروشنده (VSR) نسخه بعدی را برآورده کند.

وقتی RELEASE_AIDL_USE_UNFROZEN برابر با false باشد، Cuttlefish target-level قبلی و PRODUCT_SHIPPING_API_LEVEL را برای انعکاس یک دستگاه انتشار دارد. در اندروید ۱۴ و پایین‌تر، این تمایز با شاخه‌های مختلف Git انجام می‌شود که تغییر در target-level FCM، سطح API ارسال یا هر کد دیگری که انتشار بعدی را هدف قرار می‌دهد، را دریافت نمی‌کنند.

قوانین نامگذاری ماژول

در اندروید ۱۱، برای هر ترکیبی از نسخه‌ها و بک‌اندهای فعال، یک ماژول کتابخانه‌ی خرد به طور خودکار ایجاد می‌شود. برای ارجاع به یک ماژول کتابخانه‌ی خرد خاص برای لینک دادن، از نام ماژول 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. اولی برای برنامه‌ها و دومی برای استفاده از پلتفرم تا اندروید ۱۳ است. در اندروید ۱۳ و بالاتر، فقط ndk استفاده کنید.
    • rust برای بک‌اند Rust.

فرض کنید ماژولی با نام foo وجود دارد و آخرین نسخه آن ۲ است و از 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)

در مقایسه با اندروید ۱۱:

  • 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 پایدار ایجاد نمی‌کند. از اندروید ۱۲، نام ماژول تولید شده از یک رابط AIDL پایدار همیشه شامل نسخه آن است.

متدهای جدید رابط کاربری متا

اندروید ۱۰ چندین متد رابط کاربری متا برای 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();

برای زبان جاوا، سمت ریموت باید getInterfaceVersion() و getInterfaceHash() را به صورت زیر پیاده‌سازی کند (برای جلوگیری از اشتباهات کپی و پیست، به جای IFoo از super استفاده شده است. بسته به پیکربندی javac ، ممکن است برای غیرفعال کردن هشدارها به حاشیه‌نویسی @SuppressWarnings("static") نیاز باشد):

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 در اندروید ۱۳ و بالاتر:

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 وجود داشته‌اند) نیازی به بازنویسی (override) در کلاس پیش‌فرض impl ندارند.

تبدیل AIDL موجود به AIDL ساختاریافته یا پایدار

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

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

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

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