الخدمات ونقل البيانات

توضِّح هذه الصفحة كيفية تسجيل الخدمات واستكشافها وكيفية إرسال data إلى خدمة من خلال استدعاء الطرق المحدّدة في الواجهات في ملفات .hal.

خدمات التسجيل

يمكن تسجيل خوادم واجهة HIDL (العناصر التي تنفِّذ الواجهة) كخدمات مُسمّاة. ولا يلزم أن يكون الاسم المسجّل مرتبطًا بالواجهة أو اسم الحزمة. في حال عدم تحديد اسم، يتم استخدام الاسم "تلقائي"، ويجب استخدامه مع واجهات HAL التي لا تحتاج إلى تسجيل تنفيذَين للواجهة نفسها. على سبيل المثال، طلب C++ لتسجيل الخدمة المحدّد في كل واجهة هو:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

يتم تضمين إصدار واجهة HIDL في الواجهة نفسها. ويتم ربطه تلقائيًا بتسجيل الخدمة ويمكن استرجاعه من خلال طلب إجراء (android::hardware::IInterface::getInterfaceVersion()) في كل واجهة HIDL. لا يلزم تسجيل كائنات الخادم ويمكن تمريرها من خلال مَعلمات طريقة HIDL إلى عملية أخرى تُجري طلبات اتّصال بطرق HIDL في الخادم.

استكشاف الخدمات

يتم تقديم الطلبات من خلال رمز العميل لواجهة معيّنة بالاسم وبال الإصدار، مع استدعاء getService في فئة HAL المطلوبة:

// C++
sp<V1_1::IFooService> service = V1_1::IFooService::getService();
sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service");
// Java
V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */);
V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);

يتم التعامل مع كل إصدار من واجهة HIDL كواجهة منفصلة. وبالتالي، يمكن تسجيل كلّ منIFooService الإصدار 1.1 وIFooService الإصدار 2.2 كـ "foo_service"، وتلقّي getService("foo_service") على أيّ من الواجهات الخدمة المسجَّلة لتلك الواجهة. لهذا السبب، في معظم الحالات، لا يلزم تقديم مَعلمة اسم للتسجيل أو الاكتشاف (أي اسم "تلقائي").

يلعب عنصر واجهة المورّد أيضًا دورًا في طريقة نقل الواجهة المعروضة. بالنسبة إلى الواجهة IFoo في الحزمة android.hardware.foo@1.0، تستخدم الواجهة التي يعرضها IFoo::getService دائمًا طريقة النقل المعلَن عنها لملف android.hardware.foo في بيان الجهاز إذا كان الإدخال متوفّرًا، وإذا لم تكن طريقة النقل متاحة، يتم عرض القيمة nullptr.

في بعض الحالات، قد يكون من الضروري المتابعة فورًا حتى بدون الحصول على الخدمة. يمكن أن يحدث ذلك (على سبيل المثال) عندما يريد العميل إدارة إشعارات الخدمة بنفسه أو في برنامج تشخيص (مثل atrace) يحتاج إلى الحصول على جميع خدمات الأجهزة واستردادها. في هذه الحالة، يتم توفير واجهات برمجة تطبيقات إضافية، مثل tryGetService في C++ أو getService("instance-name", false) في Java. يجب أيضًا استخدام واجهة برمجة التطبيقات القديمة getService المقدَّمة في Java مع إشعارات الخدمة. لا يؤدي استخدام واجهة برمجة التطبيقات هذه إلى تجنُّب حالة السباق التي يسجّل فيها الخادم نفسه بعد أن يطلبه العميل باستخدام إحدى واجهات برمجة التطبيقات هذه التي لا تتيح إعادة المحاولة.

إشعارات إيقاف الخدمة

يمكن للعملاء الذين يريدون تلقّي إشعار عند إيقاف الخدمة نهائيًا تلقّي إشعارات بالتوقف نهائيًا عن العمل يتم إرسالها من خلال إطار العمل. لتلقّي الإشعارات، يجب أن يستوفي العميل الشروط التالية:

  1. أنشئ فئة فرعية لفئة/واجهة HIDL‏ hidl_death_recipient (في رمز C++ ، وليس في HIDL).
  2. إلغاء طريقة serviceDied()
  3. أنشئ مثيلًا لكائن من الفئة الفرعية hidl_death_recipient.
  4. استخدِم طريقة linkToDeath() في الخدمة للتتبّع، مع إدخال عنصر واجهة IDeathRecipient. يُرجى العلم أنّ هذه المحاولة لا تأخذ ملكية مستلِم الوفاة أو الخادم الوكيل الذي يتم استدعاؤه.

