تُعدّ أفضل الممارسات الموضّحة هنا دليلاً لتطوير واجهات AIDL بفعالية مع الانتباه إلى مرونة الواجهة، لا سيّما عند استخدام AIDL لتحديد واجهة برمجة تطبيقات أو التفاعل مع مساحات عرض واجهات برمجة التطبيقات.
يمكن استخدام AIDL لتحديد واجهة برمجة تطبيقات عندما تحتاج التطبيقات إلى التفاعل مع بعضها في عملية في الخلفية أو تحتاج إلى التفاعل مع النظام. لمزيد من المعلومات حول تطوير واجهات برمجة التطبيقات في التطبيقات التي تستخدم لغة تعريف واجهة Android (AIDL)، يُرجى الاطّلاع على لغة تعريف واجهة Android (AIDL). للحصول على أمثلة على استخدام AIDL، يُرجى الاطّلاع على AIDL لأجل HALs وAIDL الثابت.
تحديد الإصدار
تتطابق كل لقطة متوافقة مع الإصدارات القديمة لواجهة برمجة التطبيقات AIDL مع إصدار.
لالتقاط لقطة، شغِّل m <module-name>-freeze-api
. عند إصدار إصدار جديد من العميل أو
الخادم لواجهة برمجة التطبيقات (على سبيل المثال، في سلسلة Mainline)، عليك
التقاط لقطة شاشة وإنشاء إصدار جديد. بالنسبة إلى واجهات برمجة التطبيقات التي تربط النظام بالمورّد، من المفترض أن يتم تعديلها
مع مراجعة المنصة السنوية.
لمزيد من التفاصيل والمعلومات حول نوع التغييرات المسموح بها، يُرجى الاطّلاع على واجهات تحديد الإصدار.
إرشادات تصميم واجهة برمجة التطبيقات
بنود عامة
1. توثيق كل التفاصيل
- سجِّل كل طريقة لدلالتها ووسيطاتها واستخدامها للاستثناءات المضمّنة والاستثناءات الخاصة بالخدمة والقيمة المعروضة.
- توثيق كل واجهة لدلالتها
- توثيق المعنى الدلالي لقوائم التعداد والثوابت
- سجِّل أي معلومات قد تكون غير واضحة لأحد مُنفّذِي الحملة.
- يُرجى تقديم أمثلة عند الضرورة.
2- الغلاف
استخدِم الأحرف اللاتينية الكبيرة لكتابة الأنواع والأحرف اللاتينية الصغيرة لكتابة الطرق والحقول
والوسيطات. على سبيل المثال، MyParcelable
لنوع قابل للتقسيم وanArgument
لوسيطة. بالنسبة إلى الاختصارات، يمكنك اعتبار الاختصار كلمة (NFC
-> Nfc
).
[-Wconst-name] يجب أن تكون قيم Enum والثوابت ENUM_VALUE
و
CONSTANT_NAME
واجهات
1. التسمية
[-Winterface-name] يجب أن يبدأ اسم الواجهة بI
مثل IFoo
.
2- تجنَّب استخدام واجهة كبيرة تتضمّن "عناصر" مستندة إلى المعرّف.
استخدِم الواجهات الفرعية بشكل مفضّل عندما يكون هناك العديد من طلبات البيانات ذات الصلة بواجهة برمجة تطبيقات معيّنة. ويمنحك ذلك المزايا التالية:
- تسهيل فهم رمز العميل أو الخادم
- تبسيط دورة حياة العناصر
- الاستفادة من عدم إمكانية تزوير المجلدات
إجراء غير مقترَح: واجهة واحدة كبيرة تتضمّن عناصر مستندة إلى المعرّفات
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();
}
3- لا تخلِط بين الطرق ذات الاتجاه الواحد والطرق ذات الاتجاهَين.
[-Wmixed-oneway] لا تَمزِج بين الطرق ذات الاتجاه الواحد والطرق غير ذات الاتجاه الواحد، لأنّ ذلك يجعل فهم نموذج معالجة المهام المتعدّدة أمرًا معقّدًا على العملاء والخوادم. على وجه التحديد، عند قراءة رمز العميل لواجهة معيّنة، عليك البحث عن كل طريقة لمعرفة ما إذا كانت ستؤدي إلى الحظر أم لا.
4- تجنَّب عرض رموز الحالة.
يجب أن تتجنّب الطرق استخدام رموز الحالة كقيم معروضة، لأنّ جميع طرق AIDL تحتوي على
رمز عرض حالة ضمني. يُرجى الاطّلاع على ServiceSpecificException
أو
EX_SERVICE_SPECIFIC
. وفقًا للعرف، يتم تعريف هذه القيم على أنّها ثابتة في
واجهة AIDL. تتوفّر معلومات أكثر تفصيلاً في
قسم معالجة الأخطاء في AIDL
backends.
5. استخدام المصفوفات كمَعلمات للإخراج يُعدّ ضارًا
[-Wout-array] إنّ الطرق التي تحتوي على مَعلمات إخراج صفيف، مثل
void foo(out String[] ret)
، تكون عادةً سيئة لأنّ حجم صفيف الإخراج يجب
أن يُعلن عنه ويخصّصه العميل في Java، وبالتالي لا يمكن للخادم اختيار حجم صفيف
الإخراج. يحدث هذا السلوك غير المرغوب فيه بسبب
طريقة عمل الصفائف في Java (لا يمكن إعادة تخصيصها). بدلاً من ذلك، يُفضَّل استخدام واجهات برمجة التطبيقات
مثل String[] foo()
.
6. تجنُّب مَعلمات الإدخال والإخراج
[-Winout-parameter] قد يؤدي ذلك إلى إرباك العملاء لأنّ مَعلمات in
تبدو
مثل مَعلمات out
.
7. تجنَّب استخدام مَعلمات out وinout غير المصفوفة التي تحمل العلامة @nullable.
[-Wout-nullable] بما أنّ الخلفية في Java لا تتعامل مع التعليق التوضيحي @nullable
بينما تتعامل الخلفيات الأخرى معه، قد يؤدي out/inout @nullable T
إلى سلوك غير متّسق
في الخلفيات. على سبيل المثال، يمكن لأنظمة التشغيل غير المستندة إلى Java ضبط مَعلمة @nullable
out على القيمة null (في C++، يتم ضبطها على std::nullopt
)، ولكن لا يمكن لبرنامج Java العميل قراءة القيمة null.
العناصر المنظَّمة التي يمكن تقسيمها
1. حالات الاستخدام
استخدِم العناصر القابلة للتقسيم المنظَّمة عندما يكون لديك أنواع بيانات متعددة لإرسالها.
أو عندما يكون لديك نوع بيانات واحد ولكنك تتوقّع أن تحتاج
إلى توسيعه في المستقبل. على سبيل المثال، لا تستخدِم String username
. استخدِم ملفًا قابلاً للتوسيع، مثل ما يلي:
parcelable User {
String username;
}
ويمكنك تمديده في المستقبل باتّباع الخطوات التالية:
parcelable User {
String username;
int id;
}
2- تقديم الإعدادات التلقائية بشكل صريح
[-Wexplicit-default, -Wenum-explicit-default] توفير إعدادات تلقائية صريحة للحقول
العناصر غير القابلة للتقسيم
1. حالات الاستخدام
تتوفّر عناصر parcelable غير المنظَّمة في Java باستخدام
@JavaOnlyStableParcelable
وفي الخلفية في NDK باستخدام
@NdkOnlyStableParcelable
. وعادةً ما تكون هذه العناصر عبارة عن عناصر قديمة وموجودة
لا يمكن تنظيمها.
الثوابت وعمليات التعداد
1. يجب أن تستخدم حقول الوحدات بتية حقولًا ثابتة.
يجب أن تستخدم حقول الوحدات بت حقولًا ثابتة (على سبيل المثال، const int FOO = 3;
في
واجهة).
2- يجب أن تكون النماذج المحدَّدة مسبقًا مجموعات مغلقة.
يجب أن تكون النماذج المحدَّدة مسبقًا مجموعات مغلقة. ملاحظة: يمكن فقط لصاحب الواجهة إضافة عناصر enum. إذا احتاج المورّدون أو المصنّعون الأصليون إلى توسيع هذه الحقول، يجب استخدام بديل للآلية. يجب تفضيل نقل وظائف المورّد إلى أعلى السلسلة متى أمكن. ومع ذلك، في بعض الحالات، قد يُسمح باستخدام قيم المورّدين المخصّصة (على الرغم من أنّه يجب أن يكون لدى المورّدين آلية متوفّرة لإصدار هذه القيم، ربما AIDL نفسها، ويجب ألا يكون بإمكانها الاصطدام ببعضها، ويجب عدم عرض هذه القيم للتطبيقات التابعة لجهات خارجية).
3- تجنَّب استخدام قيم مثل "NUM_ELEMENTS".
بما أنّ القوائم المحدَّدة لها إصدارات، يجب تجنُّب القيم التي تشير إلى عدد القيم المتوفّرة. في C++، يمكن حلّ هذه المشكلة باستخدام enum_range<>
. بالنسبة إلى
Rust، استخدِم enum_values()
. في Java، ما مِن حلّ حتى الآن.
إجراء غير مقترَح: استخدام قيم مرقّمة
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4- تجنَّب استخدام البادئات واللاحقات المكرّرة.
[-Wredundant-name] تجنَّب استخدام البادئات واللاحقات المكرّرة أو المتكرّرة في الثوابت والمُعدِّدات.
غير مستحسن: استخدام بادئة مكرّرة
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
إجراء مقترَح: تسمية القائمة المحدودة القيم مباشرةً
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] يُنصح بشدة بعدم استخدام FileDescriptor
كوسيطة أو قيمة القيمة المعروضة
لطريقة واجهة AIDL. على وجه الخصوص، عند تنفيذ IDE في 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;