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_with_info: [
{
version: "1",
imports: ["ohter-aidl-V1"],
},
{
version: "2",
imports: ["other-aidl-V3"],
}
],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
-
name
: نام ماژول رابط AIDL که به طور منحصر به فرد یک رابط AIDL را شناسایی می کند. -
srcs
: لیستی از فایل های منبع AIDL که رابط را تشکیل می دهند. مسیر یک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
فریز میشوند. اگر نسخه ثابتی از یک رابط وجود نداشته باشد، این نباید مشخص شود، و بررسی سازگاری وجود نخواهد داشت. این قسمت باversions_with_info
برای 13 و بالاتر جایگزین شده است. -
versions_with_info
: فهرستی از تاپل ها، که هر کدام شامل نام یک نسخه ثابت و فهرستی با واردات نسخه های دیگر ماژول های aidl_interface است که این نسخه از aidl_interface وارد شده است. تعریف نسخه V یک رابط AIDL IFACE درaidl_api/ IFACE / V
قرار دارد. این فیلد در اندروید 13 معرفی شد و قرار نیست مستقیماً در Android.bp اصلاح شود. این فیلد با فراخوانی*-update-api
یا*-freeze-api
اضافه یا به روز می شود. همچنین، وقتی کاربر *-update-*-update-api
یا*-freeze-api
فراخوانی می کند، فیلدهایversions
ها به طور خودکار بهversions_with_info
منتقل می شوند. -
stability
: پرچم اختیاری برای وعده پایداری این رابط. در حال حاضر فقط"vintf"
را پشتیبانی می کند. اگر تنظیم نشده باشد، این مربوط به یک رابط با ثبات در این زمینه کامپایل است (بنابراین یک رابط بارگذاری شده در اینجا فقط می تواند با چیزهایی که با هم کامپایل شده اند، به عنوان مثال در system.img استفاده شود). اگر این روی"vintf"
تنظیم شود، با یک وعده پایداری مطابقت دارد: رابط باید تا زمانی که از آن استفاده میشود پایدار بماند. -
gen_trace
: پرچم اختیاری برای روشن یا خاموش کردن ردیابی. پیش فرضfalse
است. -
host_supported
: پرچم اختیاری که وقتی رویtrue
تنظیم میشود، کتابخانههای تولید شده را در دسترس محیط میزبان قرار میدهد. -
unstable
: پرچم اختیاری مورد استفاده برای نشان دادن این که این رابط نیازی به پایدار بودن ندارد. هنگامی که این مقدار رویtrue
تنظیم شود، سیستم ساخت نه خالی API را برای رابط ایجاد می کند و نه نیاز به به روز رسانی آن را دارد. -
backend.<type>.enabled
: این پرچم ها هر یک از backend هایی را که کامپایلر AIDL برای آنها کد تولید می کند تغییر می دهد. در حال حاضر، چهار Backend پشتیبانی میشوند: Java، C++، NDK و Rust. Java، C++، و Backendهای NDK به طور پیش فرض فعال هستند. اگر هر یک از این سه backend مورد نیاز نیست، باید به طور صریح غیرفعال شود. Rust به طور پیش فرض غیرفعال است. -
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
تنظیم شود.
برای هر ترکیبی از نسخه ها و باطن های فعال، یک کتابخانه خرد ایجاد می شود. برای چگونگی مراجعه به نسخه خاصی از کتابخانه خرد برای یک باطن خاص، قوانین نامگذاری ماژول را ببینید.
نوشتن فایل های 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"],
...
}
# or
rust_... {
name: ...,
rust_libs: ["my-module-name-rust"],
...
}
مثال در C++:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
مثال در جاوا:
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
، بسته به نسخه Android اضافه میکند، و یک فایل .hash
اضافه میکند که هر دو نسخه تازه منجمد شده رابط را نشان میدهد. foo-freeze-api همچنین ویژگی versions_with_info
را بهروزرسانی میکند تا نسخه اضافی و imports
نسخه را منعکس کند. اساساً imports
در versions_with_info
از قسمت imports
کپی می شود. اما آخرین نسخه پایدار در imports
در versions_with_info
برای وارداتی که نسخه واضحی ندارد مشخص شده است. هنگامی که ویژگی versions_with_info
مشخص شد، سیستم ساخت، بررسیهای سازگاری را بین نسخههای ثابت شده و همچنین بین 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".
ثبات واردات
بهروزرسانی نسخههای وارداتی برای نسخههای ثابت یک رابط، در لایه Stable AIDL سازگار است. با این حال، بهروزرسانی این موارد مستلزم بهروزرسانی همه سرورها و کلاینتهایی است که از نسخه قدیمی رابط استفاده میکنند، و ممکن است برخی از برنامهها هنگام مخلوط کردن نسخههای مختلف دچار اشتباه شوند. به طور کلی، برای بستههای فقط نوع یا معمولی، این کار ایمن است زیرا برای مدیریت انواع ناشناخته از تراکنشهای IPC باید کد قبلاً نوشته شده باشد.
کد پلتفرم اندروید android.hardware.graphics.common
بزرگترین نمونه از این نوع ارتقاء نسخه است.
استفاده از رابط های نسخه شده
روش های رابط
در زمان اجرا، هنگام تلاش برای فراخوانی روشهای جدید در سرور قدیمی، مشتریان جدید بسته به باطن، خطا یا استثنا دریافت میکنند.
-
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. اولی برای برنامه ها و دومی برای استفاده از پلتفرم است، -
rust
for Rust backend.
-
فرض کنید یک ماژول با نام 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،
-
foo- backend
که به آخرین نسخه پایدار اشاره می کند تبدیل بهfoo- V2 - backend
می شود -
foo-unstable- backend
، که به نسخه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 پایدار ایجاد نمی کند. از اندروید 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();
مثال با 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++ در اندروید ۱۳ و بالاتر:
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 پایدار تعریف شده است یا خیر. اگر تعریف نشده باشد، بسته باید تبدیل شود.
همه بستههای موجود در رابط خود را به بستههای پایدار تبدیل کنید (فایلهای رابط میتوانند بدون تغییر باقی بمانند). این کار را با بیان ساختار آنها به طور مستقیم در فایل های AIDL انجام دهید. برای استفاده از این انواع جدید، کلاس های مدیریت باید بازنویسی شوند. این را می توان قبل از ایجاد یک بسته
aidl_interface
(در زیر) انجام داد.یک بسته
aidl_interface
(همانطور که در بالا توضیح داده شد) ایجاد کنید که حاوی نام ماژول شما، وابستگی های آن و هر اطلاعات دیگری که نیاز دارید باشد. برای تثبیت آن (نه فقط ساختاری)، همچنین باید نسخهسازی شود. برای اطلاعات بیشتر، نسخهسازی رابطها را ببینید.