مثال على رمز برمجي وهمي (تتشابه لغة C++ وJava):

class IMyDeathReceiver : hidl_death_recipient {
  virtual void serviceDied(uint64_t cookie,
                           wp<IBase>& service) override {
    log("RIP service %d!", cookie);  // Cookie should be 42
  }
};
....
IMyDeathReceiver deathReceiver = new IMyDeathReceiver();
m_importantService->linkToDeath(deathReceiver, 42);

يمكن تسجيل مستلم الوفاة نفسه في خدمات متعددة مختلفة.

نقل البيانات

يمكن إرسال البيانات إلى خدمة من خلال استدعاء الطرق المحدّدة في الواجهات في ملفات .hal. هناك نوعان من الطرق:

  • تنتظر طرق الحظر إلى أن يُصدر الخادم نتيجة.
  • تُرسِل طُرق الاتجاه الواحد البيانات في اتجاه واحد فقط ولا تؤدي إلى حظرها. إذا تجاوز حجم البيانات التي يتم نقلها في مكالمات RPC حدود التنفيذ ، قد يتم حظر المكالمات أو عرض إشارة خطأ (لم يتم تحديد السلوك بعد).

إنّ الطريقة التي لا تعرض قيمة ولكن لم يتم تحديدها على أنّها oneway لا تزال تؤدي إلى الحظر.

يتم استدعاء جميع الطرق المعلَن عنها في واجهة HIDL في اتجاه واحد، إما من HAL أو إلى HAL. لا تحدِّد الواجهة اتجاه المعالجة. بالنسبة إلى التصاميم التي تحتاج إلى أن تبدأ المكالمات من HAL، يجب أن توفّر واجهتَين (أو أكثر) في حزمة HAL وتعرض الواجهة المناسبة من كل عملية. يتم استخدام كلمتَي العميل و الخادم في ما يتعلّق باتجاه الاتصال بالواجهة (أي يمكن أن يكون HAL خادمًا لواجهة وأحد عملاء واجهة أخرى ).

عمليات معاودة الاتصال

تشير كلمة الاستدعاء إلى مفهومَين مختلفَين، يُميّزهما الاستدعاء المتزامن والاستدعاء غير المتزامن.

تُستخدَم وظائف الاستدعاء المتزامنة في بعض أساليب HIDL التي تعرض البيانات. تعرض طريقة HIDL التي تُرجع أكثر من قيمة واحدة (أو تُرجع قيمة واحدة من نوع غير أساسي) نتائجها من خلال دالة ردّ اتصال. إذا تم عرض قيمة واحدة فقط وكانت من النوع الأساسي، لا يتم استخدام دالة استدعاء ويتم عرض قيمة من الطريقة. ينفِّذ الخادم طرق HIDL وينفِّذ العميل وظائف الاستدعاء.

تسمح عمليات الاستدعاء غير المتزامنة لخادم واجهة HIDL ببدء المكالمات. ويتم ذلك من خلال تمرير مثيل لواجهة ثانية من خلال الواجهة الأولى. يجب أن يعمل برنامج تشغيل الواجهة الأولى كمشغّل للواجهة الثانية. يمكن لخادم الواجهة الأولى استدعاء طرق في عنصر الواجهة الثانية. على سبيل المثال، يمكن لتنفيذ HAL إرسال معلومات بشكل غير متزامن إلى العملية التي تستخدمه من خلال استدعاء طرق في كائن واجهة أنشأته هذه العملية وعرضته. قد تكون الطرق في الواجهات المستخدَمة لردّ الاتصال غير المتزامن حظرًا (وقد تُرجع قيمًا إلى المُتصل) أو oneway. على سبيل المثال، اطّلِع على "وظائف الاستدعاء غير المتزامنة" في HIDL C++.

لتبسيط ملكية الذاكرة، لا تأخذ طلبات استدعاء الطرق وطلبات ردّ الاتصال سوى مَعلمات in ولا تتيح مَعلمات out أو inout.

الحدود القصوى المسموح بها لكل معاملة

لا يتم فرض حدود لكل معاملة على مقدار البيانات المُرسَلة في HIDL methods وcallbacks. ومع ذلك، فإنّ المكالمات التي تتجاوز 4 كيلوبايت لكل معاملة تُعدّ زائدة. في حال ظهور هذا الخطأ، ننصح بإعادة تصميم واجهة HIDL المحدّدة. ومن القيود الأخرى الموارد المتاحة لبنية HIDL الأساسية لمعالجة المعاملات المتعدّدة المتزامنة. يمكن أن تكون عدة معاملات قيد المعالجة في الوقت نفسه بسبب سلاسل محادثات أو عمليات متعددة تُرسِل طلبات إلى عملية أو طلبات oneway متعددة لا تعالجها العملية المستلِمة بسرعة. الحد الأقصى للمساحة الكلية المتوفّرة لجميع المعاملات المتزامنة هو 1 ميغابايت تلقائيًا.

