رهنمودهای AIDL API

روال‌های مطلوب شرح‌داده‌شده در اینجا به‌عنوان راهنمایی برای توسعه مؤثر میاناهای AIDL و با توجه به انعطاف‌پذیری میانجی عمل می‌کنند، به‌ویژه زمانی که از AIDL برای تعریف یک API پایدار و سازگار با نسخه‌های قدیمی استفاده می‌شود.

از AIDL می‌توان برای تعریف کردن میانای برنامه‌سازی کاربردی استفاده کرد وقتی برنامه‌ها نیاز دارند در فرایندی پس‌زمینه‌ای با یکدیگر ارتباط برقرار کنند یا نیاز دارند با سیستم ارتباط برقرار کنند.

از AIDL پایدار با @VintfStability برای میاناهای HAL استفاده می‌شود و به کارخواه‌ها و سرورها امکان می‌دهد به‌طور مستقل به‌روز شوند. این کار به سازگاری عقب‌گرد و داده‌های ساختاریافته نیاز دارد.

برای کسب اطلاعات بیشتر درباره توسعه واسط‌های برنامه‌نویسی در برنامه‌ها با AIDL، به زبان تعریف واسط Android (AIDL) مراجعه کنید. برای نمونه‌های عملی AIDL، AIDL برای HAL و AIDL پایدار را ببینید.

نسخه‌بندی

هر عکس آنی سازگار با نسخه قدیمی از میانای برنامه‌سازی کاربردی AIDL با یک نسخه مطابقت دارد. برای گرفتن عکس آنی، m <module-name>-freeze-api را اجرا کنید. هرگاه کارخواه یا کارساز میانای برنامه‌سازی کاربردی منتشر می‌شود (برای مثال، در قطار Mainline)، باید نمایی فوری بگیرید و نسخه جدیدی بسازید. برای میاناهای برنامه‌سازی کاربردی سیستم به فروشنده، این کار باید با بازبینی سالانه پلاتفرم انجام شود.

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

کارخواهانی که روش‌های جدید را در سرورهای قدیمی‌تر فرا می‌خوانند خطای UNKNOWN_TRANSACTION دریافت می‌کنند که کارخواه باید آن را به‌طور مناسب مدیریت کند.

برای جزئیات بیشتر و اطلاعات درباره نوع تغییراتی که مجاز است، به نسخه‌بندی میان‌ها مراجعه کنید.

ساختن وابستگی‌ها

واحدهای Android نمی‌توانند به نسخه‌های مختلف و متعددی از کتابخانه‌های تولیدشده از aidl_interface وابسته باشند. نسخه‌های مختلف کتابخانه‌ها انواع یکسانی را در فضای نام یکسان تعریف می‌کنند. سیستم ساخت aidl Android این مشکل را شناسایی می‌کند و برای هریک از نمودارهای وابستگی که به نسخه‌های ناسازگار کتابخانه‌ها ختم می‌شوند، خطا ایجاد می‌کند.

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

توسعه‌دهندگان می‌توانند از aidl_interface_defaults برای اعلام وابستگی‌های میانای هم‌رسانی‌شده به میاناهای دیگر استفاده کنند تا نیازی نباشد همه آن‌ها به‌طور مستقل به‌روز شوند.

توصیه می‌کنیم از ماژول‌های *_defaults (مثل rust_defaults، cc_defaults، java_defaults) برای سازمان‌دهی وابستگی‌ها در کتابخانه‌های تولیدشده استفاده کنید. معمولاً برای نسخه latest میاناهای کاربر و همچنین نسخه‌های قبلی (اگر هنوز استفاده می‌شوند) پیش‌فرضی وجود دارد.

توسعه‌دهندگان می‌توانند از aidl_interface_defaults برای اعلام وابستگی‌های میانای مشترک به میاناهای دیگر استفاده کنند تا همه آن‌ها به‌طور مستقل به‌روزرسانی نشوند.

رهنمودهای طراحی میانای برنامه‌سازی کاربردی

عمومی

۱. همه چیز را مستند کنید

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

۲. قاب

برای انواع از حروف بزرگ شتری و برای روش‌ها، فیلدها، و آرگومان‌ها از حروف کوچک شتری استفاده کنید. برای مثال، MyParcelable برای نوع قابل‌بسته‌بندی و anArgument برای شناسه. برای سرواژه‌ها، سرواژه را یک کلمه درنظر بگیرید (NFC -> Nfc).

