دليل أسلوب AIDL

تعمل أفضل الممارسات الموضّحة هنا كدليل لتطوير واجهات AIDL بفعالية مع التركيز على مرونة الواجهة، لا سيما عند استخدام AIDL لتحديد واجهة برمجة تطبيقات أو التفاعل مع مساحات عرض واجهات برمجة التطبيقات.

يمكن استخدام AIDL لتحديد واجهة برمجة التطبيقات عندما تحتاج التطبيقات إلى التفاعل مع بعضها في عملية في الخلفية أو عندما تحتاج إلى التفاعل مع النظام. للحصول على مزيد من المعلومات حول تطوير واجهات البرمجة في التطبيقات باستخدام AIDL، يمكنك الاطّلاع على لغة تعريف واجهة Android (AIDL). للحصول على أمثلة على لغة AIDL عمليًا، يُرجى الاطّلاع على AIDL لـ HALs ولغة 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. تجنُّب مَعلمات Inout

[-Winout-parameter] قد يؤدي ذلك إلى إرباك العملاء لأنه حتى مَعلمات in تبدو كمعلَمات out.

7. تجنُّب المَعلمات غير القابلة للضبط في @nullable وinout

[-Wout-nullable] بما أنّ واجهة Java الخلفية لا تتعامل مع تعليق @nullable التوضيحي بينما تعمل الخلفيات الأخرى على ذلك، قد تؤدي out/inout @nullable T سلوك غير متّسق في الخلفيات. على سبيل المثال، يمكن للواجهات الخلفية بخلاف Java ضبط معلَمة @nullable على "خالية" (في C++ ، ضبطها على std::nullopt) ولكن لا يمكن لعميل Java قراءتها على أنّها قيمة فارغة.

عناصر منظمة

1. متى يمكن استخدامها

استخدِم عناصر البيانات المنظَّمة في حال توفّر أنواع بيانات متعددة لإرسالها.

أو، عندما يكون لديك نوع واحد من البيانات ولكنك تتوقع أنك ستحتاج إلى توسيعه في المستقبل. على سبيل المثال، لا تستخدم السمة String username. استخدِم عنصرًا قابل للتمديد، مثل ما يلي:

parcelable User {
    String username;
}

وبالتالي، يمكنك تمديد هذه الفترة في المستقبل، على النحو التالي:

parcelable User {
    String username;
    int id;
}

2. تقديم الإعدادات التلقائية بوضوح

[-Wexplicit-default, -Wenum-explicit-default] تقديم الإعدادات التلقائية الصريحة للحقول.

العناصر غير المهيكلة

1. متى يمكن استخدامها

تتوفّر العناصر غير المنظَّمة في Java باستخدام @JavaOnlyStableParcelable وفي خلفية NDK مع @NdkOnlyStableParcelable. عادةً ما تكون هذه قطعًا قديمة وحالية لا يمكن هيكلتها.

الثوابت والتعدادات

1. يجب أن تستخدم حقول Bitfields حقولاً ثابتة

يجب أن تستخدم حقول Bitfields حقولاً ثابتة (على سبيل المثال، const int FOO = 3; في واجهة).

2. يجب أن تكون التعدادات مغلقة.

يجب أن تكون التعدادات مغلقة. ملاحظة: يمكن لمالك الواجهة فقط إضافة عناصر التعداد. إذا احتاج البائعون أو المصنّعون الأصليون للأجهزة إلى تمديد هذه الحقول، فهناك حاجة إلى آلية بديلة. كلما أمكن، ينبغي تفضيل وظائف البائع الرئيسية. ومع ذلك، في بعض الحالات، قد يُسمح باستخدام قيم المورّدين المخصّصة (مع ذلك، يجب أن يكون لدى المورّدين آلية محدّدة لإصدار هذا، وربما لغة 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
}

واصف الملفات

[-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;