في الواجهات المصمّمة جيدًا، من المفترض ألا يتم تجاوز حدود الموارد هذه. وإذا حدث ذلك، يمكن أن يتم حظر الطلب الذي تجاوزها إلى أن تصبح الموارد متاحة أو الإشارة إلى خطأ في النقل. يتم تسجيل كلّ مرّة يتم فيها تجاوز الحدود المسموح بها لكلّ معاملة أو تجاوز موارد تنفيذ HIDL من خلال دمج المعاملات الجارية لتسهيل تصحيح الأخطاء.

عمليات تنفيذ الطريقة

تُنشئ HIDL ملفات الرأس التي تحدّد الأنواع والأساليب و callbacks اللازمة باللغة المستهدَفة (C++ أو Java). إنّ النموذج الأولي للطريقة المُعرَّفة في HIDL والوظائف المرجعية هو نفسه لكل من رمز العميل والخادم. يقدّم نظام HIDL عمليات تنفيذ وكيل للطرق من جهة المُرسِل التي تنظّم البيانات لنقلها عبر واجهة برمجة التطبيقات بين العمليات، ورمز الرمز البرمجي من جهة المُستلِم الذي ينقل البيانات إلى عمليات تنفيذ المطوّرين ل methods.

يمتلك المُستدعي للدالة (طريقة HIDL أو دالة ردّ الاتصال) ملكية هياكل البيانات التي تم تمريرها إلى الدالة، ويحتفظ بالملكية بعد الاستدعاء. وفي كل الحالات، لا يحتاج المُستدعى إلى تحرير مساحة التخزين أو إزالتها.

  • في C++، قد تكون البيانات للقراءة فقط (قد تؤدي محاولات الكتابة إليها إلى تعطُّل معالجة ملف برمجي) وتكون صالحة طوال مدة الاتصال. يمكن للعميل نسخ البيانات بشكلٍ عميق لنشرها خارج نطاق المكالمة.
  • في Java، يتلقّى الرمز البرمجي نسخة محلية من البيانات (كائن Java عادي)، وقد يحتفظ بها ويُعدّلها أو يسمح بجمعها.

نقل البيانات غير المستند إلى واجهة برمجة التطبيقات (RPC)

تتوفّر في HIDL طريقتان لنقل البيانات بدون استخدام طلب RPC: ذاكرة مشترَكة و"قائمة الرسائل السريعة" (FMQ)، وكلاهما متوافقان مع C++ فقط.

  • الذاكرة المشتركة يُستخدَم نوع HIDL المضمّن memory لتمرير عنصر يمثّل ذاكرة مشترَكة تم تخصيصها. يمكن استخدامها في عملية استقبال لربط الذاكرة المشتركة.
  • قائمة الرسائل السريعة (FMQ): يوفّر HIDL نوعًا من قوائم انتظار الرسائل المستندة إلى النماذج التي تنفّذ عملية تمرير الرسائل بدون انتظار. ولا يستخدم هذا الوضع ملفّ kernel أو أداة جدولة المهام في وضع "النقل المباشر" أو وضع "الربط" (لا يمتلك ملفّ kernel هذه السمات). عادةً ما يُعدّ HAL نهاية قائمة الانتظار، ويُنشئ كائنًا يمكن تمريره من خلال RPC عبر مَعلمة من نوع HIDL المضمّنMQDescriptorSync أو MQDescriptorUnsync. يمكن أن تستخدم عملية الاستقبال هذا العنصر لإعداد الطرف الآخر من "قائمة الانتظار".
    • لا يُسمح بتجاوز قوائم انتظار المزامنة الحد الأقصى المسموح به، ويمكن أن تتضمّن ملفًا واحدًا فقط للقراءة.
    • يُسمح بتجاوز طوابير البيانات غير المتزامنة الحدّ الأقصى، ويمكن أن تتضمّن العديد من القرّاء، ويجب أن يقرأ كلّ منهم البيانات في الوقت المناسب وإلا سيفقدها.
    لا يُسمح لأي من النوعَين بانخفاض عدد العناصر إلى ما دون الحد الأدنى (تتعذّر القراءة من قائمة انتظار فارغة)، ويمكن أن يحتوي كل نوع على كاتب واحد فقط.

لمزيد من التفاصيل حول "قائمة الرسائل السريعة"، يُرجى الاطّلاع على مقالة قائمة الرسائل السريعة (FMQ).