واجهات

تحتوي كل واجهة محدّدة في حزمة HIDL على فئة C++ مُنشأة تلقائيًا داخل مساحة اسم الحزمة. يتعامل العملاء والخوادم مع الواجهات بطرق مختلفة:

  • تنفِّذ الخوادم الواجهات.
  • العملاء يستدعون طرقًا على الواجهات.

يمكن أن يسجِّل الخادم الواجهات حسب الاسم أو يمررها كمَعلمات إلى الطرق المحدَّدة في HIDL. على سبيل المثال، يمكن أن يقدّم رمز إطار العمل واجهة لتلقّي رسائل غير متزامنة من HAL ونقل هذه الواجهة مباشرةً إلى HAL بدون تسجيلها.

تنفيذ الخادم

يجب أن يتضمّن الخادم الذي ينفِّذ واجهة IFoo ملف العنوان IFoo الذي تم إنشاؤه تلقائيًا:

#include <android/hardware/samples/1.0/IFoo.h>

يتم تصدير العنوان تلقائيًا من خلال المكتبة المشتركة لواجهة IFoo لربطها. مثال IFoo.hal:

// IFoo.hal
interface IFoo {
    someMethod() generates (vec<uint32_t>);
    ...
}

مثال على هيكل لتنفيذ واجهة IFoo على الخادم:

// From the IFoo.h header
using android::hardware::samples::V1_0::IFoo;

class FooImpl : public IFoo {
    Return<void> someMethod(foo my_foo, someMethod_cb _cb) {
        vec<uint32_t> return_data;
        // Compute return_data
        _cb(return_data);
        return Void();
    }
    ...
};

لإتاحة تنفيذ واجهة خادم لأحد العملاء، يمكنك إجراء ما يلي:

  1. سجِّل عملية تنفيذ الواجهة باستخدام hwservicemanager (اطّلِع على التفاصيل أدناه)،

    أو

    .
  2. اضبط تنفيذ الواجهة كوسيطة لطريقة الواجهة (للاطّلاع على التفاصيل، راجِع الاستدعاءات المتزامنة).

عند تسجيل تنفيذ الواجهة، تتتبّع عملية hwservicemanager واجهات HIDL المسجَّلة التي تعمل على الجهاز حسب الاسم والإصدار. يمكن للخوادم تسجيل تنفيذ واجهة HIDL حسب الاسم ويمكن للعملاء طلب تنفيذات الخدمة حسب الاسم والإصدار. توفّر هذه العملية واجهة HIDL android.hidl.manager@1.0::IServiceManager.

يحتوي كل ملف عنوان لواجهة HIDL تم إنشاؤه تلقائيًا (مثل IFoo.h) على طريقة registerAsService() يمكن استخدامها لتسجيل تنفيذ الواجهة مع hwservicemanager. الوسيطة الوحيدة المطلوبة هي اسم عمليات تنفيذ الواجهة لأنّ العملاء يستخدمون هذا الاسم لاسترداد الواجهة من hwservicemanager لاحقًا:

::android::sp<IFoo> myFoo = new FooImpl();
::android::sp<IFoo> mySecondFoo = new FooAnotherImpl();
status_t status = myFoo->registerAsService();
status_t anotherStatus = mySecondFoo->registerAsService("another_foo");

يتعامل hwservicemanager مع مجموعة [package@version::interface, instance_name] على أنّها فريدة من نوعها لتتمكّن الواجهات المختلفة (أو الإصدارات المختلفة من الواجهة نفسها) من التسجيل بأسماء مثيلات متطابقة بدون حدوث تعارضات. إذا استدعيت registerAsService() باستخدام إصدار الحزمة والواجهة واسم المثيل نفسهما بالضبط، سيتخلّص hwservicemanager من الإشارة إلى الخدمة المسجَّلة سابقًا وسيستخدم الخدمة الجديدة.

تنفيذ العميل

على العميل #include كل واجهة يشير إليها، تمامًا كما يفعل الخادم:

#include <android/hardware/samples/1.0/IFoo.h>

يمكن للعميل الحصول على واجهة بطريقتَين:

  • من خلال I<InterfaceName>::getService (عبر hwservicemanager)
  • من خلال طريقة واجهة

يحتوي كل ملف عنوان واجهة تم إنشاؤه تلقائيًا على getService طريقة ثابتة يمكن استخدامها لاسترداد مثيل خدمة من hwservicemanager:

// getService returns nullptr if the service can't be found
sp<IFoo> myFoo = IFoo::getService();
sp<IFoo> myAlternateFoo = IFoo::getService("another_foo");

