ניהול גרסאות בממשק

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

מבנה קוד HIDL

קוד HIDL מאורגן בהגדרת משתמש סוגים, ממשקים וחבילות:

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

קובץ ההגדרה של סוג הנתונים types.hal מכיל רק פונקציות UDT (הכול רכיבי UDT ברמת החבילה נשמרים בקובץ יחיד). ייצוגים ביעד זמינים לכל הממשקים בחבילה.

פילוסופיה של גרסאות

חבילת HIDL (כמו android.hardware.nfc), לאחר פורסם עבור גרסה נתונה (כמו 1.0), לא ניתן לשינוי. זה ולא ניתן לשנות אותו. שינויים בממשקים שבחבילה או ששינויים ב-UDT שלו יכולים להתרחש רק בחבילה אחרת.

ב-HIDL, ניהול הגרסאות חל ברמת החבילה, לא ברמת הממשק. וכל הממשקים ורכיבי ה-UDT בחבילה משתמשים באותה גרסה. Package (חבילה) גרסאות תואמות סמנטי ניהול גרסאות ללא רמת התיקון ורכיבי המטא-נתונים של ה-build. בטווח של חבילה נתונה, שינוי בגרסה משנית מרמז על הגרסה החדשה של החבילה תואמת לאחור עם החבילה הישנה וגדולה הפניה לגרסה מרמזת על כך שהגרסה החדשה של החבילה לא תואם לאחור עם החבילה הישנה.

מבחינה רעיונית, חבילה יכולה להיות קשורה לחבילה אחרת באחת מכמה דרכים:

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

מבנה הממשק

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

טרבל תומך ברכיבי מערכת וספק שעברו הידור בנפרד, שבהם vendor.img במכשיר, וה-system.img יכול להיות עוברים הידור בנפרד. כל האינטראקציות בין vendor.img לבין צריך להגדיר את system.img באופן מפורש וביסודיות כדי שניתן יהיה ימשיכו לפעול במשך שנים רבות. זה כולל פלטפורמות רבות של API, אבל הוא מנגנון ה-IPC שבו משתמש HIDL לתקשורת בין תהליכים גבול system.img/vendor.img.

הדרישות

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

  • אפשר לתאר אותו ישירות ב-HIDL (באמצעות Builds 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 יש גרסה בנוסף לשם שלהן. לדוגמה, החבילה ייתכן ש-android.hardware.camera הוא בגרסה 3.4; זה כי גרסת החבילה משפיעה על המיקום שלה בעץ המקור.

כל חבילות הליבה ממוקמות תחת hardware/interfaces/ בשדה של מערכת ה-build. החבילה android.hardware.[name1].[name2]... בגרסה $m.$n זה פחות מ- hardware/interfaces/name1/name2/.../$m.$n/; חבילה גרסה 3.4 של android.hardware.camera נמצאת בספרייה 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 מגדירה מבנה בשם NfcData. באתר ההצהרה (בין אם types.hal או בהצהרה של הממשק), ההצהרה בפשטות:

struct NfcData {
    vec<uint8_t> data;
};

בהצהרה על מופע מהסוג הזה (בין אם בתוך מבנה נתונים או כפרמטר method), יש להשתמש בשם הסוג המוגדר במלואו:

android.hardware.nfc@1.0::NfcData

התחביר הכללי הוא PACKAGE@VERSION::UDT, כאשר:

  • PACKAGE הוא השם של חבילת HIDL, מופרדת באמצעות נקודות (למשל android.hardware.nfc).
  • VERSION היא גרסת ה-Major.minor-version שמופרדת באמצעות נקודות פורמט החבילה (למשל, 1.0).
  • UDT הוא השם של HIDL UDT, שמופרד באמצעות נקודות. מכיוון ש-HIDL תומך ברכיבי UDT מקוננים ובממשקי HIDL יכולים להכיל רכיבי UDT (סוג הצהרה בתוך הצהרה), נקודות משמשות לגישה לשמות.

לדוגמה, אם ההצהרה הפנימית הבאה הוגדרה קובץ סוגים בחבילה 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);

ערכי ספירה מוגדרים במלואם

אם UDT הוא מסוג enum, לכל ערך מסוג enum יש שם מוגדר במלואו שמתחיל בשם המוגדר במלואו מסוג 'טיפוסים בני מנייה (enum)', ואחריו תו נקודתיים ואחריו השם של ערך ה-enum. לדוגמה, מניחים את חבילה android.hardware.nfc, גרסה 1.0 מגדיר סוג enum NfcStatus:

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

כאשר מדובר ב-STATUS_OK, השם המוגדר במלואו הוא:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

