ב-Android 11 הוספנו את היכולת להשתמש ב-AIDL לנכסי HAL ב-Android. ככה ניתן ליישם חלקים של Android ללא HIDL. העברת HALs לשימוש ב-AIDL אך ורק כאשר הדבר אפשרי (כאשר קצבי HAL ב-upstream משתמשים ב-HIDL, יש להשתמש ב-HIDL).
תהליכי HAL שמשתמשים ב-AIDL כדי לתקשר בין רכיבי framework, כמו אלה
הרכיב system.img
ורכיבי חומרה, כמו אלה שב-vendor.img
, חייבים לפעול
AIDL יציב. עם זאת, כדי לתקשר בתוך מחיצה, לדוגמה,
אין הגבלה על מנגנון ה-IPC שבו ניתן להשתמש עם HAL.
מוטיבציה
טכנולוגיית AIDL קיימת במשך יותר זמן מ-HIDL, ומשמשת במקומות רבים נוספים, בין רכיבי Android framework או באפליקציות. עכשיו ל-AIDL יש יציבות תמיכה, אפשר ליישם סטאק שלם עם סביבת זמן ריצה אחת של IPC. ב-AIDL יש גם מערכת ניהול גרסאות טובה יותר מ-HIDL.
- כשמשתמשים בשפת IPC אחת, צריך ללמוד רק דבר אחד, לנפות באגים לבצע אופטימיזציה ומאובטחת.
- AIDL תומך בניהול גרסאות באופן מקומי לבעלים של ממשק:
- הבעלים יכולים להוסיף שיטות לסוף הממשקים, או שדות לחבילות. כלומר, קל יותר ליצור גרסאות של גרסאות לאורך השנים, וגם העלות לשנה נמוכה יותר (ניתן לתקן את סוגי הסוגים באופן ידני, אם תצטרכו ספריות נוספות לכל גרסת ממשק).
- אפשר לצרף ממשקי תוספים בזמן ריצה במקום בסוג מסוים המערכת, כך שאין צורך להעביר מחדש תוספים במורד הזרם אל גרסאות חדשות יותר גרסאות של ממשקים.
- אפשר להשתמש בממשק AIDL קיים באופן ישיר כאשר הבעלים של הממשק בוחרים לייצב אותו. לפני כן, עותק שלם של הממשק היה צריך להיות נוצר ב-HIDL.
פיתוח בהתאם לסביבת זמן הריצה של AIDL
ל-AIDL יש שלושה קצוות עורפיים שונים: Java, NDK ו-CPP. כדי להשתמש ב-Stable AIDL, צריך
להשתמש תמיד בעותק המערכת של Libbinder ב-system/lib*/libbinder.so
ולדבר
בתאריך /dev/binder
. כשמדובר בקוד בתמונת הספק, המשמעות היא ש-libbinder
(מ-VNDK) לא ניתן להשתמש: בספרייה הזו יש ממשק API לא יציב של C++
פנימיים ולא יציבים. במקום זאת, קוד הספק המקורי חייב להשתמש בקצה העורפי NDK של
AIDL, קישור אל libbinder_ndk
(שמגובה על ידי המערכת libbinder.so
),
וקישור לספריות ה-NDK שנוצרו על ידי רשומות aidl_interface
. עבור
את שמות המודולים המדויקים,
כללים למתן שמות למודולים.
כתיבת ממשק AIDL HAL
כדי להשתמש בממשק AIDL בין המערכת לספק, הממשק צריך שני שינויים:
- לכל הגדרה של סוג צריך להוסיף הערה
@VintfStability
. - ההצהרה
aidl_interface
צריכה לכלולstability: "vintf",
.
רק הבעלים של הממשק יכול לבצע את השינויים האלה.
כשאתם מבצעים את השינויים האלה, הממשק חייב להיות
מניפסט VINTF כדי לפעול. בדיקה זו (ושאלות קשורות
כמו אימות שהממשקים המשוחררים הוקפאו) באמצעות
בדיקת VTS vts_treble_vintf_vendor_test
. אפשר להשתמש ב-@VintfStability
ללא הדרישות האלו, על ידי קריאה
AIBinder_forceDowngradeToLocalStability
בקצה העורפי של NDK,
android::Stability::forceDowngradeToLocalStability
בקצה העורפי של C++,
או android.os.Binder#forceDowngradeToSystemStability
בקצה העורפי של Java
באובייקט של קלסר לפני שהוא נשלח לתהליך אחר. שדרוג לאחור של שירות
ליציבות הספק לא נתמכת ב-Java מפני שכל האפליקציות פועלות במערכת
הקשר מסוים.
בנוסף, כדי לאפשר ניידות מקסימלית של הקוד וכדי להימנע מבעיות אפשריות, בתור ספריות נוספות לא נחוצות, משביתים את הקצה העורפי של ה-CPP.
שימו לב שהשימוש ב-backends
בקוד לדוגמה שבהמשך הוא נכון, כי יש
הם שלושה קצוות עורפיים (Java, NDK ו-CPP). הקוד שבהמשך מסביר איך לבחור
קצה עורפי של CPP ספציפי, כדי להשבית אותו.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
חיפוש ממשקי AIDL HAL
ממשקי AOSP Stable AIDL ל-HAL נמצאים באותן ספריות בסיס כמו
ממשקי HIDL, ב-aidl
תיקיות.
- חומרה/ממשקים
- frameworks/חומרה/ממשקים
- מערכת/חומרה/ממשקים
צריך להכניס ממשקי תוספים אל hardware/interfaces
אחר
ספריות משנה ב-vendor
או ב-hardware
.
ממשקי תוספים
ב-Android יש סדרה של ממשקי AOSP רשמיים בכל גרסה. מתי ב-Android שהשותפים רוצים להוסיף פונקציונליות לממשקים האלה, הם לא אמורים לשנות ישירות, כי המשמעות היא שזמן הריצה של Android לא תואם לסביבת זמן הריצה של Android ב-AOSP. עבור מכשירי GMS, נמנעים משינויים הוא גם מה שמבטיח שהתמונה של GSI יכולה להמשיך לפעול.
תוספים יכולים להירשם בשתי דרכים שונות:
- בזמן הריצה, ראו תוספים מצורפים.
- בנפרד, רשום בכל העולם וב-VINTF.
עם זאת, הרחבה רשומה כאשר היא ספציפית לספק (כלומר אינה חלק מ- ב-upstream AOSP) משתמשים בממשק, אין אפשרות למיזוג שיש התנגשות. עם זאת, כששינויים ב-downstream ברכיבי AOSP ב-upstream עלולות להתרחש התנגשויות במיזוג, ומומלץ להשתמש באסטרטגיות הבאות:
- ניתן להעלות את השידור של התוספות לממשק ל-AOSP בגרסה הבאה
- והוספות לממשקים שמאפשרות גמישות רבה יותר, ללא התנגשויות בלו"ז, תהיה זמינה ב-upstream בגרסה הבאה.
מתקנים לתוספים: ParcelableHolder
ParcelableHolder
הוא Parcelable
שיכול להכיל עוד Parcelable
.
התרחיש לדוגמה העיקרי של ParcelableHolder
הוא ליצור Parcelable
שניתן להרחיב.
לדוגמה, תמונה שמטמיעי מכשירים מצפים שתהיה להם אפשרות להרחיב
Parcelable
, AospDefinedParcelable
שמוגדר על ידי AOSP, כדי לכלול ערך מוסף
לבינה מלאכותית גנרטיבית.
בעבר בלי ParcelableHolder
, מטמיעי מכשירים לא יכלו לשנות
ממשק AIDL יציב שמוגדר על ידי AOSP, כי זו תהיה שגיאה להוסיף עוד ממשקים
שדות:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
כמו שאפשר לראות בקוד הקודם, הנוהל הזה לא תקין כי השדות נוסף על ידי הטמעה של המכשיר עלול להיות התנגשות עם מצב החבילה ובגרסאות הבאות של Android.
באמצעות ParcelableHolder
, הבעלים של חבילה יכולים להגדיר תוסף
ב-Parcelable
.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
לאחר מכן מכשירי הטמעה של המכשירים יוכלו להגדיר Parcelable
משלהם
לתוסף.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
לסיום, ניתן לצרף את Parcelable
החדש אל Parcelable
המקורי באמצעות
השדה ParcelableHolder
.
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
שמות מכונות של שרתי AIDL HAL
לפי המוסכמה, לשירותי AIDL HAL יש שם של מכונה בפורמט הזה
$package.$type/$instance
לדוגמה, דוגמה של הרטט HAL
רשום בתור android.hardware.vibrator.IVibrator/default
.
כתיבה של שרת AIDL HAL
צריך להצהיר על @VintfStability
שרתי AIDL במניפסט של VINTF, בשביל
לדוגמה:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
אחרת, הם צריכים לרשום שירות AIDL כרגיל. כשמפעילים VTS כל תכונות ה-AIDL HAL המוצהרות צפויות להיות זמינות.
כתיבה של לקוח AIDL
לקוחות AIDL חייבים להצהיר על עצמם במטריצת התאימות, לדוגמה, כך:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
המרה של HAL קיים מ-HIDL ל-AIDL
משתמשים בכלי hidl2aidl
כדי להמיר ממשק HIDL ל-AIDL.
התכונות של hidl2aidl
:
- יצירת קובצי
.aidl
על סמך קובצי ה-.hal
של החבילה הנתונה - יצירת כללי build לחבילת AIDL החדשה שנוצרה עם כל הקצוות העורפיים מופעל
- ליצור שיטות תרגום בקצה העורפי Java, CPP ו-NDK לתרגום מסוגים של HIDL לסוגי AIDL
- יצירת כללי build לתרגום של ספריות עם יחסי תלות נדרשים
- יוצרים הצהרות סטטיות כדי לוודא שלמספרי HIDL ו-AIDL זהים בקצוות העורפיים של CPP ו-NDK
כדי להמיר חבילה של קובצי .hal לקובצי .aidl, פועלים לפי השלבים הבאים:
בניית הכלי נמצאת ב-
system/tools/hidl/hidl2aidl
.פיתוח הכלי הזה מהמקור העדכני ביותר מספק חוויה אישית. ניתן להשתמש בגרסה העדכנית ביותר כדי להמיר ממשקים במכשירים ישנים יותר להסתעפויות מהגרסאות הקודמות.
m hidl2aidl
מריצים את הכלי עם ספריית פלט ואחריה החבילה שרוצים שהומר.
אפשר להשתמש בארגומנט
-l
כדי להוסיף את התוכן של קובץ רישיון חדש לחלק העליון של כל הקבצים שנוצרו. צריך להקפיד להשתמש ברישיון ובתאריך הנכונים.hidl2aidl -o <output directory> -l <file with license> <package>
לדוגמה:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
קוראים את הקבצים שנוצרו ומתקנים בעיות בהמרה.
- בכלי
conversion.log
יש בעיות שלא טופלו שצריך לתקן קודם. - יכול להיות שב-
.aidl
הקבצים שנוצרו יש אזהרות והצעות נדרשת פעולה. התגובות האלה מתחילות ב-//
. - כדאי לנצל את ההזדמנות כדי לפנות מקום ולשפר את החבילה.
- כדאי לבדוק את
@JavaDerive
הערה לגבי תכונות שאולי נחוצות, כמוtoString
אוequals
- בכלי
בונים רק את היעדים הנחוצים.
- השבתת קצוות עורפיים שלא יהיו בשימוש. עדיף להשתמש בקצה העורפי NDK על פני ה-CPP בקצה העורפי, ראו בחירת זמן ריצה.
- מסירים את ספריות התרגום או כל קוד שנוצר בהן שלא יהיה בשימוש.
ראו הבדלים משמעותיים ב-AIDL/HIDL.
- השימוש ב-
Status
ובחריגים המובנים של AIDL בדרך כלל משפרים את ולהסיר את הצורך בסוג סטטוס אחר שספציפי לממשק. - כברירת מחדל, ארגומנטים של ממשק AIDL ב-method הם לא
@nullable
כמו שהם היו ב-HIDL.
- השימוש ב-
SEPolicy ל-AIDL HALs
סוג שירות AIDL שגלוי לקוד הספק חייב לכלול
מאפיין hal_service_type
. אחרת, ההגדרות האישיות של המדיניות יהיו זהות
כמו כל שירות AIDL אחר (למרות שיש מאפיינים מיוחדים ל-HAL). כאן
היא הגדרה לדוגמה להקשר של שירות HAL:
type hal_foo_service, service_manager_type, hal_service_type;
ברוב השירותים שהפלטפורמה מגדירה, הקשר שירות עם
מסוג כבר התווסף (לדוגמה, android.hardware.foo.IFoo/default
יוסיף
כבר סומן כ-hal_foo_service
). אבל, אם לקוח framework תומך
כמה שמות של מכונות, צריך להוסיף שמות של מכונות
קובצי service_contexts
ספציפיים למכשיר.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
צריך להוסיף מאפייני HAL כשאנחנו יוצרים סוג חדש של HAL. רמת HAL ספציפית
משויך לכמה סוגי שירותים (כל אחד מהם עשוי
יש כמה מקרים, כמו שהסברנו קודם לכן). ל-HAL, foo
, יש לנו
hal_attribute(foo)
. המאקרו הזה מגדיר את המאפיינים hal_foo_client
hal_foo_server
. עבור דומיין נתון, הערכים hal_client_domain
ו-
פקודות המאקרו hal_server_domain
משייכות דומיין למאפיין HAL נתון. עבור
לדוגמה, שרת מערכת שהוא לקוח של ה-HAL הזה תואם למדיניות
hal_client_domain(system_server, hal_foo)
שרת HAL כולל באופן דומה
hal_server_domain(my_hal_domain, hal_foo)
בדרך כלל, כשמדובר ב-HAL נתון
אנחנו יוצרים גם דומיין כמו hal_foo_default
לעיון
HAL לדוגמה. עם זאת, מכשירים מסוימים משתמשים בדומיינים האלה עבור השרתים שלהם.
הבחנה בין דומיינים של שרתים מרובים חשובה רק אם יש לנו
כמה שרתים שמציגים את אותו הממשק וצריכים הרשאה שונה
שהוגדרו בהטמעות שלהם. בכל פקודות המאקרו האלה, hal_foo
אינו למעשה
אובייקט Sepolicy. במקום זאת, בפקודות המאקרו משתמשים באסימון הזה כדי להפנות
קבוצת המאפיינים שמשויכים לצמד שרתי לקוח.
עם זאת, עד עכשיו לא שויכו בין hal_foo_service
לבין hal_foo
(צמד המאפיינים מ-hal_attribute(foo)
). מתבצע שיוך של מאפיין HAL
עם שירותי AIDL HAL באמצעות המאקרו hal_attribute_service
(שימוש ב-HIDL HALs)
מאקרו hal_attribute_hwservice
). לדוגמה,
hal_attribute_service(hal_foo, hal_foo_service)
המשמעות היא
תהליכים של hal_foo_client
יכולים להשיג תקן HAL ו-hal_foo_server
יכולים לרשום את האלמנט HAL. האכיפה של כללי הרישום האלה היא
נעשה על ידי מנהל ההקשר (servicemanager
). שימו לב, שמות של שירותים
לא תמיד תואמות למאפייני HAL. לדוגמה, אנחנו עשויים לראות
hal_attribute_service(hal_foo, hal_foo2_service)
עם זאת, באופן כללי,
הדבר מרמז על כך שהשירותים תמיד בשימוש יחד, לכן אולי כדאי להסיר
hal_foo2_service
ושימוש ב-hal_foo_service
בכל השירותים שלנו
הקשרים שונים. רוב יעדי HAL שמגדירים כמה hal_attribute_service
הם כי
השם של מאפיין HAL המקורי אינו כללי מספיק ואי אפשר לשנות אותו.
סיכום של כל המידע הזה, לדוגמה HAL, נראה כך:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
ממשקי תוספים מצורפים
אפשר לצרף תוסף לכל ממשק של binder, לא משנה אם הוא ברמה העליונה רשום ישירות במנהל השירות, או שהוא ממשק משנה. כשמקבלים תוסף, צריך לוודא שסוג התוסף הוא מה מצופה. ניתן להגדיר תוספים רק בתהליך ההצגה של קלסר.
יש להשתמש בתוספים מצורפים בכל פעם שתוסף משנה את של רכיב HAL קיים. כאשר יש צורך בפונקציונליות חדשה לגמרי, אין צורך להשתמש במנגנון הזה, וניתן להשתמש בממשק תוספים להירשם ישירות למנהל השירות. ממשקי תוספים מצורפים הכי הגיוני כשהם מצורפים לממשקי משנה, כי ההיררכיות יכולות להיות עמוקות או עם מספר מכונות. שימוש בתוסף גלובלי לשיקוף היררכיית ממשק המשנה של שירות אחר תדרוש ניהול חשבונות כדי לספק פונקציונליות שוות ערך לתוספים המצורפים ישירות.
כדי להגדיר תוסף ב-binder, צריך להשתמש בממשקי ה-API הבאים:
- בקצה העורפי של NDK:
AIBinder_setExtension
- בקצה העורפי של Java:
android.os.Binder.setExtension
- בקצה העורפי של CPP:
android::Binder::setExtension
- בקצה העורפי של Rust:
binder::Binder::set_extension
כדי לקבל הארכה ב-Binder, צריך להשתמש בממשקי ה-API הבאים:
- בקצה העורפי של NDK:
AIBinder_getExtension
- בקצה העורפי של Java:
android.os.IBinder.getExtension
- בקצה העורפי של CPP:
android::IBinder::getExtension
- בקצה העורפי של Rust:
binder::Binder::get_extension
מידע נוסף על ממשקי ה-API האלה זמין במסמכי התיעוד של
getExtension
בקצה העורפי המתאים. דוגמה לאופן השימוש
אפשר למצוא את התוספים
hardware/interfaces/tests/extension/vibrator.
הבדלים משמעותיים ב-AIDL וב-HIDL
כשמשתמשים ב-AIDL HAL או בממשקי AIDL HAL, חשוב לשים לב להבדלים בהשוואה לכתיבת HIDL HAL.
- התחביר של שפת AIDL יותר קרוב ל-Java. תחביר HIDL דומה ל-C++.
- לכל ממשקי AIDL יש סטטוסים מובנים של שגיאות. במקום ליצור תבניות מותאמות אישית
סוגי הסטטוס, ליצור אובייקטים של סטטוס קבוע בקובצי ממשק ולהשתמש
EX_SERVICE_SPECIFIC
בקצה העורפי של CPP/NDK ו-ServiceSpecificException
בקצה העורפי של Java. להצגת שגיאה טיפול. - טכנולוגיית AIDL לא מפעילה מאגרי שרשורים באופן אוטומטי כשאובייקטים של קישור נשלחים. צריך להפעיל אותם באופן ידני שרשור ניהול).
- המערכת של AIDL לא מבטלת שגיאות תעבורה לא מסומנות (HIDL
Return
מבטל את אם התיבה לא מסומנת). - ב-AIDL אפשר להצהיר רק על סוג אחד בכל קובץ.
- אפשר לציין ארגומנטים של AIDL בתור 'in/out'/'in' בנוסף לפלט (אין 'קריאות חוזרות סינכרוניות').
- AIDL משתמש ב-fd כסוג הראשוני במקום בכינוי.
- HIDL משתמשת בגרסאות ראשיות לשינויים לא תואמים ובגרסאות משניות עבור
ושינויים תואמים. ב-AIDL, מתבצעים שינויים שתואמים לאחור.
ב-AIDL אין קונספט מפורש של גרסאות ראשיות. במקום זאת,
ישולבו בשמות החבילות. לדוגמה, AIDL עשוי להשתמש בשם החבילה
bluetooth2
- כברירת מחדל, מערכת AIDL לא מקבלת עדיפות בזמן אמת.
setInheritRt
כדי לאפשר ירושה בעדיפות גבוהה בזמן אמת, צריך להשתמש בפונקציה לכל הקשרר.