أصبح لدى العميل الآن واجهة IFoo، ويمكنه استدعاء الطرق ل استخدامها كما لو كانت تنفيذًا لفئة محلية. في الواقع، يمكن تنفيذ الإجراء في العملية نفسها أو عملية مختلفة أو حتى على جهاز آخر (باستخدام واجهة HAL عن بُعد). بما أنّ العميل استدعى getService على IFoo مضمّن من الإصدار 1.0 من الحزمة، لن يعرضhwservicemanager تنفيذًا للخادم إلا إذا كان هذا التنفيذ متوافقًا مع عملاء 1.0. من الناحية العملية، يعني ذلك أنّ عمليات تنفيذ الخادم التي تستخدم الإصدار 1.n فقط (يجب أن يمتد الإصدار x.(y+1) من الواجهة (يُكتسَب من) x.y).

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

sp<V1_0::IFoo> foo1_0 = V1_0::IFoo::getService();
sp<V1_1::IFoo> foo1_1 = V1_1::IFoo::castFrom(foo1_0);

طلبات معاودة الاتصال غير المتزامنة

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

مثال على ملف واجهة IFooCallback.hal:

package android.hardware.samples@1.0;
interface IFooCallback {
    sendEvent(uint32_t event_id);
    sendData(vec<uint8_t> data);
}

مثال على طريقة جديدة في IFoo تستخدِم مَعلمة IFooCallback:

package android.hardware.samples@1.0;
interface IFoo {
    struct Foo {
       int64_t someValue;
       handle myHandle;
    };

    someMethod(Foo foo) generates (int32_t ret);
    anotherMethod() generates (vec<uint32_t>);
    registerCallback(IFooCallback callback);
};

العميل الذي يستخدم واجهة IFoo هو الخادم لواجهة IFooCallback، ويقدّم تنفيذًا لواجهة IFooCallback:

class FooCallback : public IFooCallback {
    Return<void> sendEvent(uint32_t event_id) {
        // process the event from the HAL
    }
    Return<void> sendData(const hidl_vec<uint8_t>& data) {
        // process data from the HAL
    }
};

ويمكنه أيضًا تمرير ذلك ببساطة على مثيل حالي من واجهة IFoo:

sp<IFooCallback> myFooCallback = new FooCallback();
myFoo.registerCallback(myFooCallback);

يتلقّى الخادم الذي ينفّذ IFoo هذا العنصر كعنصر sp<IFooCallback>. ويمكنه تخزين طلب إعادة الاتصال والاتصال مجددًا بالزبون متى أراد استخدام هذه الواجهة.

المستلمون في حال الوفاة

بما أنّ عمليات تنفيذ الخدمات يمكن أن تعمل في عملية مختلفة، قد يحدث أن تنتهي عملية تنفيذ واجهة مع استمرار عمل العميل. تؤدي أيّ طلبات إلى عنصر واجهة مستضاف في عملية توقّفت عن العمل إلى تعذُّر الحصول على النتيجة بسبب خطأ في النقل (isOK() يعرض false). والطريقة الوحيدة للتغلب على هذا التعذُّر هي طلب مثيل جديد من الخدمة من خلال استدعاء I<InterfaceName>::getService(). لا يعمل هذا الإجراء إلا إذا تم إعادة تشغيل العملية التي تعطّلت وإعادة تسجيل خدماتها باستخدام servicemanager (وهو ما ينطبق بشكل عام على عمليات تنفيذ HAL).

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

foo->linkToDeath(recipient, 1481 /* cookie */);

يجب أن تكون المَعلمة recipient عملية تنفيذ لواجهة android::hardware::hidl_death_recipient التي يوفّرها HIDL، التي تحتوي على طريقة واحدة serviceDied() يتمّ استدعاؤها من سلسلة مهام في تجمع سلاسل مهام RPC عند إنهاء العملية التي تستضيف الواجهة:

class MyDeathRecipient : public android::hardware::hidl_death_recipient {
    virtual void serviceDied(uint64_t cookie, const android::wp<::android::hidl::base::V1_0::IBase>& who) {
       // Deal with the fact that the service died
    }
}

تحتوي المَعلمة cookie على ملف تعريف الارتباط الذي تم تمريره مع linkToDeath()، في حين تحتوي المَعلمة who على مُشير ضعيف إلى العنصر الذي يمثّل الخدمة في العميل. استنادًا إلى نموذج المكالمة أعلاه، تساوي cookie 1481، وwho يساوي foo.

من الممكن أيضًا إلغاء تسجيل مستلم الوفاة بعد تسجيله:

foo->unlinkToDeath(recipient);