ב-Android 11 הוספנו את היכולת להשתמש ב-AIDL לנכסי HAL ב-Android. כך אפשר להטמיע חלקים של Android בלי HIDL. יש להעביר HALs כדי להשתמש ב-AIDL באופן בלעדי כשזה אפשרי (כש-HALs ב-upstream משתמשים ב-HIDL, חובה להשתמש ב-HIDL).
רכיבי HAL שמשתמשים ב-AIDL כדי לתקשר בין רכיבי framework, כמו של system.img
, ורכיבי חומרה, כמו אלה של vendor.img
, חייבים להשתמש ב-Stable AIDL. עם זאת, כדי לתקשר בתוך קטגוריה, למשל בין HAL אחד לאחר, אין הגבלה על מנגנון ה-IPC.
מוטיבציה
ה-AIDL פועל במשך יותר זמן מ-HIDL, ומשמש במקומות רבים אחרים, למשל בין רכיבי framework של Android או באפליקציות. עכשיו, אחרי ש-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 באובייקט binder לפני שהוא נשלח לתהליך אחר. אי אפשר לשדרג לאחור שירות ליציבות הספק ב-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 לא תואם לזמן הריצה של AOSP ב-Android. במכשירי GMS, הימנעות משינוי הממשקים האלה היא גם זו שמבטיחה שתמונת ה-GSI תמשיך לפעול.
תוספים יכולים להירשם בשתי דרכים שונות:
- בזמן הריצה, ראו תוספים מצורפים.
- בנפרד, רשום בכל העולם וב-VINTF.
עם זאת, תוסף נרשם, כאשר רכיבים ספציפיים לספק (כלומר לא חלק מ-upstream AOSP) משתמשים בממשק, אין אפשרות למיזוג. עם זאת, כשמתבצעים שינויים ב-downstream ברכיבי AOSP ב-upstream, עלולות להיווצר התנגשויות בין מיזוגים, ומומלץ להשתמש באסטרטגיות הבאות:
- ניתן להעלות את השידור של התוספות לממשק ל-AOSP בגרסה הבאה
- בגרסה הבאה יש הוספות לממשקים שמאפשרות גמישות נוספת, ללא התנגשויות מיזוג.
מתקנים לתוספים: 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
}
כמו שאפשר לראות בקוד הקודם, השיטה הזו לא תקינה כי עשויה להיות התנגשות בשדות שנוספו על ידי מטמיע המכשיר במועד העדכון של Parcelable בגרסאות הבאות של 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 יש סטטוסים מובנים של שגיאות. במקום ליצור סוגי סטטוסים מותאמים אישית, צריך ליצור קובצי int של סטטוס קבוע בקובצי הממשק ולהשתמש ב-
EX_SERVICE_SPECIFIC
בקצוות העורפיים של CPP/NDK וב-ServiceSpecificException
בקצה העורפי של Java. ראו טיפול בשגיאות. - טכנולוגיית AIDL לא מפעילה מאגרי שרשורים באופן אוטומטי כשאובייקטים של קישור נשלחים. צריך להפעיל אותם באופן ידני (ראו ניהול שרשורים).
- המערכת של AIDL לא מבטלת שגיאות תעבורה לא מסומנות (HIDL
Return
מבטלת את הסימון של שגיאות שלא נבדקו). - ב-AIDL אפשר להצהיר רק על סוג אחד בכל קובץ.
- אפשר לציין ארגומנטים של AIDL גם בתור 'in/out' או 'inout' בנוסף לפרמטר הפלט (אין 'קריאות חוזרות סינכרוניות').
- AIDL משתמש ב-fd כסוג הראשוני במקום בכינוי.
- ב-HIDL נעשה שימוש בגרסאות ראשיות לשינויים לא תואמים, ובגרסאות משניות כדי לבצע שינויים תואמים. ב-AIDL, מתבצעים שינויים שתואמים לאחור.
ב-AIDL אין מושג מפורש של גרסאות ראשיות. במקום זאת, הוא משולב בשמות של חבילות. לדוגמה, AIDL עשוי להשתמש בשם החבילה
bluetooth2
. - כברירת מחדל, מערכת AIDL לא מקבלת עדיפות בזמן אמת. כדי לאפשר ירושה של עדיפות בזמן אמת צריך להשתמש בפונקציה
setInheritRt
בכל קלסר.