ממשקים

לכל ממשק שמוגדר בחבילת HIDL יש מחלקה משלו של C++ שנוצרה אוטומטית במרחב השמות של החבילה. לקוחות ושרתים מטפלים בממשקים בדרכים שונות:

  • שרתים מטמיעים ממשקים.
  • שיטות קריאה של לקוחות בממשקים.

הממשקים יכולים להירשם לפי שם על ידי השרת או להעביר אותם בתור ל-methods שהוגדרו ל-HIDL. לדוגמה, קוד framework יכול להציג לקבל הודעות אסינכרוניות מ-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. מעבירים את הטמעת הממשק כארגומנט של שיטת ממשק (לנתונים מפורטים, ראו אסינכרוני). קריאות חוזרות (callbacks)).

במהלך הרישום של הטמעת הממשק, תהליך 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 כדי להפעיל Cast בין ממשקים שונים. השיטה הזו פועלת על ידי ביצוע קריאת IPC לשלט הרחוק כדי לוודא שהסוג הבסיסי זהה לסוג נדרש. אם הסוג המבוקש לא זמין, nullptr הוא הוחזרו.

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

קריאות חוזרות (callback) אסינכרוניות

הרבה הטמעות 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 מכיל את קובץ ה-cookie שהועבר עם linkToDeath(), ואילו הפרמטר who מכיל מצביע חלש אל האובייקט שמייצג את השירות בלקוח. עם הקריאה לדוגמה שצוינה למעלה, cookie שווה ל-1481, ו-who שווה ל-foo.

אפשר גם לבטל את הרישום של מקבל הפטירה לאחר הרישום שלו:

foo->unlinkToDeath(recipient);