[-Wconst-name] مقادیر Enum و ثابت‌ها باید ENUM_VALUE و CONSTANT_NAME باشند

۳. از الزام به داشتن دانش جهانی اجتناب کنید

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

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

۴. همه داده‌ها ساختاریافته و با نسخه قدیمی سازگار است

داده‌های بدون ساختار مثل string، byte[]، و حافظه هم‌رسانی‌شده باید قالب پایداری برای محتوایشان داشته باشند یا برای یک طرف رابط مبهم باشند.

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

به‌طور مشابه، اشیاء را در byte[] یا حافظه مشترک سریالی نکنید، مگراینکه پایدار و سازگار با نسخه قدیمی باشند. در برخی موارد، می‌توانید از حاشیه‌نویسی @FixedSize برای هم‌رسانی parcelable و union در حافظه مشترک و «صف‌های پیام سریع» استفاده کنید.

میانای کاربر

۱. نام‌گذاری

[-Winterface-name] نام واسط باید با I مثل IFoo شروع شود.

۲. از رابط بزرگ با «اشیا» مبتنی بر شناسه اجتناب کنید

وقتی تماس‌های زیادی مربوط به یک میانای برنامه‌سازی کاربردی خاص وجود دارد، زیررابط‌ها را ترجیح دهید. این مزایای زیر را ارائه می‌دهد:

  • درک کد کارخواه یا سرور را آسان‌تر می‌کند
  • چرخه حیات اشیاء را ساده‌تر می‌کند
  • از غیرقابل جعل بودن پوشه‌ها بهره می‌برد.

توصیه نمی‌شود: یک میانای بزرگ واحد با اشیای مبتنی بر شناسه

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) معمولاً بد هستند زیرا اندازه آرایه برونداد باید توسط مشتری در جاوا تعریف و تخصیص داده شود، و بنابراین اندازه برونداد آرایه نمی‌تواند توسط سرور انتخاب شود. این رفتار نامطلوب به‌دلیل نحوه عملکرد آرایه‌ها در Java اتفاق می‌افتد (نمی‌توان آن‌ها را دوباره تخصیص داد). درعوض، میاناهای برنامه‌سازی کاربردی مثل String[] foo() را ترجیح دهید.

۶. از پارامترهای ورودی/خروجی اجتناب کنید

[-Winout-parameter] This can confuse clients because even in parameters look like out parameters.

‫۷. از پارامترهای غیرآرایه‌ای out و inout @nullable اجتناب کنید

[-Wout-nullable] ازآنجایی‌که زیرینه Java حاشیه @nullable را مدیریت نمی‌کند درحالی‌که زیرینه‌های دیگر آن را مدیریت می‌کنند، out/inout @nullable T ممکن است منجر به رفتار ناسازگار در زیرینه‌ها شود. برای مثال، زیرینه‌های غیرجاوا می‌توانند پارامتر برون‌برد @nullable را روی تهی تنظیم کنند (در ++C، آن را به‌عنوان std::nullopt تنظیم کنند) اما کارخواه جاوا نمی‌تواند آن را به‌عنوان تهی بخواند.

‫۸. از درخواست‌ها و پاسخ‌های منحصربه‌فرد استفاده کنید

همه پارامترهای لازم را در یک ورودی parcelable گروه‌بندی کنید. برای هر روش میانای به‌جای ارسال داده‌های اولیه، Parcelable درخواست و پاسخ اختصاصی ایجاد کنید (برای مثال، به‌جای ارسال متغیرهای جداگانه، از ComputeResponse compute(in ComputeRequest request) استفاده کنید). این کار امکان می‌دهد تا آرگومان‌های جدید بعداً بدون تغییر امضای تابع اضافه شوند. این الگو زمانی به‌شدت پیشنهاد می‌شود که انتظار می‌رود پارامترهای بیشتری در آینده اضافه شود، یا اگر روشی ازقبل بیش‌از چهار پارامتر داشته باشد.

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

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

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.

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

۱. زمان استفاده

اگر چند نوع داده برای ارسال دارید، از Parcelable ساختاریافته استفاده کنید.

یا زمانی که یک نوع داده دارید اما انتظار دارید که در آینده نیاز به گسترش آن داشته باشید. برای مثال، از String username استفاده نکنید. از یک parcelable توسعه‌پذیر، مانند موارد زیر، استفاده کنید:

parcelable User {
    String username;
}

به‌طوری‌که در آینده بتوانید آن را به‌صورت زیر گسترش دهید:

