AIDL پایدار

Android 10 پشتیبانی از زبان تعریف رابط اندروید پایدار (AIDL) را اضافه می کند، روشی جدید برای پیگیری رابط برنامه کاربردی (API) / رابط باینری برنامه (ABI) ارائه شده توسط رابط های AIDL. AIDL پایدار دارای تفاوت های کلیدی زیر با AIDL است:

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

تعریف رابط AIDL

تعریف aidl_interface به این صورت است:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions: ["1", "2"],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
    },

}
  • name : نام ماژول رابط AIDL که به طور منحصر به فرد یک رابط AIDL را شناسایی می کند.
  • srcs : لیست فایل های منبع AIDL که رابط را تشکیل می دهند. مسیر یک 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 شما از یک رابط یا یک بسته بندی از aidl_interface دیگر استفاده می کند، نام آن را در اینجا قرار دهید. این نام می تواند به خودی خود برای اشاره به آخرین نسخه یا نامی با پسوند نسخه (مانند -V1 ) برای اشاره به یک نسخه خاص باشد. تعیین نسخه از اندروید 12 پشتیبانی می شود
  • versions : نسخه‌های قبلی رابط کاربری که تحت api_dir ، از اندروید ۱۱ شروع می‌شود، versions تحت aidl_api/ name فریز می‌شوند. اگر نسخه ثابتی از یک رابط وجود نداشته باشد، این نباید مشخص شود، و بررسی سازگاری وجود نخواهد داشت.
  • stability : پرچم اختیاری برای وعده پایداری این رابط. در حال حاضر فقط "vintf" را پشتیبانی می کند. اگر تنظیم نشده باشد، این مربوط به یک رابط با ثبات در این زمینه کامپایل است (بنابراین یک رابط بارگذاری شده در اینجا فقط می تواند با چیزهایی که با هم کامپایل شده اند، به عنوان مثال در system.img استفاده شود). اگر این روی "vintf" تنظیم شود، با یک وعده پایداری مطابقت دارد: رابط باید تا زمانی که استفاده می‌شود پایدار بماند.
  • gen_trace : پرچم اختیاری برای روشن یا خاموش کردن ردیابی. پیش فرض false است.
  • host_supported : پرچم اختیاری که وقتی روی true تنظیم شود، کتابخانه های تولید شده را در دسترس محیط میزبان قرار می دهد.
  • unstable : پرچم اختیاری مورد استفاده برای علامت گذاری که این رابط نیازی به پایدار بودن ندارد. هنگامی که این مقدار روی true تنظیم شود، سیستم ساخت نه خالی API را برای رابط ایجاد می کند و نه نیاز به به روز رسانی آن را دارد.
  • backend.<type>.enabled : این پرچم ها هر یک از backend هایی را که کامپایلر AIDL برای آنها کد تولید می کند تغییر می دهد. در حال حاضر، سه Backend پشتیبانی می‌شوند: java ، cpp و ndk . پشتیبان ها همه به طور پیش فرض فعال هستند. هنگامی که به پشتیبان خاصی نیاز نیست، باید به طور صریح غیرفعال شود.
  • backend.<type>.apex_available : لیستی از نام های APEX که کتابخانه خرد تولید شده برای آنها در دسترس است.
  • backend.[cpp|java].gen_log : پرچم اختیاری که کنترل می‌کند کد اضافی برای جمع‌آوری اطلاعات مربوط به تراکنش تولید شود یا خیر.
  • backend.[cpp|java].vndk.enabled : پرچم اختیاری برای تبدیل این رابط به بخشی از VNDK. پیش فرض false است.
  • backend.java.platform_apis : پرچم اختیاری که کنترل می کند آیا کتابخانه خرد جاوا در برابر API های خصوصی پلتفرم ساخته شده است یا خیر. هنگامی که stability روی "vintf" تنظیم شده است، باید روی "true" تنظیم شود.
  • backend.java.sdk_version : پرچم اختیاری برای تعیین نسخه SDK که کتابخانه خرد جاوا بر اساس آن ساخته شده است. پیش فرض "system_current" است. وقتی backend.java.platform_apis درست است، این نباید تنظیم شود.
  • backend.java.platform_apis : پرچم اختیاری که باید در زمانی که کتابخانه های تولید شده نیاز به ساخت بر اساس API پلتفرم به جای SDK دارند، روی true تنظیم شود.

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

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

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

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

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

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

مثال در C++:

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

مثال در جاوا:

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

رابط های نسخه سازی

اعلان یک ماژول با نام foo همچنین یک هدف در سیستم ساخت ایجاد می کند که می توانید از آن برای مدیریت API ماژول استفاده کنید. هنگامی که ساخته شد، foo-freeze-api یک تعریف API جدید تحت api_dir یا aidl_api/ name ، بسته به نسخه Android اضافه می‌کند، و یک فایل .hash اضافه می‌کند که هر دو نسخه تازه منجمد شده رابط را نشان می‌دهد. ساخت این ویژگی versions ها را نیز به روز می کند تا نسخه اضافی را منعکس کند. هنگامی که ویژگی versions ها مشخص شد، سیستم ساخت، بررسی های سازگاری را بین نسخه های فریز شده و همچنین بین Top of Tree (ToT) و آخرین نسخه فریز شده اجرا می کند.

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

