ב-HIDL נדרש שממשק לכל גרסה יהיה כתוב ב-HIDL. אחרי שפורסם ממשק HAL, הוא קופא וכל שינוי נוסף צריך להתבצע בגרסה חדשה של הממשק. אי אפשר לשנות ממשק שפורסם, אבל אפשר להרחיב אותו באמצעות ממשק אחר.
מבנה הקוד של HIDL
קוד HIDL מאורגן בסוגי ממשקים וחבילות שהוגדרו על ידי משתמשים:
- סוגים מוגדרים על ידי משתמשים (UDT). HIDL מספק גישה לקבוצה של סוגי נתונים פרימיטיביים שאפשר להשתמש בהם כדי ליצור סוגים מורכבים יותר באמצעות מבנים, איחודים ומניינים. משתני UDT מועברים לשיטות של ממשקים, וניתן להגדיר אותם ברמת החבילה (שמשותפת לכל הממשקים) או באופן מקומי לממשק.
- ממשקים. ממשק הוא אבן בניין בסיסית של HIDL, והוא מורכב מ-UDT ומהצהרות על שיטות. ממשקים יכולים גם לרשת ממשק אחר.
- חבילות. ארגון של ממשקי HIDL קשורים וסוגים של נתונים שבהם הם פועלים. חבילה מזוהה לפי שם וגרסה, והיא כוללת את הפריטים הבאים:
- קובץ הגדרה של סוג נתונים שנקרא
types.hal
. - אפס ממשקים או יותר, כל אחד בקובץ
.hal
משלו.
- קובץ הגדרה של סוג נתונים שנקרא
קובץ ההגדרה של סוגי הנתונים types.hal
מכיל רק UDT (כל סוגי ה-UDT ברמת החבילה נשמרים בקובץ אחד). הייצוגים בשפת היעד זמינים לכל הממשקים בחבילה.
פילוסופיית ניהול הגרסאות
לאחר פרסום של חבילת HIDL (כמו android.hardware.nfc
) לגרסה מסוימת (כמו 1.0
), אי אפשר לשנות אותה. אפשר לבצע שינויים בממשקים בחבילה או שינויים ב-UDT שלה רק בחבילה אחרת.
ב-HIDL, ניהול הגרסאות חל ברמת החבילה ולא ברמת הממשק, וכל הממשקים וה-UDTs בחבילה משתפים את אותה גרסה. גרסאות החבילות מבוססות על ניהול גרסאות סמנטי, ללא רמת התיקון והרכיבים של המטא-נתונים של ה-build. בתוך חבילה נתונה, עלייה בגרסה משנית מצביעה על כך שהגרסה החדשה של החבילה תואמת לאחור לחבילה הישנה, ועלייה בגרסה ראשית מצביעה על כך שהגרסה החדשה של החבילה לא תואמת לאחור לחבילה הישנה.
מבחינה מושגית, חבילה יכולה להכיל חבילה אחרת באחת מכמה דרכים:
- בכלל לא.
- יכולת התאמה לעומס (extendibility) ברמת החבילה עם תאימות לאחור. המצב הזה מתרחש בגרסאות חדשות של גרסאות משנה (next incremented revision) של חבילה. לחבילה החדשה יש את אותו שם ואותה גרסה ראשית כמו לחבילה הישנה, אבל גרסה משנה גבוהה יותר. מבחינה פונקציונלית, החבילה החדשה היא קבוצה רחבה יותר מהחבילה הישנה, כלומר:
- ממשקי הרמה העליונה של החבילה ההורה נמצאים בחבילה החדשה, אבל יכול להיות שהממשקים יכללו שיטות חדשות, UDT מקומיים חדשים לממשק (התוסף ברמת הממשק שמתואר בהמשך) ו-UDT חדשים ב-
types.hal
. - אפשר גם להוסיף ממשקים חדשים לחבילה החדשה.
- כל סוגי הנתונים של החבילה ההורה נמצאים בחבילה החדשה, וניתן לטפל בהם באמצעות השיטות מהחבילה הישנה (שעשויות להיות מוטמעות מחדש).
- אפשר גם להוסיף סוגי נתונים חדשים לשימוש בשיטות חדשות של ממשקים קיימים ששודרגו, או בממשקים חדשים.
- ממשקי הרמה העליונה של החבילה ההורה נמצאים בחבילה החדשה, אבל יכול להיות שהממשקים יכללו שיטות חדשות, UDT מקומיים חדשים לממשק (התוסף ברמת הממשק שמתואר בהמשך) ו-UDT חדשים ב-
- יכולת הרחבה ברמת הממשק שתואמת לאחור. החבילה החדשה יכולה גם להרחיב את החבילה המקורית על ידי שימוש בממשקים נפרדים לוגית שמספקים פונקציונליות נוספת, ולא את הפונקציות המרכזיות.
לשם כך, כדאי לבצע את הפעולות הבאות:
- ממשקים בחבילה החדשה צריכים להשתמש בסוגי הנתונים של החבילה הישנה.
- ממשקים בחבילה חדשה יכולים להרחיב ממשקים של חבילת קוד ישנה אחת או יותר.
- להרחיב את חוסר התאימות לדורות קודמים. זוהי גרסה משופרת של החבילה בגרסה הראשית, ואין צורך בקורלציה כלשהי ביניהן. אם יש תכונה כזו, אפשר לבטא אותה באמצעות שילוב של סוגים מהגרסה הישנה של החבילה וירושה של קבוצת משנה של ממשקי החבילה הישנה.
מבנה הממשק
בממשק בעל מבנה טוב, הוספת סוגים חדשים של פונקציונליות שלא נכללים בעיצוב המקורי צריכה לחייב שינוי בממשק HIDL. לעומת זאת, אם אפשר או צפוי לבצע שינוי בשני הצדדים של הממשק שמוסיף פונקציונליות חדשה בלי לשנות את הממשק עצמו, הממשק לא מובנה.
Treble תומך ברכיבי מערכת ורכיבי ספק שנוצרו בנפרד, שבהם אפשר ליצור את vendor.img
במכשיר ואת system.img
בנפרד. כל האינטראקציות בין vendor.img
ל-system.img
צריכות להיות מוגדרות באופן מפורש ומקיף כדי שהן יוכלו להמשיך לפעול במשך שנים רבות. הוא כולל ממשקי API רבים, אבל אחד מהם הוא מנגנון ה-IPC שבו HIDL משתמש לתקשורת בין תהליכים בגבול system.img
/vendor.img
.
דרישות
כל הנתונים המועברים דרך HIDL חייבים להיות מוגדרים באופן מפורש. כדי להבטיח שההטמעה והלקוח יוכלו להמשיך לפעול יחד גם כשהם יתאספו בנפרד או יתפתחו בנפרד, הנתונים צריכים לעמוד בדרישות הבאות:
- אפשר לתאר אותם ישירות ב-HIDL (באמצעות structs, enums וכו') עם שמות ומשמעות סמנטיים.
- ניתן לתאר אותם באמצעות תקן ציבורי כמו ISO/IEC 7816.
- אפשר לתאר אותו באמצעות תקן חומרה או פריסה פיזית של חומרה.
- אם יש צורך, יכולים להיות אלה נתונים אטומים (כמו מפתחות ציבוריים, מזהים וכו').
אם משתמשים בנתונים אטומים, הם צריכים להיקרא רק על ידי צד אחד של ממשק HIDL. לדוגמה, אם קוד vendor.img
מעביר לרכיב ב-system.img
הודעת מחרוזת או נתוני vec<uint8_t>
, הרכיב ב-system.img
לא יכול לנתח את הנתונים האלה. אפשר רק להעביר אותם חזרה ל-vendor.img
לצורך פרשנות. כשמעבירים ערך מ-vendor.img
לקוד הספק ב-system.img
או למכשיר אחר, צריך לתאר במדויק את הפורמט של הנתונים ואת אופן הפענוח שלהם, והם עדיין חלק מהממשק.
הנחיות
אתם אמורים להיות מסוגלים לכתוב הטמעה או לקוח של HAL באמצעות קובצי ה-hal. בלבד (כלומר, לא צריך לעיין במקורות של Android או בתקנים הציבוריים). מומלץ לציין את ההתנהגות הנדרשת בדיוק. הצהרות כמו "הטמעה עשויה לבצע פעולה א' או ב'" מעודדות הטמעות להשתלב עם הלקוחות שבהם הן מפותחות.
פריסת הקוד של HIDL
HIDL כולל חבילות ליבה וחבילות של ספקים.
ממשקי הליבה של HIDL הם אלה שצוינו על ידי Google. השמות של החבילות שהן שייכות אליהן מתחילים ב-android.hardware.
, והן נקראות לפי מערכת המשנה, עם אפשרות לשימוש ברמות עץ של שמות. לדוגמה, שם החבילה של ה-NFC הוא android.hardware.nfc
ושם החבילה של המצלמה הוא android.hardware.camera
. באופן כללי, לחבילת הליבה יש את השם android.hardware.
[name1
].[name2
]…. לחבילות HIDL יש גרסת build בנוסף לשם שלהן. לדוגמה, יכול להיות שהגרסה של החבילה android.hardware.camera
היא 3.4
. זה חשוב כי גרסת החבילה משפיעה על המיקום שלה בעץ המקור.
כל חבילות הליבה ממוקמות ב-hardware/interfaces/
במערכת ה-build. החבילה android.hardware.
[name1
].[name2
]… בגרסה $m.$n
נמצאת בתיקייה hardware/interfaces/name1/name2/
…/$m.$n/
. החבילה android.hardware.camera
בגרסה 3.4
נמצאת בתיקייה hardware/interfaces/camera/3.4/.
. יש מיפוי מקודד בין הקידומת של החבילה android.hardware.
לבין הנתיב hardware/interfaces/
.
חבילות שאינן ליבה (של ספק) הן חבילות שספק ה-SoC או ה-ODM ייצר. הקידומת של חבילות שאינן ליבה היא vendor.$(VENDOR).hardware.
, כאשר $(VENDOR)
מתייחס לספק SoC או ל-OEM/ODM. המיפוי הזה מתבצע לנתיב vendor/$(VENDOR)/interfaces
בעץ (גם המיפוי הזה מקודד מראש).
שמות מוגדרים במלואו של טיפוסים מוגדרים על ידי משתמשים
ב-HIDL, לכל UDT יש שם מלא שמורכב משם ה-UDT, שם החבילה שבה ה-UDT מוגדר וגרסה של החבילה. השם המלא משמש רק כשמגדירים את המופעים של הסוג, ולא כשמגדירים את הסוג עצמו. לדוגמה, נניח שחבילת android.hardware.nfc,
בגרסה 1.0
מגדירה struct בשם NfcData
. באתר ההצהרה (ב-types.hal
או בהצהרה של ממשק), ההצהרה פשוט כוללת את הפרטים הבאים:
struct NfcData { vec<uint8_t> data; };
כשמגדירים מופע מהסוג הזה (בתוך מבנה נתונים או כפרמטר של שיטה), צריך להשתמש בשם הסוג המלא:
android.hardware.nfc@1.0::NfcData
התחביר הכללי הוא PACKAGE@VERSION::UDT
, כאשר:
PACKAGE
הוא השם של חבילת HIDL שמופרד בנקודות (למשל,android.hardware.nfc
).VERSION
הוא הפורמט של גרסת החבילה, בפורמט major.minor-version שמופרד בנקודות (למשל,1.0
).UDT
הוא השם של HIDL UDT, שמופרד בנקודות. מאחר ש-HIDL תומך ב-UDTs בתצוגת עץ וממשקי HIDL יכולים להכיל UDTs (סוג של הצהרה בתצוגת עץ), נקודות משמשות לגישה לשמות.
לדוגמה, אם ההצהרה ההטמעתית הבאה הוגדרה בקובץ הסוגים הנפוצים בחבילה android.hardware.example
בגרסה 1.0
:
// types.hal package android.hardware.example@1.0; struct Foo { struct Bar { // … }; Bar cheers; };
השם המלא של Bar
הוא android.hardware.example@1.0::Foo.Bar
. אם ההצהרה בתצוגת עץ נמצאת גם בחבילה שלמעלה וגם בממשק שנקרא IQuux
:
// IQuux.hal package android.hardware.example@1.0; interface IQuux { struct Foo { struct Bar { // … }; Bar cheers; }; doSomething(Foo f) generates (Foo.Bar fb); };
השם המלא של Bar
הוא android.hardware.example@1.0::IQuux.Foo.Bar
.
בשני המקרים, אפשר להתייחס ל-Bar
בתור Bar
רק במסגרת ההצהרה על Foo
. ברמת החבילה או הממשק, צריך להפנות אל Bar
דרך Foo
:Foo.Bar
, כמו בהצהרה על השיטה doSomething
למעלה. לחלופין, אפשר להצהיר על השיטה באופן מפורט יותר:
// IQuux.hal doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);
ערכי Enumeration מוגדרים במלואם
אם UDT הוא סוג enum, לכל ערך מסוג enum יש שם מלא שמתחיל בשם המלא של סוג ה-enum, ואחריו נקודתיים ואז השם של ערך ה-enum. לדוגמה, נניח שגרסת החבילה android.hardware.nfc,
1.0
מגדירה את סוג המאפיין NfcStatus
:
enum NfcStatus { STATUS_OK, STATUS_FAILED };
כשמדברים על STATUS_OK
, השם המלא הוא:
android.hardware.nfc@1.0::NfcStatus:STATUS_OK
התחביר הכללי הוא PACKAGE@VERSION::UDT:VALUE
, כאשר:
PACKAGE@VERSION::UDT
הוא אותו שם מלא ומוגדר לחלוטין של סוג הטיפוס המנוהל.VALUE
הוא שם הערך.
כללי הסקה אוטומטית
אין צורך לציין שם UDT מלא. אפשר להשמיט בשם UDT את הפרטים הבאים ללא חשש:
- החבילה, למשל
@1.0::IFoo.Type
- גם החבילה וגם הגרסה, למשל
IFoo.Type
HIDL מנסה להשלים את השם באמצעות כללי התערבות אוטומטית (מספר כלל נמוך יותר מציין עדיפות גבוהה יותר).
כלל 1
אם לא צוינו חבילה וגרסה, מתבצעת ניסיון לחיפוש שם מקומי. דוגמה:
interface Nfc { typedef string NfcErrorMessage; send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m); };
המערכת מחפשת את NfcErrorMessage
באופן מקומי, ומוצאת את typedef
שמעליו. גם המשתנה NfcData
מחפש באופן מקומי, אבל מכיוון שהוא לא מוגדר באופן מקומי, נעשה שימוש בכללים 2 ו-3. @1.0::NfcStatus
מציין גרסה, ולכן כלל 1 לא חל.
כלל 2
אם כלל 1 נכשל ורכיב מהשם המלא חסר (חבילה, גרסה או חבילה וגרסה), הרכיב יתמלא באופן אוטומטי במידע מהחבילה הנוכחית. לאחר מכן, המהדר של HIDL מחפש בקובץ הנוכחי (ובכל הייבוא) כדי למצוא את השם המלא המלא שהושלם באופן אוטומטי.
בהתאם לדוגמה שלמעלה, נניח שהצהרת ExtendedNfcData
נעשתה באותה חבילה (android.hardware.nfc
) ובאותה גרסה (1.0
) כמו NfcData
, באופן הבא:
struct ExtendedNfcData { NfcData base; // … additional members };
המהדר של HIDL ממלא את שם החבילה ואת שם הגרסה מהחבילה הנוכחית כדי ליצור את השם המלא של ה-UDT android.hardware.nfc@1.0::NfcData
. מכיוון שהשם קיים בחבילה הנוכחית (בהנחה שהיא מיובאת כראוי), הוא משמש להצהרה.
שם בחבילה הנוכחית מיובא רק אם אחד מהמצבים הבאים מתקיים:
- הוא מיובא באופן מפורש באמצעות משפט
import
. - הוא מוגדר ב-
types.hal
בחבילה הנוכחית
אותו תהליך מתבצע אם NfcData
הוגדר רק לפי מספר הגרסה:
struct ExtendedNfcData { // autofill the current package name (android.hardware.nfc) @1.0::NfcData base; // … additional members };
כלל 3
אם כלל 2 לא מוצא התאמה (ה-UDT לא מוגדר בחבילה הנוכחית), המהדר של HIDL סורק את כל החבילות המיובאות כדי למצוא התאמה.
לפי הדוגמה שלמעלה, נניח ש-ExtendedNfcData
מוצהר בגרסה 1.1
של החבילה android.hardware.nfc
, ש-1.1
מייבא את 1.0
כראוי (ראו תוספים ברמת החבילה) וההגדרה מציינת רק את שם ה-UDT:
struct ExtendedNfcData { NfcData base; // … additional members };
המהדר מחפש UDT בשם NfcData
ומוצא אחד ב-android.hardware.nfc
בגרסה 1.0
, וכתוצאה מכך נוצר UDT מלא עם הכיתוב android.hardware.nfc@1.0::NfcData
. אם נמצאת יותר מהתאמה אחת ל-UDT מסוים עם הסמכה חלקית, מתקבלת שגיאה במהלך הידור ה-HIDL.
דוגמה
לפי כלל 2, סוג שיובא מוגדר בחבילה הנוכחית מקבל עדיפות על פני סוג שיובא מחבילה אחרת:
// hardware/interfaces/foo/1.0/types.hal package android.hardware.foo@1.0; struct S {}; // hardware/interfaces/foo/1.0/IFooCallback.hal package android.hardware.foo@1.0; interface IFooCallback {}; // hardware/interfaces/bar/1.0/types.hal package android.hardware.bar@1.0; typedef string S; // hardware/interfaces/bar/1.0/IFooCallback.hal package android.hardware.bar@1.0; interface IFooCallback {}; // hardware/interfaces/bar/1.0/IBar.hal package android.hardware.bar@1.0; import android.hardware.foo@1.0; interface IBar { baz1(S s); // android.hardware.bar@1.0::S baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback };
- הערך של
S
מחושב כ-android.hardware.bar@1.0::S
, והוא נמצא ב-bar/1.0/types.hal
(כיtypes.hal
מיובאת באופן אוטומטי). - הערך
IFooCallback
מומר ל-android.hardware.bar@1.0::IFooCallback
באמצעות כלל 2, אבל אי אפשר למצוא אותו כי הערךbar/1.0/IFooCallback.hal
לא מיובא באופן אוטומטי (בניגוד ל-types.hal
). לכן, כלל 3 פותר את הערך ל-android.hardware.foo@1.0::IFooCallback
במקום זאת, שמיובא דרךimport android.hardware.foo@1.0;
).
types.hal
כל חבילת HIDL מכילה קובץ types.hal
שמכיל UDTs ששותפים בין כל הממשקים שמשתתפים בחבילה. סוגי HIDL הם תמיד ציבוריים. לא משנה אם ה-UDT מוצהר ב-types.hal
או בהצהרת ממשק, אפשר לגשת לסוגי ה-UDT מחוץ להיקף שבו הם מוגדרים. types.hal
לא מיועד לתאר את ה-API הציבורי של החבילה, אלא לארח UDTs שבהם משתמשים כל הממשקים בחבילה. בגלל אופי ה-HIDL, כל ה-UDTs הם חלק מהממשק.
types.hal
מורכב מ-UDT ומהוראות import
.
מאחר ש-types.hal
זמין לכל ממשק בחבילה (זהו ייבוא משתמע), הצהרות ה-import
האלה הן ברמת החבילה מעצם הגדרתן. טיפוסי UDT ב-types.hal
יכולים גם לכלול ממשקים וטיפוסי UDT שיובאו כך.
לדוגמה, עבור IFoo.hal
:
package android.hardware.foo@1.0; // whole package import import android.hardware.bar@1.0; // types only import import android.hardware.baz@1.0::types; // partial imports import android.hardware.qux@1.0::IQux.Quux; // partial imports import android.hardware.quuz@1.0::Quuz;
הנתונים הבאים מיובאים:
android.hidl.base@1.0::IBase
(באופן משתמע)android.hardware.foo@1.0::types
(באופן משתמע)- כל מה ששייך ל-
android.hardware.bar@1.0
(כולל כל הממשקים ו-types.hal
שלו) types.hal
מ-android.hardware.baz@1.0::types
(ממשקים ב-android.hardware.baz@1.0
לא מיובאים)IQux.hal
ו-types.hal
מ-android.hardware.qux@1.0
Quuz
מ-android.hardware.quuz@1.0
(בהנחה ש-Quuz
מוגדר ב-types.hal
, כל הקובץtypes.hal
מנותח, אבל סוגי נתונים שאינםQuuz
לא מיובאים).
ניהול גרסאות ברמת הממשק
כל ממשק בחבילה נמצא בקובץ משלו. ההצהרה על החבילה שאליה הממשק שייך מופיעה בחלק העליון של הממשק באמצעות ההצהרה package
. אחרי הצהרת החבילה, יכול להיות שיופיעו אפס או יותר ייבוא ברמת הממשק (חלקי או של החבילה כולה). לדוגמה:
package android.hardware.nfc@1.0;
ב-HIDL, ממשקים יכולים לרשת ממשקים אחרים באמצעות מילת המפתח extends
. כדי שממשק יוכל להרחיב ממשק אחר, צריכה להיות לו גישה אליו באמצעות משפט import
. השם של הממשק שרוצים להרחיב (ממשק הבסיס) צריך לעמוד בכללים למתן שמות של סוגי נתונים שמפורטים למעלה. ממשק יכול לרשת רק ממשק אחד. HIDL לא תומך בירושה מרובה.
בדוגמאות הבאות לניהול גרסאות של uprev נעשה שימוש בחבילה הבאה:
// types.hal package android.hardware.example@1.0 struct Foo { struct Bar { vec<uint32_t> val; }; }; // IQuux.hal package android.hardware.example@1.0 interface IQuux { fromFooToBar(Foo f) generates (Foo.Bar b); }
כללי Uprev
כדי להגדיר את החבילה package@major.minor
, התנאי A או כל התנאים של B חייבים להתקיים:
כלל א' | 'גרסה משנית ראשונית': אסור להגדיר את כל הגרסאות המשניות הקודמות, package@major.0 , package@major.1 , …, package@major.(minor-1) .
|
---|
כלל ב' | כל התנאים הבאים מתקיימים:
|
---|
בגלל כלל א':
- החבילה יכולה להתחיל עם כל מספר גרסה משני (לדוגמה,
android.hardware.biometrics.fingerprint
מתחיל ב-@2.1
). - הדרישה '
android.hardware.foo@1.0
לא מוגדרת' פירושה שספרייתhardware/interfaces/foo/1.0
לא אמורה להתקיים בכלל.
עם זאת, כלל א' לא משפיע על חבילה עם אותו שם חבילה אבל עם גרסה ראשית שונה (לדוגמה, ב-android.hardware.camera.device
מוגדרים גם @1.0
וגם @3.2
, ו-@3.2
לא צריך לקיים אינטראקציה עם @1.0
). לכן, @3.2::IExtFoo
יכול להרחיב את @1.0::IFoo
.
אם שם החבילה שונה, אפשר להרחיב את package@major.minor::IBar
מממשק עם שם שונה (לדוגמה, android.hardware.bar@1.0::IBar
יכול להרחיב את android.hardware.baz@2.2::IBaz
). אם ממשק לא מצהיר במפורש על סופר-סוג באמצעות מפתח המילה extend
, הוא מרחיב את android.hidl.base@1.0::IBase
(למעט IBase
עצמו).
צריך לפעול לפי השלבים ב-B.2 וב-B.3 בו-זמנית. לדוגמה, גם אם android.hardware.foo@1.1::IFoo
הוא תת-מסנן של android.hardware.foo@1.0::IFoo
שעובר את כלל B.2, אם android.hardware.foo@1.1::IExtBar
הוא תת-מסנן של android.hardware.foo@1.0::IBar
, עדיין לא מדובר בגרסה משופרת תקפה.
ממשקי Uprev
כדי לשדרג את android.hardware.example@1.0
(שצוין למעלה) ל-@1.1
:
// types.hal package android.hardware.example@1.1; import android.hardware.example@1.0; // IQuux.hal package android.hardware.example@1.1 interface IQuux extends @1.0::IQuux { fromBarToFoo(Foo.Bar b) generates (Foo f); }
זהו import
ברמת החבילה של גרסת 1.0
של android.hardware.example
ב-types.hal
. אמנם לא נוספו UDT חדשים בגרסה 1.1
של החבילה, אבל עדיין יש צורך בהפניות ל-UDT בגרסה 1.0
, ולכן מתבצע ייבוא ברמת החבילה ב-types.hal
. (אפשר היה להשיג את אותו אפקט באמצעות ייבוא ברמת הממשק ב-IQuux.hal
).
ב-extends @1.0::IQuux
בהצהרה של IQuux
, צייינו את הגרסה של IQuux
שעוברת בירושה (נדרשת הסבר מפורט כי IQuux
משמש להצהרה על ממשק ולירושה ממשק). מאחר שהצהרות הן פשוט שמות שעוברים בירושה את כל מאפייני החבילה והגרסה באתר ההצהרה, הסרת הספק האפשרי חייבת להופיע בשם של הממשק הבסיסי. אפשר היה להשתמש גם ב-UDT המלא, אבל זה היה מיותר.
הממשק החדש IQuux
לא מכריז מחדש על השיטה fromFooToBar()
שעוברת בירושה מ-@1.0::IQuux
, אלא פשוט מציג את השיטה החדשה שהוא מוסיף fromBarToFoo()
. ב-HIDL, אי אפשר להצהיר שוב על שיטות שעברו בירושה בממשקי הצאצאים, ולכן הממשק IQuux
לא יכול להצהיר על השיטה fromFooToBar()
באופן מפורש.
מוסכמות של Uprev
לפעמים צריך לשנות את השם של ממשק ההרחבה בשמות של ממשקים. מומלץ שאותו שם יהיה למבנים, לממשקי ה-union ולתוספים של enum, כמו לשמות של הרכיבים שהם מוסיפים, אלא אם יש הבדל משמעותי שמצדיק שם חדש. לדוגמה:
// in parent hal file enum Brightness : uint32_t { NONE, WHITE }; // in child hal file extending the existing set with additional similar values enum Brightness : @1.0::Brightness { AUTOMATIC }; // extending the existing set with values that require a new, more descriptive name: enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };
אם אפשר לתת לשיטה שם סמנטי חדש (למשל fooWithLocation
), זה עדיף. אחרת, השם צריך להיות דומה לשם של הספרייה שאליה הוא מרחיב. לדוגמה, השיטה foo_1_1
ב-@1.1::IFoo
יכולה להחליף את הפונקציונליות של השיטה foo
ב-@1.0::IFoo
אם אין שם חלופי טוב יותר.
ניהול גרסאות ברמת החבילה
ניהול הגרסאות של HIDL מתבצע ברמת החבילה. אחרי שחבילה מתפרסמת, אי אפשר לשנות אותה (אי אפשר לשנות את קבוצת הממשקים וה-UDTs שלה). יש כמה דרכים שבהן חבילות יכולות להיות קשורות זו לזו, ואפשר לבטא את כולן באמצעות שילוב של ירושה ברמת הממשק וליצור UDTs על ידי יצירה.
עם זאת, יש סוג אחד של יחסים שמוגדר באופן קפדני וצריך לאכוף אותו: ירושה עם תאימות לאחור ברמת החבילה. בתרחיש הזה, החבילה parent היא החבילה שממנה עוברת הירושה, והחבילה child היא החבילה שמרחיבה את החבילה ההורה. כללי הירושה ברמת החבילה שתואמים לאחור הם:
- כל ממשקי הרמה העליונה של החבילה ההורה עוברים בירושה לממשקים בחבילת הצאצא.
- אפשר גם להוסיף ממשקים חדשים לחבילה החדשה (אין הגבלות על קשרים לממשקים אחרים בחבילות אחרות).
- אפשר גם להוסיף סוגי נתונים חדשים לשימוש בשיטות חדשות של ממשקים קיימים ששודרגו, או בממשקים חדשים.
אפשר להטמיע את הכללים האלה באמצעות ירושה ברמת הממשק של HIDL ותזמור של UDT, אבל צריך ידע ברמת המטא כדי לדעת שהיחסים האלה מהווים תוסף לחבילה שתואם לאחור. המערכת מסיקה את הידע הזה באופן הבא:
אם החבילה עומדת בדרישות האלה, hidl-gen
אוכף כללי תאימות לאחור.