parcelable User {
    String username;
    int id;
}

۲. ارائه پیش‌فرض‌ها به‌طور صریح

[-Wexplicit-default, -Wenum-explicit-default] پیش‌فرض‌های صریح برای فیلدها ارائه کنید. وقتی فیلدهای جدیدی به parcelable اضافه می‌شود، کارخواهان و سرورهای قدیمی آن‌ها را رها می‌کنند، اما مقادیر پیش‌فرض به‌طور خودکار برای کارخواهان و سرورهای جدید پر می‌شوند.

۳. استفاده از ParcelableHolder برای افزونه‌های فروشنده

اگر parcelable AOSP را تعریف می‌کنید که پیاده‌سازان دستگاه باید آن را گسترش دهند، نمونه‌ای از ParcelableHolder را در شیء خود جاسازی کنید. این کار به‌عنوان یک نقطه توسعه بدون ایجاد تعارض ادغام عمل می‌کند. این شبیه به افزونه‌های رابط پیوست‌شده است اما به پیاده‌سازان اجازه می‌دهد parcelable اختصاصی خود را در کنار parcelable موجود بدون ایجاد رابط و انواع خودشان اضافه کنند.

۴. ساختارهای داده

  • از آرایه‌ها یا List بسته‌های قابل‌انتقال برای نمایش نقشه‌ها استفاده کنید، زیرا AIDL به‌طور بومی از انواع Map که به‌طور ایمن در همه زیرینه‌های بومی ترجمه می‌شوند پشتیبانی نمی‌کند (برای مثال، FeatureToScoreEntry[]).
  • برای فیلدهای تکراری به‌جای آرایه‌های نوع‌های اولیه، از آرایه‌های parcelable شیء استفاده کنید تا در آینده نیازی به آرایه‌های موازی نباشد.
  • به‌جای رشته‌های سریال‌سازی‌شده یا JSON از اشیای parcelable با نوع قوی در IPC استفاده کنید.
  • برای وضعیت‌ها به‌جای مقدار بولی از شمارش استفاده کنید تا امکان گسترش در آینده فراهم شود. برای ماسک‌های بیتی، به‌جای انواع enum از const int استفاده کنید تا از تبدیل نوع دست‌وپاگیر در برخی‌از زیرینه‌ها جلوگیری شود.

بسته‌های غیرساختاریافته

۱. زمان استفاده

‫Parcelableهای غیرساختاریافته در Java با @JavaOnlyStableParcelable و در زیرینه NDK با @NdkOnlyStableParcelable دردسترس است. معمولاً این‌ها بسته‌های قدیمی و موجودی هستند که نمی‌توان آن‌ها را ساختاردهی کرد.

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

۱. فیلدهای بیت باید از فیلدهای ثابت استفاده کنند

فیلدهای بیتی باید از فیلدهای ثابت استفاده کنند (برای مثال، const int FOO = 3; در یک میان‌گیری).

۲. تعداد شمارش باید مجموعه بسته باشد.

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

۳. از مقادیر مثل «NUM_ELEMENTS» اجتناب کنید

ازآنجایی‌که شمارش‌ها دارای نسخه هستند، باید از مقادیری که نشان می‌دهند چند مقدار وجود دارد اجتناب شود. در C++‎، این مشکل را می‌توان با enum_range<> برطرف کرد. برای Rust، از enum_values() استفاده کنید. در Java، هنوز راه حلی وجود ندارد.

توصیه نمی‌شود: استفاده از مقادیر شماره‌دار

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

۴. از پیشوندها و پسوندهای زائد اجتناب کنید

[-Wredundant-name] Avoid redundant or repetitive prefixes and suffixes in constants and enumerators.

توصیه نمی‌شود: استفاده از پیشوند زائد

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

توصیه‌شده: نام‌گذاری مستقیم شمارش

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] استفاده از FileDescriptor به‌عنوان آرگومان یا مقدار برگشتی روش میانای AIDL به‌شدت منع می‌شود. به‌ویژه، وقتی AIDL در Java پیاده‌سازی می‌شود، این امر ممکن است باعث نشت توصیف‌گر فایل شود، مگراینکه با دقت مدیریت شود. اساساً، اگر FileDescriptor را بپذیرید، باید وقتی دیگر از آن استفاده نمی‌شود آن را به‌صورت دستی ببندید.

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

درعوض، از 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 باشد. این کار مانع از آن می‌شود که مشتریان بتوانند سرویس را برای همیشه مسدود کنند.

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