התחביר הכללי הוא PACKAGE@VERSION::UDT:VALUE, איפה:

  • PACKAGE@VERSION::UDT הוא/היא בדיוק אותו שם מלא שמוגדר לסוג 'טיפוסים בני מנייה (enum)'.
  • 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 לא מוגדר ), המהדר (compiler) 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;).

segments.hal

כל חבילה של HIDL מכילה קובץ types.hal שמכיל UDTs שמשותפים בין כל הממשקים שמשתתפים בחבילה הזו. סוגי HIDL הם תמיד ציבוריים. בין אם הוצהר על UDT types.hal או בתוך הצהרה בממשק, הסוגים האלה נגישים מחוץ להיקף שבו הם מוגדרים. types.hal לא מיועד לתאר את ה-API הציבורי של חבילה, אלא לארח רכיבי UDT משמש את כל הממשקים בתוך החבילה. בשל האופי של HIDL, כל רכיבי ה-UDT הן חלק מהממשק.

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 לא תומך במספר ירושה.

הדוגמאות הבאות לניהול גרסאות משתמשות בחבילה הבאה:

// 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, א' או את כל B חייב להיות True:

כלל א' "זו גרסת התחלה משנית": כל הגרסאות המשניות הקודמות, package@major.0, package@major.1, ..., אין להגדיר package@major.(minor-1).
או
כלל ב'

כל התנאים הבאים נכונים:

  1. "הגרסה המשנית הקודמת תקינה": package@major.(minor-1) חייבים להיות מוגדרים ולפעול לפי אותו כלל א' (אף אחד package@major.0 עד package@major.(minor-2) מוגדרים) או כלל ב' (אם הוא גבוה מ-@major.(minor-2));

    וגם

  2. "קבלה בירושה של ממשק אחד לפחות עם אותו שם": קיים ממשק package@major.minor::IFoo שאורכו package@major.(minor-1)::IFoo (אם לחבילה הקודמת יש ממשק);

    וגם

  3. "אין ממשק שעבר בירושה עם שם אחר": אסור package@major.minor::IBar למשך זמן ארוך יותר package@major.(minor-1)::IBaz, כאשר IBar וגם IBaz הם שני שמות שונים. אם יש ממשק עם אותו שם, package@major.minor::IBar חייבת להאריך package@major.(minor-k)::IBar כך שלא קיים IBar עם k.

בגלל כלל א':

  • החבילה יכולה להתחיל בכל מספר גרסה משני (לדוגמה, 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 עצמו).

ב.2 וגם ב.3 חייבים לפעול בו-זמנית. לדוגמה, גם אם android.hardware.foo@1.1::IFoo הרחבה android.hardware.foo@1.0::IFoo כדי להעביר את כלל ב'.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 של החבילה, הפניות ל-UDTs יש עדיין צורך בגרסה 1.0, ולכן הייבוא ברמת החבילה ב-types.hal. (ניתן היה לקבל את אותו אפקט באמצעות ייבוא ברמת הממשק ב-IQuux.hal).

ב-extends @1.0::IQuux בהצהרה של IQuux, ציינת את הגרסה של IQuux עברה בירושה (נדרשת הבחנה כי IQuux משמש להצהיר על ממשק ולקבל בירושה מממשק). מאחר שההצהרות שמות שיורשים את כל מאפייני החבילות והגרסה באתר אחרת, ההבחנה חייבת להיות בשם של ממשק הבסיס. אנחנו היה צריך להשתמש גם ב-UDT מלא, אבל זה היה מיותרת.

הממשק החדש IQuux לא מצהיר מחדש על השיטה fromFooToBar() שהיא מקבלת בירושה מ@1.0::IQuux; פשוט מציין את השיטה החדשה ומוסיף אותה fromBarToFoo(). ב-HIDL, מתבצעת בירושה אי אפשר להצהיר שוב על methods בממשקי הצאצא, לכן הממשק IQuux לא יכול להצהיר על fromFooToBar() במפורש.

מוסכמות בעלייה

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

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

  1. כל הממשקים ברמה העליונה של חבילת ההורה עוברים בירושה מהממשקים חבילת צאצא.
  2. ניתן גם להוסיף ממשקים חדשים את החבילה החדשה (אין הגבלות על קשרים בממשקים אחרים בחבילות אחרות).
  3. ניתן גם להוסיף סוגי נתונים חדשים לשימוש באחת מהשיטות החדשות להעלאה או בממשקים חדשים.

ניתן להטמיע את הכללים האלה באמצעות ירושה ברמת הממשק HIDL ו-UDT אבל נדרש ידע ברמת המטא כדי להכיר את הקשרים להגדיר תוסף חבילה שתואם לאחור. את הידע הזה מסיקים ככה:

אם חבילה עומדת בדרישה הזו, המדיניות hidl-gen אוכפת כללי תאימות לאחור.