تُستخدَم أفضل الممارسات الموضّحة هنا كدليل لتطوير واجهات AIDL بفعالية مع الاهتمام بمرونة الواجهة، خاصةً عند استخدام AIDL لتحديد واجهة برمجة تطبيقات أو التفاعل مع مساحات واجهة برمجة التطبيقات.
يمكن استخدام AIDL لتحديد واجهة برمجة تطبيقات عندما تحتاج التطبيقات إلى التفاعل مع بعضها البعض في عملية تعمل في الخلفية أو تحتاج إلى التفاعل مع النظام. لمزيد من المعلومات حول تطوير واجهات البرمجة في التطبيقات باستخدام AIDL، يُرجى الاطّلاع على لغة تعريف واجهة نظام Android (AIDL). للاطّلاع على أمثلة على استخدام AIDL، راجِع AIDL لواجهات HAL وStable AIDL.
تحديد الإصدار
تتوافق كل لقطة من واجهة برمجة تطبيقات AIDL مع الإصدارات القديمة، وتتطابق مع إصدار.
لالتقاط لقطة، شغِّل الأمر m <module-name>-freeze-api
. عند إصدار عميل أو خادم لواجهة برمجة التطبيقات (على سبيل المثال، في إصدار Mainline)، عليك أخذ لقطة وإنشاء إصدار جديد. بالنسبة إلى واجهات برمجة التطبيقات من النظام إلى المورّد، يجب أن يحدث ذلك مع مراجعة النظام الأساسي السنوية.
لمزيد من التفاصيل والمعلومات حول نوع التغييرات المسموح بها، يُرجى الاطّلاع على إصدار واجهات برمجة التطبيقات.
إرشادات تصميم واجهات برمجة التطبيقات
بنود عامة
1. توثيق كل التفاصيل
- يجب توثيق كل طريقة من حيث دلالاتها ومعامِلاتها واستخدامها للاستثناءات المضمّنة والاستثناءات الخاصة بالخدمة والقيمة المعروضة.
- يجب توثيق كل واجهة من حيث دلالاتها.
- وثِّق المعنى الدلالي لقيم التعداد والثوابت.
- دوِّن أي شيء قد يكون غير واضح للمنفِّذ.
- يُرجى تقديم أمثلة عند الحاجة.
2- الغلاف
استخدِم أسلوب الكتابة بالحروف الكبيرة في بداية الكلمات المتتالية بدون مسافات لأنواع البيانات، وأسلوب الكتابة بالحروف الكبيرة في بداية الكلمات المتتالية بدون مسافات مع كتابة الحرف الأول من الكلمة الأولى بحرف صغير للطرق والحقول والمعلَمات. على سبيل المثال، MyParcelable
لنوع قابل للتسلسل وanArgument
لوسيطة. بالنسبة إلى الاختصارات، اعتبر الاختصار كلمة (NFC
-> Nfc
).
[-Wconst-name] يجب أن تكون قيم التعداد والثوابت 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.
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
خارجية على القيمة null (في C++، يتم ضبطها على std::nullopt
)، ولكن لا يمكن لبرنامج Java قراءتها على أنّها null.
عناصر قابلة للتسلسل منظَّمة
1. حالات الاستخدام
استخدِم عناصر قابلة للتسلسل منظَّمة عندما يكون لديك أنواع بيانات متعددة لإرسالها.
أو عندما يكون لديك نوع بيانات واحد ولكن تتوقّع أنّك ستحتاج إلى توسيعه في المستقبل. على سبيل المثال، لا تستخدِم String username
. استخدِم
كائنًا قابلاً للتوسيع من نوع Parcelable، مثل ما يلي:
parcelable User {
String username;
}
وبالتالي، يمكنك في المستقبل تمديدها باتّباع الخطوات التالية:
parcelable User {
String username;
int id;
}
2- توفير القيم التلقائية بشكل صريح
[-Wexplicit-default, -Wenum-explicit-default] توفير قيم تلقائية صريحة للحقول
حِزم غير منظَّمة
1. حالات الاستخدام
تتوفّر العناصر القابلة للتسلسل غير البنيوي في Java باستخدام
@JavaOnlyStableParcelable
وفي الخلفية NDK باستخدام
@NdkOnlyStableParcelable
. عادةً ما تكون هذه الحزم قديمة وموجودة ولا يمكن تنظيمها.
الثوابت وعمليات التعداد
1. يجب أن تستخدم حقول Bitfields حقولاً ثابتة
يجب أن تستخدم حقول البت حقولاً ثابتة (مثل 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. وقد يؤدي ذلك إلى تسرُّب واصف الملف إذا تم تنفيذ 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;