ממשקים וחבילות

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

חבילות

שמות של חבילות יכולים לכלול רמות משנה, כמו package.subpackage. הספרייה ברמה הבסיסית (root) של חבילות HIDL שפורסמו היא hardware/interfaces או vendor/vendorName (לדוגמה, vendor/google במכשירי Pixel). שם החבילה יוצר ספריית משנה אחת או יותר בספריית הבסיס. כל הקבצים שמגדירים את החבילה נמצאים באותה ספרייה. לדוגמה, אפשר למצוא את package android.hardware.example.extension.light@2.0 בקטע hardware/interfaces/example/extension/light/2.0.

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

תחילית של חבילה מיקום סוגי ממשקים
android.hardware.* hardware/interfaces/* HAL
android.frameworks.* frameworks/hardware/interfaces/* frameworks/ related
android.system.* system/hardware/interfaces/* system/ related
android.hidl.* system/libhidl/transport/* core

ספריית החבילה מכילה קבצים עם הסיומת .hal. כל קובץ חייב לכלול הצהרת package שמציינת את השם ואת הגרסה של החבילה שהקובץ נכלל בה. הקובץ types.hal, אם הוא קיים, לא מגדיר ממשק, אלא מגדיר סוגי נתונים שכל ממשק בחבילה יכול לגשת אליהם.

הגדרת ממשק

מלבד types.hal, כל קובץ .hal אחר מגדיר ממשק. ממשק מוגדר בדרך כלל כך:

interface IBar extends IFoo { // IFoo is another interface
    // embedded types
    struct MyStruct {/*...*/};

    // interface methods
    create(int32_t id) generates (MyStruct s);
    close();
};

ממשק ללא הצהרה מפורשת של extends הוא ממשק שמבוסס על android.hidl.base@1.0::IBase באופן משתמע (בדומה ל-java.lang.Object ב-Java). הממשק IBase, שמיובאים באופן משתמע, מכריז על כמה שיטות שמורות שאסור להצהיר עליהן מחדש בממשקים מוגדרי-משתמש, או להשתמש בהן בדרכים אחרות. השיטות האלה כוללות:

  • ping
  • interfaceChain
  • interfaceDescriptor
  • notifySyspropsChanged
  • linkToDeath
  • unlinkToDeath
  • setHALInstrumentation
  • getDebugInfo
  • debug
  • getHashChain

תהליך הייבוא

ההצהרה import היא מנגנון HIDL לגישה לממשקים ולסוגים של חבילה בחבילה אחרת. הצהרת import עוסקת בשתי ישויות:

  • הישותייבוא, שיכולה להיות חבילה או ממשק
  • הישות המיובאת, שיכולה להיות חבילה או ממשק

הישות המייבאת נקבעת לפי המיקום של ההצהרה import. כשהצהרה נמצאת ב-types.hal של החבילה, הנתונים שיובאו גלויים לכל החבילה. זהו ייבוא ברמת החבילה. כשהצהרה נמצאת בקובץ ממשק, הישות המייבאת היא הממשק עצמו. זהו ייבוא ברמת הממשק.

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

  • ייבוא של חבילות שלמות אם הערך הוא שם חבילת וגרסה (התחביר מתואר בהמשך), החבילה כולה מיובאת לישות המייבאת.
  • ייבוא חלקי אם הערך הוא:
    • ממשק, types.hal של החבילה והממשק הזה מיובאים לישויות המייבאת.
    • UDT שמוגדר ב-types.hal, ואז רק ה-UDT הזה יובא לישות המייבאת (סוגי נתונים אחרים ב-types.hal לא מיובאים).
  • ייבוא של סוגים בלבד אם הערך כולל את התחביר של ייבוא חלקי שמתואר למעלה, אבל עם מילת המפתח types במקום שם ממשק, רק ה-UDTs ב-types.hal של החבילה הייעודית מיובאים.

הישות המייבאת מקבלת גישה לשילוב של:

  • הטיפוסים הנפוצים של UDT של החבילה המיובאת שמוגדרים ב-types.hal.
  • ממשקי החבילה המיובאת (לייבוא של החבילה כולה) או הממשק שצוין (לייבוא חלקי) לצורך קריאה להם, העברת כינויים אליהם ו/או ירושה מהם.

הצהרת הייבוא משתמשת בתחביר של שם סוג מלא כדי לספק את השם והגרסה של החבילה או הממשק שיובאים:

import android.hardware.nfc@1.0;            // import a whole package
import android.hardware.example@1.0::IQuux; // import an interface and types.hal
import android.hardware.example@1.0::types; // import just types.hal

ירושה של ממשק

ממשק יכול להיות תוסף לממשק שהוגדר בעבר. התוספים יכולים להיות מאחד משלושת הסוגים הבאים:

  • ממשק יכול להוסיף פונקציונליות לממשק אחר, תוך שילוב ה-API שלו ללא שינוי.
  • חבילה יכולה להוסיף פונקציונליות לחבילה אחרת, תוך שילוב ה-API שלה ללא שינוי.
  • ממשק יכול לייבא סוגים מחבילה או ממשק ספציפי.

