שירותים והעברת נתונים

בדף הזה מוסבר איך להירשם ולגלות שירותים ואיך לשלוח אותם נתונים לשירות באמצעות שיטות קריאה שמוגדרות בממשקים ב-.hal .

רישום שירותים

ניתן לרשום שרתי ממשק HIDL (אובייקטים שמטמיעים את הממשק) בתור שירותים בעלי שם. השם הרשום לא חייב להיות קשור לממשק או שם החבילה. אם לא תציינו שם, השם 'default' יהיה 'default'. נעשה בו שימוש, צריך שיכול לשמש ל-HALs שלא צריכים לרשום שתי הטמעות של גרפי. לדוגמה, קריאת C++ לגבי רישום שירות שמוגדרת בכל הוא:

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

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

לאובייקט Vendor Interface גם יש תפקיד מרכזי בשיטת התעבורה שחוזרת על עצמו. לממשק IFoo בחבילה android.hardware.foo@1.0, הממשק שהוחזר על ידי IFoo::getService משתמש תמיד בשיטת התעבורה שהוצהרה עבור android.hardware.foo במניפסט של המכשיר, אם הרשומה קיימת; ואם שיטת התעבורה לא זמינה, מוחזר nullptr.

במקרים מסוימים, ייתכן שיהיה צורך להמשיך באופן מיידי גם בלי לקבל את השירות. זה יכול לקרות (למשל) כשלקוח רוצה לנהל את ההתראות בשירות עצמו או במסגרת תוכנית אבחון (כמו atrace) שצריך לקבל את כל שירותי ה-hwservices ולאחזר אותם. לחשבון במקרה הזה, יש ממשקי API נוספים כמו tryGetService ב-C++ או getService("instance-name", false) ב-Java. ה-API מהדור הקודם צריך להשתמש גם בשדה getService שסופק ב-Java עם השירות התראות. שימוש ב-API הזה לא מונע מרוץ תהליכים שבו שרת רושם את עצמו אחרי שהלקוח מבקש אותו באמצעות אחד מממשקי ה-API האלה ללא ניסיון חוזר.

התראות על מקרי מוות של שירות

לקוחות שרוצים לקבל הודעה על מוות של שירות מסוים עלולים לקבל מוות התראות שנמסרות על ידי ה-framework. כדי לקבל התראות, הלקוח חייב:

  1. מחלקה משנית של המחלקה או הממשק HIDL hidl_death_recipient (ב-C++ לא ב-HIDL).
  2. שינוי ה-method serviceDied().
  3. יצירת אובייקט ממחלקת המשנה hidl_death_recipient.
  4. מפעילים את ה-method linkToDeath() בשירות כדי לעקוב אחרי הנתונים. מעבירים את אובייקט הממשק של IDeathRecipient. לתשומת ליבכם: לא לוקחת בעלות על מקבל הפטירה או על שרת ה-proxy שבו נקראת.