برای حفظ ثبات یک رابط، صاحبان می توانند موارد زیر را اضافه کنند:

  • روش‌های پایان یک رابط (یا روش‌هایی با سریال‌های جدید به‌صراحت تعریف شده)
  • عناصر در انتهای یک بسته بندی (نیاز به اضافه شدن یک پیش فرض برای هر عنصر دارد)
  • ارزش های ثابت
  • در اندروید 11، شمارشگرها
  • در اندروید 12، فیلدها تا انتهای یک اتحادیه

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

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

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

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

روش های رابط

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

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

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

بسته بندی ها

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

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

اعداد و ثابت ها

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

اتحادیه ها

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

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

در اندروید 11، برای هر ترکیبی از نسخه‌ها و باطن‌های فعال، یک ماژول کتابخانه خرد به‌طور خودکار ایجاد می‌شود. برای ارجاع به یک ماژول کتابخانه خرد خاص برای پیوند، از نام ماژول aidl_interface استفاده نکنید، بلکه از نام ماژول کتابخانه خرد استفاده کنید که ifacename - version - backend است.

  • ifacename : نام ماژول aidl_interface
  • version یکی از
    • V version-number برای نسخه های فریز شده
    • V latest-frozen-version-number + 1 برای نسخه نوک درخت (هنوز منجمد نشده)
  • backend یکی از این دو است
    • java برای باطن جاوا،
    • cpp برای باطن ++C،
    • ndk_platform ndk باطن NDK. اولی برای برنامه ها و دومی برای استفاده از پلتفرم است.

فرض کنید یک ماژول با نام foo وجود دارد و آخرین نسخه آن 2 است و از NDK و C++ پشتیبانی می کند. در این مورد، AIDL این ماژول ها را تولید می کند:

  • بر اساس نسخه 1
    • foo-V1-(java|cpp|ndk|ndk_platform)
  • بر اساس نسخه 2 (آخرین نسخه پایدار)
    • foo-V2-(java|cpp|ndk|ndk_platform)
  • بر اساس نسخه ToT
    • foo-V3-(java|cpp|ndk|ndk_platform)

در مقایسه با اندروید 11،

  • foo- backend که به آخرین نسخه پایدار اشاره می کند تبدیل به foo- V2 - backend می شود
  • foo-unstable- backend ، که به نسخه foo- V3 - backend می شود.

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

  • بر اساس نسخه 1: foo-V1-(cpp|ndk|ndk_platform).so
  • بر اساس نسخه 2: foo-V2-(cpp|ndk|ndk_platform).so
  • بر اساس نسخه ToT: foo-V3-(cpp|ndk|ndk_platform).so

توجه داشته باشید که کامپایلر AIDL یک ماژول نسخه unstable یا یک ماژول غیرنسخه ای برای یک رابط AIDL پایدار ایجاد نمی کند. از اندروید 12، نام ماژول تولید شده از یک رابط پایدار AIDL همیشه شامل نسخه آن می شود.

روش های جدید رابط متا

اندروید 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();

برای زبان جاوا، سمت راه دور باید getInterfaceVersion() و getInterfaceHash() را به صورت زیر پیاده سازی کند:

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return IFoo.VERSION; }

    @Override
    public final String getInterfaceHash() { return IFoo.HASH; }
}

دلیل آن این است که کلاس‌های تولید شده ( IFoo ، IFoo.Stub ، و غیره) بین مشتری و سرور به اشتراک گذاشته می‌شوند (برای مثال، کلاس‌ها می‌توانند در مسیر کلاس راه‌اندازی باشند). وقتی کلاس‌ها به اشتراک گذاشته می‌شوند، سرور نیز با جدیدترین نسخه کلاس‌ها مرتبط می‌شود، حتی اگر ممکن است با نسخه قدیمی‌تری از رابط ساخته شده باشد. اگر این رابط متا در کلاس اشتراکی پیاده سازی شود، همیشه جدیدترین نسخه را برمی گرداند. با این حال، با پیاده‌سازی روش فوق، شماره نسخه رابط در کد سرور تعبیه می‌شود (زیرا IFoo.VERSION یک static final int است که در صورت ارجاع درون خطی می‌شود) و بنابراین روش می‌تواند نسخه دقیقی را که سرور ساخته شده است برگرداند. با.

برخورد با رابط های قدیمی تر

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

با AIDL پایدار، مشتریان کنترل بیشتری دارند. در سمت کلاینت، می توانید یک پیاده سازی پیش فرض را روی یک رابط AIDL تنظیم کنید. یک روش در اجرای پیش‌فرض تنها زمانی فراخوانی می‌شود که متد در سمت راه دور پیاده‌سازی نشده باشد (زیرا با نسخه قدیمی‌تر رابط ساخته شده است). از آنجایی که پیش‌فرض‌ها به صورت سراسری تنظیم شده‌اند، نباید از زمینه‌های بالقوه مشترک استفاده شوند.

مثال در C++ در اندروید 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 پایدار استفاده کنید.

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

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

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