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