بهترین شیوههای ذکر شده در اینجا به عنوان راهنمایی برای توسعه مؤثر رابطهای AIDL و با توجه به انعطافپذیری رابط، به ویژه هنگامی که از AIDL برای تعریف یک API پایدار و سازگار با نسخههای قبلی استفاده میشود، عمل میکنند.
AIDL میتواند برای تعریف یک API مورد استفاده قرار گیرد، زمانی که برنامهها نیاز به ارتباط با یکدیگر در یک فرآیند پسزمینه یا ارتباط با سیستم دارند.
AIDL پایدار با @VintfStability برای رابطهای HAL استفاده میشود و به کلاینتها و سرورها اجازه میدهد تا بهطور مستقل بهروزرسانی شوند. این امر مستلزم سازگاری با نسخههای قبلی و دادههای ساختاریافته است.
برای اطلاعات بیشتر در مورد توسعه رابطهای برنامهنویسی در برنامهها با AIDL، به زبان تعریف رابط اندروید (AIDL) مراجعه کنید. برای مثالهایی از AIDL در عمل، به AIDL برای HALها و AIDL پایدار مراجعه کنید.
نسخهبندی
هر اسنپشات سازگار با نسخههای قبلی از یک API AIDL مربوط به یک نسخه است. برای گرفتن اسنپشات، دستور m <module-name>-freeze-api را اجرا کنید. هر زمان که یک کلاینت یا سرور از API منتشر میشود (به عنوان مثال، در یک قطار Mainline)، باید یک اسنپشات بگیرید و یک نسخه جدید ایجاد کنید. برای APIهای سیستم به فروشنده، این کار باید با نسخه سالانه پلتفرم انجام شود.
وقتی یک رابط فریز میشود (در دایرکتوری aidl_api نسخهبندیشده ذخیره میشود)، هرگز نباید تغییر کند. شما فقط میتوانید دایرکتوری current را ویرایش کنید. میتوانید با خیال راحت متدها را به انتهای یک رابط، فیلدها را به انتهای یک parcelable، enumratorها را به یک enum و اعضا را به یک union اضافه کنید.
کلاینتهایی که متدهای جدید را روی سرورهای قدیمیتر فراخوانی میکنند، خطای UNKNOWN_TRANSACTION دریافت میکنند که باید توسط کلاینت به درستی مدیریت شود.
برای جزئیات بیشتر و اطلاعات مربوط به نوع تغییراتی که مجاز هستند، به رابطهای نسخهبندی مراجعه کنید.
وابستگیها را بسازید
ماژولهای اندروید نمیتوانند به چندین نسخه مختلف از کتابخانههای تولید شده از یک aidl_interface وابسته باشند. نسخههای مختلف کتابخانهها، انواع یکسانی را در فضاهای نام یکسان تعریف میکنند. سیستم ساخت aidl اندروید این مشکل را شناسایی میکند و با هر یک از نمودارهای وابستگی که به نسخههای ناسازگار کتابخانهها ختم میشوند، خطا میدهد.
این امر میتواند بهروزرسانی یک نسخه از یک رابط مشترک را دشوار کند، زمانی که یک ماژول شامل وابستگیهای زیادی با وابستگیهای خاص خود باشد.
توسعهدهندگان میتوانند از aidl_interface_defaults برای اعلام وابستگیهای یک رابط مشترک به رابطهای دیگر استفاده کنند تا نیازی به بهروزرسانی مستقل همه آنها نباشد.
ما استفاده از ماژولهای *_defaults (مانند rust_defaults ، cc_defaults ، java_defaults ) را برای سازماندهی وابستگیها در کتابخانههای تولید شده توصیه میکنیم. داشتن یک پیشفرض برای latest نسخه رابطها و همچنین پیشفرضهایی برای نسخههای قبلی در صورت استفاده، رایج است.
توسعهدهندگان میتوانند از aidl_interface_defaults برای تعریف وابستگیهای یک رابط مشترک به رابطهای دیگر استفاده کنند تا نیازی به بهروزرسانی مستقل همه آنها نباشد.
دستورالعملهای طراحی API
عمومی
۱. همه چیز را مستند کنید
- هر متد را از نظر معناشناسی، آرگومانها، استفاده از استثنائات داخلی، استثنائات خاص سرویس و مقدار برگشتی مستندسازی کنید.
- هر رابط را از نظر معناشناسی مستند کنید.
- معنای معنایی enumها و ثابتها را مستند کنید.
- هر آنچه را که ممکن است برای یک مجری مبهم باشد، مستند کنید.
- در صورت لزوم، مثالهایی ارائه دهید.
2. پوشش
برای نوعها از upper camel casing و برای متدها، فیلدها و آرگومانها از lower camel casing استفاده کنید. برای مثال، برای یک نوع parcelable MyParcelable و برای یک آرگومان anArgument استفاده کنید. برای کلمات اختصاری، از حروف اختصاری a ( NFC -> Nfc ) استفاده کنید.
[-Wconst-name] مقادیر و ثابتهای Enum باید ENUM_VALUE و CONSTANT_NAME باشند.
۳. از الزام به دانش جهانی خودداری کنید
APIها نباید فرض کنند که توسعهدهندگان دانش جهانی از کل کدبیس یا تخصص در یک حوزه خاص دارند. هنگام کار با شناسههای مختص یک حوزه (مانند نام دستگاه، شناسه یا شناسههای کاربری):
- اگر دانستن این شناسهها برای هر دو طرف رابط کاربری مهم است، صریح باشید و منبع و فرمت آنها را مستند کنید.
- روش دیگر، استفاده از شناسههای مختص رابط (مانند اشیاء binder یا توکنهای سفارشی) است و یک طرف باید نگاشت به مقادیر زیرین را مدیریت کند. این کار باعث کاهش تصادمها میشود و از نیاز کاربران به درک جزئیات پیادهسازی خارج از محدودهی خود جلوگیری میکند.
۴. تمام دادهها ساختار یافته و سازگار با نسخههای قبلی هستند
دادههای بدون ساختار مانند string ، byte[] و حافظه مشترک باید قالب پایداری برای محتوای خود داشته باشند، یا در یک طرف رابط کاربری مبهم باشند.
برای مثال، یک آرگومان رشتهای که به عنوان پیام خطا برای یک نتیجه استفاده میشود، میتواند برای اشکالزدایی دریافت و ثبت شود، اما نباید تجزیه و تفسیر شود زیرا قالب و محتویات آن ممکن است با نسخههای قبلی سازگار نباشد. اگر طرف دیگر رابط کاربری نیاز به دانستن خطا در زمان اجرا داشته باشد، از یک enum، ثابت یا ServiceSpecificException استفاده کنید.
به طور مشابه، اشیاء را در byte[] یا حافظه مشترک سریالایز نکنید، مگر اینکه پایدار و سازگار با نسخههای قبلی باشند. در برخی موارد، میتوانید از حاشیهنویسی @FixedSize برای اشتراکگذاری parcelables و unions در حافظه مشترک و صفهای پیام سریع استفاده کنید.
رابطها
۱. نامگذاری
[-Winterface-name] نام رابط باید با I like IFoo شروع شود.
۲. از رابطهای کاربری بزرگ با «اشیاء» مبتنی بر شناسه (id-based) اجتناب کنید
وقتی فراخوانیهای زیادی مربوط به یک API خاص وجود دارد، زیررابطها را ترجیح دهید. این مزایای زیر را ارائه میدهد:
- درک کد کلاینت یا سرور را آسانتر میکند.
- چرخه حیات اشیاء را سادهتر میکند
- از غیرقابل جعل بودن صحافیها بهره میبرد.
توصیه نمیشود: یک رابط کاربری بزرگ و واحد با اشیاء مبتنی بر شناسه
interface IManager {
int getFooId();
void beginFoo(int id); // clients in other processes can guess an ID
void opFoo(int id);
void recycleFoo(int id); // ownership not handled by type
}
توصیه شده: رابطهای کاربری مجزا
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
۳. روشهای یکطرفه را با روشهای دوطرفه قاطی نکنید
[-Wmixed-oneway] متدهای یکطرفه را با متدهای غیر یکطرفه اشتباه نگیرید، زیرا درک مدل نخکشی را برای کلاینتها و سرورها پیچیده میکند. بهطور خاص، هنگام خواندن کد کلاینت یک رابط خاص، باید برای هر متد جستجو کنید که آیا آن متد مسدود میشود یا خیر.
۴. از برگرداندن کدهای وضعیت خودداری کنید
متدها باید از کدهای وضعیت به عنوان مقادیر بازگشتی اجتناب کنند، زیرا همه متدهای AIDL دارای یک کد بازگشتی وضعیت ضمنی هستند. به ServiceSpecificException یا EX_SERVICE_SPECIFIC مراجعه کنید. طبق قرارداد، این مقادیر به عنوان ثابت در رابط AIDL تعریف میشوند. اگر یک تأخیر سفارشی یا داده خطای منحصر به فرد در کنار یک خطا مورد نیاز باشد، تنها زمانی است که یک شیء پاسخ سفارشی باید یک خطا را نشان دهد. برای اطلاعات دقیقتر، به مدیریت خطا مراجعه کنید.
۵. آرایهها به عنوان پارامترهای خروجی مضر در نظر گرفته میشوند
[-Wout-array] متدهایی که پارامترهای خروجی آرایه دارند، مانند void foo(out String[] ret) معمولاً نامناسب هستند زیرا اندازه آرایه خروجی باید توسط کلاینت در جاوا اعلام و تخصیص داده شود، و بنابراین اندازه خروجی آرایه نمیتواند توسط سرور انتخاب شود. این رفتار نامطلوب به دلیل نحوه عملکرد آرایهها در جاوا اتفاق میافتد (آنها نمیتوانند دوباره تخصیص داده شوند). در عوض، APIهایی مانند String[] foo() را ترجیح میدهند.
۶. از پارامترهای بدون پارامتر اجتناب کنید
[-Winout-parameter] این میتواند کلاینتها را گیج کند زیرا حتی پارامترهای in نیز شبیه پارامترهای out هستند.
۷. از پارامترهای غیر آرایهای out و inout @nullable اجتناب کنید.
[-Wout-nullable] از آنجایی که بکاند جاوا حاشیهنویسی @nullable را مدیریت نمیکند، در حالی که سایر بکاندها این کار را انجام میدهند، out/inout @nullable T ممکن است منجر به رفتار متناقض در بکاندها شود. برای مثال، بکاندهای غیر جاوا میتوانند پارامتر out @nullable را روی null تنظیم کنند (در C++، آن را به صورت std::nullopt تنظیم میکنند) اما کلاینت جاوا نمیتواند آن را به عنوان null بخواند.
۸. از درخواستها و پاسخهای منحصر به فرد استفاده کنید
تمام پارامترهای لازم را در یک parcelable ورودی واحد گروهبندی کنید. به جای ارسال مقادیر اولیه، parcelableهای درخواست و پاسخ اختصاصی برای هر متد رابط ایجاد کنید (برای مثال، به جای ارسال متغیرهای جداگانه ComputeResponse compute(in ComputeRequest request) استفاده کنید). این امر امکان اضافه کردن آرگومانهای جدید را بعداً بدون تغییر امضای تابع فراهم میکند. این الگو زمانی که انتظار میرود پارامترهای بیشتری در آینده اضافه شوند یا اگر متدی از قبل بیش از چهار پارامتر داشته باشد، اکیداً توصیه میشود.
روشهایی که به ورودیها یا خروجیهای اضافی نیاز ندارند، از این پیشنهاد بهرهمند نخواهند شد. تفکر صریح در مورد هر مورد و انعطافپذیری در برابر تغییرات آینده میتواند منجر به کاهش روشهای منسوخشده و پیچیدگی کمتر برای کد سازگار با نسخههای قبلی شود.
اگر متدی با استفاده از این الگو ایجاد نشده باشد، میتوانید با ایجاد یک متد جدید با قابلیت parcelable درخواست و پاسخ و منسوخ کردن متد قدیمی، به این الگو تغییر دهید. برای مثال:
void foo(int a, int b, int c); // original version, but deprecated in favor of the next version
void fooV2(in MyArg arg); // new version having int a, b, c, and d.
بستههای قابل ساخت
۱. چه زمانی استفاده شود
از بستههای قابل ارسال ساختاریافته استفاده کنید که در آنها چندین نوع داده برای ارسال دارید.
یا وقتی یک نوع داده واحد دارید اما انتظار دارید که در آینده نیاز به گسترش آن داشته باشید. برای مثال، String username استفاده نکنید. از یک parcelable قابل گسترش مانند زیر استفاده کنید:
parcelable User {
String username;
}
به طوری که در آینده بتوانید آن را به شرح زیر گسترش دهید:
parcelable User {
String username;
int id;
}
۲. پیشفرضها را صریحاً ارائه دهید
[-Weexplicit-default, -Wenum-explicit-default] پیشفرضهای صریحی برای فیلدها ارائه میدهد. وقتی فیلدهای جدیدی به یک parcelable اضافه میشوند، کلاینتها و سرورهای قدیمی آنها را حذف میکنند، اما مقادیر پیشفرض به طور خودکار برای کلاینتها و سرورهای جدید پر میشوند.
۳. از ParcelableHolder برای افزونههای فروشنده استفاده کنید
اگر یک parcelable AOSP تعریف میکنید که پیادهسازیکنندگان دستگاه باید آن را توسعه دهند، یک نمونه از ParcelableHolder را در شیء خود جاسازی کنید. این به عنوان یک نقطه توسعه بدون ایجاد تداخل ادغام عمل میکند. این شبیه به افزونههای رابط پیوست شده است، اما به پیادهسازیکنندگان اجازه میدهد parcelable اختصاصی خود را در کنار parcelable موجود بدون ایجاد رابط و انواع خاص خود قرار دهند.
۴. ساختارهای داده
- از آرایهها یا
Listparcelableها برای نمایش نقشهها استفاده کنید، زیرا AIDL به طور پیشفرض از انواعMapکه به طور ایمن در تمام backendهای بومی ترجمه میشوند (مثلاًFeatureToScoreEntry[]) پشتیبانی نمیکند. - برای فیلدهای تکراری، به جای آرایههای مقادیر اولیه، از آرایههایی از اشیاء
parcelableاستفاده کنید تا در آینده از نیاز به آرایههای موازی جلوگیری شود. - به جای رشتههای سریالی یا JSON روی IPC، از اشیاء
parcelableبا تایپ قوی استفاده کنید. - برای حالتها به جای بولیها از enums استفاده کنید تا امکان بسط در آینده فراهم شود. برای bitmaskها، به جای انواع
enumconst intاستفاده کنید تا از تبدیلهای دست و پا گیر در برخی از backendها جلوگیری شود.
بستههای غیرساختارمند
۱. چه زمانی استفاده شود
parcelableهای غیرساختاریافته در جاوا با @JavaOnlyStableParcelable و در backend NDK با @NdkOnlyStableParcelable در دسترس هستند. معمولاً، اینها parcelableهای قدیمی و موجود هستند که نمیتوانند ساختاریافته شوند.
ثابتها و enumها
۱. فیلدهای بیتی باید از فیلدهای ثابت استفاده کنند
فیلدهای بیتی باید از فیلدهای ثابت استفاده کنند (برای مثال، const int FOO = 3; در یک رابط).
۲. Enumها باید مجموعههای بسته باشند.
Enumها باید مجموعههای بسته باشند. توجه: فقط مالک رابط میتواند عناصر enum را اضافه کند. اگر فروشندگان یا OEMها نیاز به گسترش این فیلدها داشته باشند، به یک مکانیسم جایگزین نیاز است. در صورت امکان، عملکرد فروشندهی بالادستی باید ترجیح داده شود. با این حال، در برخی موارد، مقادیر سفارشی فروشنده ممکن است مجاز باشند (اگرچه، فروشندگان باید مکانیسمی برای نسخهبندی این مورد داشته باشند، شاید خود AIDL، آنها نباید بتوانند با یکدیگر تداخل داشته باشند و این مقادیر نباید در معرض برنامههای شخص ثالث قرار گیرند).
۳. از مقادیری مانند "NUM_ELEMENTS" اجتناب کنید
از آنجایی که enumها نسخهبندی میشوند، باید از مقادیری که نشان میدهند چند مقدار وجود دارد، اجتناب شود. در ++C، این مشکل را میتوان با enum_range<> حل کرد. برای Rust، enum_values() استفاده کنید. در جاوا، هنوز هیچ راه حلی وجود ندارد.
توصیه نمیشود: استفاده از مقادیر شمارهگذاری شده
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
۴. از پیشوندها و پسوندهای اضافی اجتناب کنید
[-Wredundant-name] از پیشوندها و پسوندهای تکراری یا اضافی در ثابتها و شمارندهها خودداری کنید.
توصیه نمیشود: استفاده از پیشوند تکراری
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
توصیه میشود: نامگذاری مستقیم enum
enum MyStatus {
GOOD,
BAD
}
توصیفگر فایل
[-Wfile-descriptor] استفاده از FileDescriptor به عنوان یک آرگومان یا مقدار برگشتی یک متد رابط AIDL اکیداً توصیه نمیشود. به خصوص، هنگامی که AIDL در جاوا پیادهسازی شده باشد، این امر ممکن است باعث نشت توصیفگر فایل شود، مگر اینکه با دقت مدیریت شود. اساساً، اگر یک FileDescriptor بپذیرید، باید وقتی دیگر استفاده نمیشود، آن را به صورت دستی ببندید.
برای backend های native، شما در امان هستید زیرا FileDescriptor به unique_fd نگاشت میشود که به صورت خودکار قابل بستن است. اما صرف نظر از زبان backend که استفاده میکنید، عاقلانه است که اصلاً FileDescriptor استفاده نکنید زیرا این کار آزادی شما را برای تغییر زبان backend در آینده محدود میکند.
در عوض، از ParcelFileDescriptor استفاده کنید که قابلیت بسته شدن خودکار دارد.
واحدهای متغیر
مطمئن شوید که واحدهای متغیر در نام گنجانده شدهاند تا واحدهای آنها به خوبی تعریف شده و بدون نیاز به مراجعه به مستندات قابل فهم باشند.
مثالها
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
مهرهای زمانی باید مرجع خود را نشان دهند
مهرهای زمانی (در واقع، همه واحدها!) باید به وضوح واحدها و نقاط مرجع خود را نشان دهند.
مثالها
/**
* Time since device boot in milliseconds
*/
long timestampMs;
/**
* UTC time received from the NTP server in units of milliseconds
* since January 1, 1970
*/
long utcTimeMs;
عملیات همزمانی و ناهمزمانی
برای جلوگیری از مسدود شدن، عملیات طولانی مدت را با یک رابط ناهمزمان ( oneway ) مدیریت کنید.
اگر یک سرویس به کلاینتهای خود اعتماد نداشته باشد، هرگونه فراخوانی مجددی که از کلاینتها دریافت میکند باید رابطهای oneway باشند. این کار مانع از آن میشود که کلاینتها بتوانند سرویس را به طور نامحدود مسدود کنند.
API های ناهمزمان را که شامل یک فراخوانی رو به جلو، آرگومانهای ورودی و یک رابط فراخوانی برگشتی برای دریافت نتایج هستند، ساختاردهی کنید. برای توصیههایی برای آرگومانها، به «استفاده از درخواستها و پاسخهای منحصر به فرد» مراجعه کنید.