ממשק יכול להרחיב רק ממשק אחד נוסף (אין ירושה מרובה). כל ממשק בחבילה עם מספר גרסה משני שאינו אפס חייב להרחיב ממשק בגרסה הקודמת של החבילה. לדוגמה, אם ממשק IBar בגרסה 4.0 של החבילה derivative מבוסס על ממשק IFoo בגרסה 1.2 של החבילה original, ונוצרת גרסה 1.3 של החבילה original, IBar בגרסה 4.1 לא יכול להרחיב את הגרסה 1.3 של IFoo. במקום זאת, IBar בגרסה 4.1 צריך להרחיב את IBar בגרסה 4.0, שמקושרת ל-IFoo בגרסה 1.2. אם רוצים, אפשר להרחיב את IFoo בגרסה 1.3 באמצעות IBar בגרסה 5.0.

תוספים לממשק לא משמעותם תלות בספרייה או הכללה של HAL בממשק בקוד שנוצר – הם פשוט מייבאים את מבנה הנתונים והגדרות השיטות ברמת HIDL. כל שיטה ב-HAL חייבת להיות מוטמעת ב-HAL הזה.

תוספי ספקים

במקרים מסוימים, תוספי ספקים מיושמים כסוג משנה של אובייקט הבסיס שמייצג את ממשק הליבה שהם מרחיבים. אותו אובייקט רשום בשם ובגרסה של HAL הבסיסי, וגם בשם ובגרסה של HAL של התוסף (הספק).

ניהול גרסאות

לחבילות יש גרסאות, ולממשקים יש את הגרסה של החבילה שלהם. הגרסאות מיוצגות בשני מספרים שלמים, major.minor.

  • לגרסאות ראשיות אין תאימות לאחור. הוספת מספר לגרסה הראשית מאפסת את מספר הגרסה המשנית ל-0.
  • לגרסאות משניות יש תאימות לאחור. הוספת מספר לגרסה המשנית מציינת שהגרסה החדשה תואמת לאחור לגרסה הקודמת. אפשר להוסיף שיטות ומבנים חדשים של נתונים, אבל אי אפשר לשנות מבנים קיימים של נתונים או חתימות של שיטות.

יכולות להיות כמה גרסאות ראשיות או משניות של HAL במכשיר בו-זמנית. עם זאת, מומלץ להשתמש בגרסה משנית במקום בגרסה ראשית, כי קוד לקוח שפועל עם ממשק של גרסה משנית קודמת פועל גם עם גרסאות משניות מאוחרות יותר של אותו ממשק. למידע נוסף על ניהול גרסאות ועל תוספים של ספקים, ראו ניהול גרסאות ב-HIDL.

סיכום של פריסת הממשק

בקטע הזה נסכם איך לנהל חבילת ממשק HIDL (כמו hardware/interfaces) ונרכז את המידע שמוצג בקטע HIDL. לפני קריאת המאמר, מומלץ להכיר את המושגים הבאים: ניהול גרסאות של HIDL, יצירת גיבוב באמצעות hidl-gen, פרטי העבודה עם HIDL באופן כללי וההגדרות הבאות:

מונח הגדרה
Application Binary Interface ‏ (ABI) ממשק לתכנות אפליקציות (API) וכן כל קישורים בינאריים נדרשים.
שם מלא (fqName) שם להבדיל בין סוגי hidl. דוגמה: android.hardware.foo@1.0::IFoo.
חבילה חבילה שמכילה ממשק וסוגי HIDL. דוגמה: android.hardware.foo@1.0.
root של החבילה חבילת Root שמכילה את ממשקי HIDL. דוגמה: ממשק HIDL‏ android.hardware נמצא ברמה הבסיסית (root) של החבילה android.hardware.foo@1.0.
נתיב הבסיס של החבילה המיקום בעץ המקור של Android שאליו ממופה שורש החבילה.

הגדרות נוספות זמינות בטרמינולוגיה של HIDL.

אפשר למצוא כל קובץ באמצעות מיפוי של שורש החבילה ושם הקובץ המלא

השורשים של החבילות מצוינים ב-hidl-gen כארגומנטים -r android.hardware:hardware/interfaces. לדוגמה, אם החבילה היא vendor.awesome.foo@1.0::IFoo ו-hidl-gen נשלח -r vendor.awesome:some/device/independent/path/interfaces, קובץ הממשק צריך להיות ב-$ANDROID_BUILD_TOP/some/device/independent/path/interfaces/foo/1.0/IFoo.hal.

בפועל, מומלץ לספק או ליצרן ציוד מקורי בשם awesome להציב את הממשקים הרגילים שלו ב-vendor.awesome. אחרי שבוחרים נתיב לחבילה, אסור לשנות אותו כי הוא מוטמע ב-ABI של הממשק.

מיפוי של נתיב החבילה צריך להיות ייחודי

לדוגמה, אם יש לכם -rsome.package:$PATH_A ו--rsome.package:$PATH_B, הערך של $PATH_A צריך להיות שווה ל-$PATH_B כדי ליצור ספריית ממשק עקבית (כך גם קל יותר ליצור ממשקי גרסאות).

ברמה הבסיסית של החבילה צריך להיות קובץ ניהול גרסאות

אם יוצרים נתיב חבילה כמו -r vendor.awesome:vendor/awesome/interfaces, צריך ליצור גם את הקובץ $ANDROID_BUILD_TOP/vendor/awesome/interfaces/current.txt, שצריך להכיל גיבוב של ממשקים שנוצרו באמצעות האפשרות -Lhash בקובץ hidl-gen (הנושא הזה מוסבר בהרחבה במאמר גיבוב באמצעות hidl-gen).

ממשקים נמצאים במיקומים שאינם תלויים במכשיר

בפועל, מומלץ לשתף ממשקים בין ההסתעפויות. כך תוכלו לעשות שימוש חוזר בקוד ולבדוק אותו במגוון מכשירים ותרחישי שימוש.