צריך להשתמש ב-HIDL כדי לתאר את כל דגלי ה-build שמשמשים להרכבת ה-framework באופן מותנה. דגלי build רלוונטיים צריכים להיות מקובצים ולכלול בקובץ .hal
יחיד. השימוש ב-HIDL לציון פריטי תצורה כולל את היתרונות הבאים:
- גרסאות (כדי להוסיף פריטי הגדרה חדשים, ספקים/יצרני ציוד מקורי חייבים להרחיב באופן מפורש את HAL)
- מתועד היטב
- בקרת גישה באמצעות SELinux
- בדיקת שאיפה לפריטי תצורה באמצעות חבילת הבדיקה של הספק (בדיקת טווח, בדיקת תלות הדדית בין פריטים וכו')
- ממשקי API שנוצרים באופן אוטומטי ב-C++ וב-Java
זיהוי הדגלים של build שמשמשים את ה-framework
מתחילים בזיהוי הגדרות ה-build שמשמשות להדרת ה-framework באופן מותנה, ואז נוטשים את ההגדרות המיושנות כדי להקטין את הקבוצה. לדוגמה, הקבוצה הבאה של דגלי build מזוהה בשביל surfaceflinger
:
TARGET_USES_HWC2
TARGET_BOARD_PLATFORM
TARGET_DISABLE_TRIPLE_BUFFERING
TARGET_FORCE_HWC_FOR_VIRTUAL_DISPLAYS
NUM_FRAMEBUFFER_SURFACE_BUFFERS
TARGET_RUNNING_WITHOUT_SYNC_FRAMEWORK
VSYNC_EVENT_PHASE_OFFSET_NS
SF_VSYNC_EVENT_PHASE_OFFSET_NS
PRESENT_TIME_OFFSET_FROM_VSYNC_NS
MAX_VIRTUAL_DISPLAY_DIMENSION
יצירת ממשק HAL
ההגדרות של build למערכת משנה ניגשים דרך ממשק HAL, והממשקים למתן ערכי תצורה מקובצים בחבילת ה-HAL android.hardware.configstore
(כרגע בגרסה 1.0).
לדוגמה, כדי ליצור קובץ ממשק HAL ל-surfaceflinger
ב-hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal
:
package android.hardware.configstore@1.0; interface ISurfaceFlingerConfigs { // TO-BE-FILLED-BELOW };
אחרי יצירת הקובץ .hal
, מריצים את הפקודה hardware/interfaces/update-makefiles.sh
כדי להוסיף את הקובץ .hal
החדש לקבצים Android.bp
ו-Android.mk
.
הוספת פונקציות ל-build דגלים
לכל דגל build, מוסיפים פונקציה חדשה לממשק. לדוגמה, ב-hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal
:
interface ISurfaceFlingerConfigs { disableTripleBuffering() generates(OptionalBool ret); forceHwcForVirtualDisplays() generates(OptionalBool ret); enum NumBuffers: uint8_t { USE_DEFAULT = 0, TWO = 2, THREE = 3, }; numFramebufferSurfaceBuffers() generates(NumBuffers ret); runWithoutSyncFramework() generates(OptionalBool ret); vsyncEventPhaseOffsetNs generates (OptionalUInt64 ret); presentTimeOffsetFromSyncNs generates (OptionalUInt64 ret); maxVirtualDisplayDimension() generates(OptionalInt32 ret); };
כשמוסיפים פונקציה:
- שמות תמציתיים. לא כדאי להמיר שמות של משתני קובץ לשמות של פונקציות, ולזכור שהקידומות
TARGET_
ו-BOARD_
כבר לא נחוצות. - הוספת תגובות. חשוב לעזור למפתחים להבין את המטרה של פריט התצורה, איך הוא משנה את ההתנהגות של המסגרת, את הערכים התקינים ומידע רלוונטי נוסף.
הסוגים של החזרת הפונקציות יכולים להיות Optional[Bool|String|Int32|UInt32|Int64|UInt64]
. הסוגים מוגדרים ב-types.hal
באותה ספרייה ועוטפים ערכים ראשוניים בשדה שמציין אם הערך צוין על ידי HAL. אחרת, נעשה שימוש בערך ברירת המחדל.
struct OptionalString { bool specified; string value; };
במקרים המתאימים, מגדירים את ה-enum שמייצג בצורה הטובה ביותר את סוג פריט ההגדרה, ומשתמשים ב-enum הזה כסוג ההחזרה. בדוגמה שלמעלה, enum NumBuffers
מוגדר כך שמגביל את מספר הערכים התקינים. כשמגדירים סוגי נתונים בהתאמה אישית כאלה, צריך להוסיף שדה או ערך enum (לדוגמה, USE_DEFAULT
) כדי לציין אם הערך לא צוין על ידי HAL.
לא חובה שדגל אחד של build יהיה פונקציה יחידה ב-HIDL. לחלופין, בעלי מודולים יכולים לקבץ יחד דגלי build שיש להם קשר הדוק למבנה, ולקבל פונקציה שמחזירה את המבנה הזה (כך אפשר לצמצם את מספר הקריאות לפונקציות).
לדוגמה, אפשרות לצבירת שני דגלי build למבנה יחיד ב-hardware/interfaces/configstore/1.0/ISurfaceFlingerConfigs.hal
היא:
interface ISurfaceFlingerConfigs { // other functions here struct SyncConfigs { OptionalInt64 vsyncEventPhaseoffsetNs; OptionalInt64 presentTimeoffsetFromSyncNs; }; getSyncConfigs() generates (SyncConfigs ret); // other functions here };
חלופות לפונקציית HAL יחידה
כחלופה לשימוש בפונקציית HAL יחידה לכל דגלי ה-build, ממשק HAL מספק גם פונקציות פשוטות כמו getBoolean(string
key)
ו-getInteger(string key)
. הצמדים עצמם של key=value
מאוחסנים בקבצים נפרדים, ושירות HAL מספק ערכים על ידי קריאה/ניתוח של הקבצים האלו.
אומנם קל להגדיר את הגישה הזו, אבל היא לא כוללת את היתרונות של HIDL (אכיפת גרסאות, קלות התיעוד ובקרת גישה), ולכן לא מומלץ להשתמש בה.
ממשק יחיד או מספר ממשקים
העיצוב של ממשק HAL לפריטי תצורה מציג שתי אפשרויות:
- ממשק אחד שמכסה את כל פריטי התצורה
- מספר ממשקים, שכל אחד מהם כולל קבוצה של פריטי תצורה קשורים
קל יותר להשתמש בממשק אחד, אבל קשה יותר לתחזק אותו כי נוספים יותר פריטי תצורה לאותו קובץ. בנוסף, בקרת הגישה לא פרטנית, ולכן בתהליך עם גישה לממשק יש אפשרות לקרוא את כל פריטי ההגדרות (אי אפשר לתת גישה לקבוצה חלקית של פריטי תצורה). לחלופין, אם לא מעניקים גישה, אי אפשר לקרוא את פריטי ההגדרות.
בגלל הבעיות האלה, מערכת Android משתמשת בכמה ממשקים עם ממשק HAL אחד לקבוצה של פריטי תצורה קשורים. לדוגמה, ISurfaceflingerConfigs
לפריטי הגדרה שקשורים ל-surfaceflinger
ו-IBluetoothConfigs
לפריטי הגדרות שקשורים ל-Bluetooth.