ממשקים

לכל ממשק שמוגדר בחבילת 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 remoting). מכיוון שהלקוח קרא ל-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);

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

בהרבה הטמעות קיימות של 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);