דוגמה לקוד מדומה (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 ולמלא את בממשק המתאים מכל תהליך. המילים client ו- משתמשים בשרת ביחס לכיוון הקריאה של הממשק (כלומר, תכונת ה-HAL יכולה להיות שרת של ממשק אחד ולקוח של ממשק אחר גרפי).

התקשרות חזרה

המילה קריאה חוזרת (callback) מתייחסת לשני מושגים שונים, שההבחנה ביניהם היא קריאה חוזרת סינכרונית וקריאה חוזרת (callback) אסינכרונית.

נעשה שימוש בקריאות חוזרות סינכרוניות בחלק משיטות HIDL שחוזרות . שיטת HIDL שמחזירה יותר מערך אחד (או מחזירה ערך אחד של שאינו פרימיטיבי) מחזירה את התוצאות באמצעות פונקציית קריאה חוזרת. אם רק משתמש אחד מוחזר ערך וזהו טיפוס פרימיטיבי, לא נעשה שימוש בקריאה חוזרת (callback) מוחזר מה-method. השרת מיישם את השיטות HIDL הלקוח מיישם את הקריאות החוזרות (callback).

קריאות חוזרות אסינכרוניות מאפשרות לשרת של ממשק HIDL: שיחות יוצאות. כדי לעשות את זה, מעבירים מופע של ממשק שני דרך הממשק הראשון. הלקוח של הממשק הראשון חייב לפעול בתור של שרת השני. השרת של הממשק הראשון יכול לקרוא ל-methods אובייקט של ממשק שני. לדוגמה, הטמעת נתוני HAL יכולה לשלוח מידע חזרה באופן אסינכרוני לתהליך שבו נעשה שימוש בו על ידי קריאה ל-methods את האובייקט של הממשק שנוצר והוגש באמצעות התהליך הזה. שיטות בממשקים שבהם נעשה שימוש עבור קריאה חוזרת אסינכרונית, עשויות לחסום (ועשויה להחזיר ערכים למתקשר) או oneway. לדוגמה, אפשר לעיין במאמר 'קריאות חוזרות אסינכרוניות'. באזור HIDL C++.

כדי לפשט את הבעלות על הזיכרון, קריאות לשיטות וקריאות חוזרות (callback) מטופלות רק in פרמטרים שלא תומכים ב-out או inout פרמטרים.

מגבלות לכל עסקה

מגבלות לכל עסקה לא חלות על כמות הנתונים שנשלחים ב-HIDL שיטות וקריאות חוזרות (callback). עם זאת, קריאות שמגיעות מ-4KB לכל עסקה נחשבת למוגזמת. אם נראה זאת, יצירת ארכיטקטורה מחדש של ממשק HIDL הנתון מומלץ. מגבלה נוספת היא המשאבים שזמינים ל-HIDL לתשתית לטיפול בעסקאות מרובות בו-זמנית. יותר מאחת טרנזקציות יכולות להיות פעילות בו-זמנית בגלל מספר שרשורים יוצרת שיחות לתהליך או למספר קריאות oneway לא מטופלות במהירות בתהליך הקבלה. נפח כולל מקסימלי זמין לכל העסקאות בו-זמנית הוא 1MB כברירת מחדל.

בממשק מתוכנן היטב, חריגה ממגבלות המשאבים האלה קורה; אם כן, השיחה שחורגת מהמגבלות יכולה לחסום עד שהמשאבים הופכים לזמינים או מסמנים שגיאה בהעברה. כל אירוע של חריגה ממגבלות לכל עסקה או חריגה ממשאבי ההטמעה של HIDL על ידי טרנזקציות מצטברות במהלך ההפעלה נרשמות כדי להקל על ניפוי באגים.

הטמעות של שיטות

HIDL יוצר קובצי כותרת עם הצהרה על הסוגים והשיטות הנדרשים קריאות חוזרות (callback) בשפת היעד (C++ או Java). אב הטיפוס של HIDL ה-methods וה-callbacks זהים עבור קוד הלקוח וקוד השרת. HIDL מספקת הטמעות שרתי proxy של השיטות צד המתקשר שמארגן את הנתונים להעברת IPC, ו-stub בצד מקבל הקריאה החוזרת, שמעביר את הנתונים לאפליקציות של המפתחים שיטות.

מבצע הקריאה החוזרת (callback) של פונקציה (שיטת HIDL או קריאה חוזרת) הוא בעלות על הנתונים של מבנים שמועברים לפונקציה, ושומרים על הבעלות אחרי הקריאה. באזור כל המקרים שבהם מקבל הקריאה לא צריך לפנות מקום או לשחרר את האחסון.

  • ב-C++ הנתונים עשויים להיות לקריאה בלבד (ניסיונות לכתוב אליהם עלולים לגרום טעות בפילוח) והם תקפים למשך השיחה. הלקוח יכול לבצע העתקה עמוקה של הנתונים כדי להפיץ אותם מעבר לקריאה.
  • ב-Java, הקוד מקבל עותק מקומי של הנתונים (אובייקט Java רגיל), שהוא יכול לשמור ולשנות או לאפשר איסוף אשפה.

העברת נתונים ללא RPC

ל-HIDL יש שתי דרכים להעביר נתונים ללא שימוש בקריאה ל-RPC: זיכרון ותור הודעות מהיר (FMQ), שתיהן נתמכים רק ב-C++.

  • זיכרון משותף. סוג HIDL המובנה memory משמש להעברת אובייקט שמייצג זיכרון משותף שהוקצה. אפשר להשתמש בו בתהליך קבלה כדי למפות את הזיכרון המשותף.
  • תור הודעות מהיר (FMQ). HIDL מספקת הודעה בתבנית תור שמאפשר להעביר הודעות ללא המתנה. הוא לא משתמש בליבה (kernel) או מתזמן הודעות במצב של העברה או קשר כוללים את המאפיינים האלה). בדרך כלל, ב‐HAL מגדיר את סוף התור, יצירת אובייקט שניתן להעביר דרך RPC דרך פרמטר של רכיב HIDL מסוג MQDescriptorSync או MQDescriptorUnsync. הזה בתהליך המקבל אפשר להשתמש באובייקט כדי להגדיר את הקצה השני של התור.
    • סנכרון בתורים לא מורשים, והם יכולים לכלול רק תור אחד בקורא.
    • תורים לא מסונכרנים יכולים לגלוש, והם יכולים לכלול קוראים רבים, שכל אחת מהן צריכה לקרוא את הנתונים בזמן או לאבד אותם.
    אף אחד מהסוגים לא מורשה לבצע זרימה של פחות זרימה (כשלקריאה מתור ריק), וגם לכל סוג יכול להיות רק כותב אחד.

לפרטים נוספים על FMQ: תור הודעות מהיר (FMQ).