אנדרואיד 10 מוסיפה תמיכה בשפת הגדרת ממשק אנדרואיד יציבה (AIDL), דרך חדשה לעקוב אחר ממשק תוכניות היישום (API)/ממשק הבינארי של היישום (ABI) המסופק על ידי ממשקי AIDL. ל-AIDL יציב יש את ההבדלים העיקריים הבאים מ-AIDL:
- ממשקים מוגדרים במערכת הבנייה עם
aidl_interfaces
. - ממשקים יכולים להכיל נתונים מובנים בלבד. פריטי חבילות המייצגים את הסוגים הרצויים נוצרים אוטומטית על סמך הגדרת ה-AIDL שלהם, והם מסודרים באופן אוטומטי ולא מסודרים.
- ניתן להכריז על ממשקים כיציבים (תואמים לאחור). כאשר זה קורה, ה-API שלהם נמצא במעקב ובגירסאות בקובץ ליד ממשק AIDL.
הגדרת ממשק AIDL
הגדרה של aidl_interface
נראית כך:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions_with_info: [
{
version: "1",
imports: ["other-aidl-V1"],
},
{
version: "2",
imports: ["other-aidl-V3"],
}
],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
-
name
: השם של מודול ממשק AIDL המזהה באופן ייחודי ממשק AIDL. -
srcs
: רשימת קובצי המקור של AIDL המרכיבים את הממשק. הנתיב עבורFoo
מסוג AIDL המוגדר בחבילהcom.acme
צריך להיות ב-<base_path>/com/acme/Foo.aidl
, כאשר<base_path>
יכול להיות כל ספרייה הקשורה לספרייה שבה נמצאAndroid.bp
. בדוגמה למעלה,<base_path>
הואsrcs/aidl
. -
local_include_dir
: הנתיב שממנו מתחיל שם החבילה. זה מתאים ל<base_path>
שהוסבר לעיל. -
imports
: רשימה של מודוליaidl_interface
שבהם זה משתמש. אם אחד ממשקי ה-AIDL שלך משתמש בממשק או ב-parcelable מ-aidl_interface
אחר, שים את שמו כאן. זה יכול להיות השם בפני עצמו, כדי להתייחס לגרסה העדכנית ביותר, או השם עם סיומת הגרסה (כגון-V1
) כדי להתייחס לגרסה ספציפית. ציון גרסה נתמך מאז אנדרואיד 12 -
versions
: הגרסאות הקודמות של הממשק שהוקפאו תחתapi_dir
, החל באנדרואיד 11,versions
מוקפאות תחתaidl_api/ name
. אם אין גרסאות קפואות של ממשק, אין לציין זאת, ולא יהיו בדיקות תאימות. שדה זה הוחלף ב-versions_with_info
עבור 13 ומעלה. -
versions_with_info
: רשימת tuples, שכל אחת מהן מכילה שם של גרסה קפואה ורשימה עם ייבוא גרסאות של מודולי aidl_interface אחרים שגרסה זו של aidl_interface ייבאה. ההגדרה של גרסה V של ממשק AIDL IFACE ממוקמת ב-aidl_api/ IFACE / V
. שדה זה הוצג באנדרואיד 13, והוא לא אמור להשתנות ישירות ב-Android.bp. השדה מתווסף או מתעדכן על ידי הפעלת*-update-api
או*-freeze-api
. כמו כן, שדותversions
מועברים אוטומטית ל-versions_with_info
כאשר משתמש מפעיל*-update-api
או*-freeze-api
. -
stability
: הדגל האופציונלי להבטחת היציבות של ממשק זה. כרגע תומך רק"vintf"
. אם זה לא מוגדר, זה מתאים לממשק עם יציבות בהקשר הקומפילציה הזה (לכן ניתן להשתמש בממשק שנטען כאן רק עם דברים שהידור ביחד, למשל ב-system.img). אם זה מוגדר ל-"vintf"
, זה מתאים להבטחת יציבות: יש לשמור על הממשק יציב כל עוד נעשה בו שימוש. -
gen_trace
: הדגל האופציונלי להפעיל או לכבות את המעקב. ברירת המחדל היאfalse
. -
host_supported
: הדגל האופציונלי שכאשר מוגדר כ-true
הופך את הספריות שנוצרות לזמינות לסביבת המארח. -
unstable
: הדגל האופציונלי המשמש לסימון שממשק זה אינו צריך להיות יציב. כאשר זה מוגדר כ-true
, מערכת ה-build לא יוצרת את ה-API dump עבור הממשק וגם לא דורשת את עדכון זה. -
frozen
: הדגל האופציונלי שכאשר מוגדר כ-true
פירושו שלממשק אין שינויים מאז הגרסה הקודמת של הממשק. זה מאפשר יותר בדיקות בזמן בנייה. כאשר מוגדר כ-false
זה אומר שהממשק נמצא בפיתוח ויש בו שינויים חדשים כך שהפעלתfoo-freeze-api
תיצור גרסה חדשה ותשנה אוטומטית את הערך ל-true
. הוצג באנדרואיד 14 (ניסיוני AOSP). -
backend.<type>.enabled
: דגלים אלה מחליפים את כל אחד מהחלקים האחוריים שעבורם מהדר AIDL מייצר קוד. נכון לעכשיו, ארבעה קצה אחורי נתמכים: Java, C++, NDK ו-Rust. הקצה האחורי של Java, C++ ו-NDK מופעלים כברירת מחדל. אם אין צורך באחד משלושת הקצה האחורי הללו, יש להשבית אותו במפורש. חלודה מושבתת כברירת מחדל. -
backend.<type>.apex_available
: רשימת שמות ה-APEX שספריית העצבים שנוצרה זמינה עבורם. -
backend.[cpp|java].gen_log
: הדגל האופציונלי השולט אם ליצור קוד נוסף לאיסוף מידע על העסקה. -
backend.[cpp|java].vndk.enabled
: הדגל האופציונלי להפוך את הממשק הזה לחלק מ-VNDK. ברירת המחדל היאfalse
. -
backend.java.sdk_version
: הדגל האופציונלי לציון הגרסה של ה-SDK שספריית ה-Stubs של Java בנויה נגדה. ברירת המחדל היא"system_current"
. אין להגדיר זאת כאשרbackend.java.platform_apis
נכון. -
backend.java.platform_apis
: הדגל האופציונלי שאמור להיות מוגדר כ-true
כאשר הספריות שנוצרות צריכות לבנות מול ה-API של הפלטפורמה ולא מול ה-SDK.
עבור כל שילוב של הגירסאות והחלקים האחוריים המאופשרים, נוצרת ספריית קטעים. כיצד להתייחס לגרסה הספציפית של ספריית ה-stub עבור backend ספציפי, ראה כללי מתן שמות של מודול .
כתיבת קבצי AIDL
ממשקים ב-AIDL יציב דומים לממשקים מסורתיים, למעט שהם אינם מורשים להשתמש ב-parcelables לא מובנים (מכיוון שאלו אינם יציבים!). ההבדל העיקרי ב-AIDL יציב הוא אופן הגדרת החבילות. בעבר הוכרזו חבילות חבילות קדימה ; ב-AIDL יציב, שדות ומשתנים של חבילות מוגדרים במפורש.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
ברירת מחדל נתמכת כרגע (אך לא נדרשת) עבור boolean
, char
, float
, double
, byte
, int
, long
ו- String
. ב-Android 12, ברירות מחדל עבור ספירות מוגדרות על ידי המשתמש נתמכות גם. כאשר לא צוין ברירת מחדל, נעשה שימוש בערך 0 דמוי או ריק. ספירות ללא ערך ברירת מחדל מאותחלות ל-0 גם אם אין מונה אפס.
שימוש בספריות בדל
לאחר הוספת ספריות בדל כתלות למודול שלך, תוכל לכלול אותן בקבצים שלך. להלן דוגמאות לספריות בדל במערכת הבנייה (ניתן להשתמש Android.mk
גם עבור הגדרות מודול מדור קודם):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if desire is to load a library and share
// it among multiple users or if you only need access to constants
static_libs: ["my-module-name-java"],
...
}
# or
rust_... {
name: ...,
rust_libs: ["my-module-name-rust"],
...
}
דוגמה ב-C++:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
דוגמה ב-Java:
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
דוגמה בחלודה:
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
ממשקי גירסאות
הכרזה על מודול עם שם foo יוצר גם יעד במערכת ה-build שבו אתה יכול להשתמש כדי לנהל את ה-API של המודול. כאשר נבנה, foo-freeze-api מוסיף הגדרת API חדשה תחת api_dir
או aidl_api/ name
, בהתאם לגרסת האנדרואיד, ומוסיף קובץ .hash
, שניהם מייצגים את הגרסה החדשה של הממשק שהוקפאה. foo-freeze-api מעדכן גם את המאפיין versions_with_info
כדי לשקף את הגרסה הנוספת imports
עבור הגרסה. בעיקרון, imports
ב- versions_with_info
מועתק משדה imports
. אבל הגרסה היציבה האחרונה מצוינת imports
ב- versions_with_info
עבור הייבוא שאין לו גרסה מפורשת. לאחר שצוין המאפיין versions_with_info
, מערכת ה-build מפעילה בדיקות תאימות בין גרסאות קפואות וגם בין Top of Tree (ToT) לגרסה הקפואה האחרונה.
בנוסף, עליך לנהל את הגדרת ה-API של גרסת ToT. בכל פעם ש-API מתעדכן, הפעל את foo-update-api כדי לעדכן את aidl_api/ name /current
המכיל את הגדרת ה-API של גרסת ToT.
כדי לשמור על היציבות של ממשק, בעלים יכולים להוסיף חדשים:
- שיטות עד סוף ממשק (או שיטות עם סדרות חדשות מוגדרות במפורש)
- אלמנטים לסוף פריט (מחייב הוספת ברירת מחדל עבור כל רכיב)
- ערכים קבועים
- באנדרואיד 11, מונים
- באנדרואיד 12, שדות עד סוף איחוד
לא מותרות פעולות אחרות, ואף אחד אחר לא יכול לשנות ממשק (אחרת הם מסתכנים בהתנגשות עם שינויים שמבצע בעלים).
כדי לבדוק שכל הממשקים מוקפאים לשחרור, אתה יכול לבנות עם ערכת המשתנים הסביבתיים הבאים:
-
AIDL_FROZEN_REL=true m ...
- build מחייב את הקפאת כל ממשקי ה-AIDL היציבים שאין להםowner:
צוין שדה. -
AIDL_FROZEN_OWNERS="aosp test"
- בנייה מחייבת את הקפאת כל ממשקי ה-AIDL היציבים עםowner:
השדה שצוין כ"aosp" או "test".
יציבות היבוא
עדכון גרסאות הייבוא עבור גרסאות קפואות של ממשק תואם לאחור בשכבת ה- Stable AIDL. עם זאת, עדכון אלה מצריך עדכון של כל השרתים והלקוחות המשתמשים בגרסה הישנה של הממשק, וחלק מהיישומים עשויים להתבלבל בעת ערבוב גרסאות שונות של סוגים. בדרך כלל, עבור חבילות מסוגים בלבד או נפוצות, זה בטוח מכיוון שכבר צריך לכתוב קוד כדי לטפל בסוגים לא ידועים מעסקאות IPC.
בקוד פלטפורמת אנדרואיד android.hardware.graphics.common
היא הדוגמה הגדולה ביותר לשדרוג גרסה מסוג זה.
שימוש בממשקים עם גרסאות
שיטות ממשק
בזמן ריצה, כאשר מנסים לקרוא לשיטות חדשות בשרת ישן, לקוחות חדשים מקבלים שגיאה או חריגה, תלוי ב-backend.
-
cpp
backend מקבל::android::UNKNOWN_TRANSACTION
. -
ndk
backend מקבלSTATUS_UNKNOWN_TRANSACTION
. -
java
backend מקבלandroid.os.RemoteException
עם הודעה האומרת שה-API אינו מיושם.
לאסטרטגיות להתמודדות עם זה, ראה שאילתת גרסאות ושימוש בברירות מחדל .
חפצים
כאשר שדות חדשים מתווספים לחבילות, לקוחות ושרתים ישנים משחררים אותם. כאשר לקוחות ושרתים חדשים מקבלים חבילות ישנות, ערכי ברירת המחדל של שדות חדשים ממולאים באופן אוטומטי. משמעות הדבר היא שיש לציין ברירות מחדל עבור כל השדות החדשים ברכיב רכיב.
לקוחות לא צריכים לצפות משרתים להשתמש בשדות החדשים אלא אם כן הם יודעים שהשרת מיישם את הגרסה שהשדה מוגדר בה (ראה גרסאות שאילתות ).
מינונים וקבועים
באופן דומה, לקוחות ושרתים צריכים לדחות או להתעלם מערכי קבועים וממונים בלתי מזוהים לפי הצורך, מכיוון שעשויים להתווסף נוספים בעתיד. לדוגמה, שרת לא צריך לבטל כאשר הוא מקבל מונה שהוא לא יודע עליו. זה צריך להתעלם ממנו, או להחזיר משהו כדי שהלקוח ידע שהוא לא נתמך ביישום זה.
איגודים
ניסיון לשלוח איחוד עם שדה חדש נכשל אם המקלט ישן ואינו יודע על התחום. היישום לעולם לא יראה את האיחוד עם התחום החדש. מתעלמים מהכשל אם מדובר בעסקה חד-כיוונית; אחרת השגיאה היא BAD_VALUE
(עבור הקצה האחורי של C++ או NDK) או IllegalArgumentException
(עבור הקצה האחורי של Java). השגיאה מתקבלת אם הלקוח שולח ערכת איחוד לשדה החדש לשרת ישן, או כאשר מדובר בלקוח ישן שמקבל את האיחוד משרת חדש.
כללי מתן שמות של מודול
ב-Android 11, עבור כל שילוב של הגרסאות והחלקים האחוריים המופעלים, מודול ספריית בדל נוצר באופן אוטומטי. כדי להתייחס למודול ספריית קטעים ספציפי לקישור, אל תשתמש בשם של מודול aidl_interface
, אלא בשם של מודול ספריית קטעים, שהוא ifacename - version - backend , שבו
-
ifacename
: שם מודולaidl_interface
-
version
היא אחת מהן-
V version-number
עבור הגרסאות הקפואות -
V latest-frozen-version-number + 1
לגרסת קצה העץ (טרם הוקפאה)
-
-
backend
הוא אחד מהם-
java
עבור הקצה האחורי של Java, -
cpp
עבור הקצה האחורי של C++, -
ndk
אוndk_platform
עבור הקצה האחורי של NDK. הראשון מיועד לאפליקציות, והשני מיועד לשימוש בפלטפורמה, -
rust
לחלק האחורי של חלודה.
-
נניח שיש מודול עם name foo והגרסה האחרונה שלו היא 2 , והוא תומך גם ב-NDK וגם ב-C++. במקרה זה, AIDL מייצר את המודולים הבאים:
- מבוסס על גרסה 1
-
foo-V1-(java|cpp|ndk|ndk_platform|rust)
-
- מבוסס על גרסה 2 (הגרסה היציבה האחרונה)
-
foo-V2-(java|cpp|ndk|ndk_platform|rust)
-
- מבוסס על גרסת ToT
-
foo-V3-(java|cpp|ndk|ndk_platform|rust)
-
בהשוואה לאנדרואיד 11,
-
foo- backend
, שהתייחס לגרסה היציבה האחרונה הופךfoo- V2 - backend
-
foo-unstable- backend
, שהתייחס לגרסת ה-ToT הופךfoo- V3 - backend
שמות קבצי הפלט תמיד זהים לשמות המודולים.
- מבוסס על גרסה 1:
foo-V1-(cpp|ndk|ndk_platform|rust).so
- מבוסס על גרסה 2:
foo-V2-(cpp|ndk|ndk_platform|rust).so
- מבוסס על גרסת ToT:
foo-V3-(cpp|ndk|ndk_platform|rust).so
שים לב שהמהדר AIDL אינו יוצר מודול גרסה unstable
, או מודול ללא גרסה עבור ממשק AIDL יציב. החל מאנדרואיד 12, שם המודול שנוצר מממשק AIDL יציב כולל תמיד את הגרסה שלו.
שיטות ממשק מטא חדשות
אנדרואיד 10 מוסיף מספר שיטות ממשק מטא עבור ה-AIDL היציב.
שאילתה לגרסת הממשק של האובייקט המרוחק
לקוחות יכולים לשאול את הגרסה וה-hash של הממשק שהאובייקט המרוחק מיישם ולהשוות את הערכים המוחזרים עם הערכים של הממשק שהלקוח משתמש בו.
דוגמה עם ה- cpp
backend:
sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();
דוגמה עם הקצה האחורי ndk
(וה- ndk_platform
):
IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);
דוגמה עם ה- java
Backend:
IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
// the remote side is using an older interface
}
String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();
עבור שפת Java, הצד המרוחק חייב ליישם getInterfaceVersion()
ו- getInterfaceHash()
כדלקמן (משתמש super
במקום IFoo
כדי למנוע טעויות העתקה/הדבקה. ייתכן שיהיה צורך בהערה @SuppressWarnings("static")
כדי להשבית אזהרות, בהתאם ל תצורת javac
):
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return super.VERSION; }
@Override
public final String getInterfaceHash() { return super.HASH; }
}
הסיבה לכך היא שהמחלקות שנוצרו ( IFoo
, IFoo.Stub
וכו') משותפים בין הלקוח לשרת (לדוגמה, המחלקות יכולות להיות ב-boot classpath). כאשר מחלקות משותפות, השרת מקושר גם לגרסה החדשה ביותר של המחלקות למרות שייתכן שהוא נבנה עם גרסה ישנה יותר של הממשק. אם ממשק מטא זה מיושם במחלקה המשותפת, הוא תמיד מחזיר את הגרסה החדשה ביותר. עם זאת, על ידי יישום השיטה כמפורט לעיל, מספר הגרסה של הממשק מוטבע בקוד השרת (מכיוון ש- IFoo.VERSION
הוא static final int
שמוטבע בעת הפניה) וכך השיטה יכולה להחזיר את הגרסה המדויקת שהשרת נבנה עם.
התמודדות עם ממשקים ישנים יותר
ייתכן שלקוח מעודכן בגרסה החדשה יותר של ממשק AIDL, אך השרת משתמש בממשק AIDL הישן. במקרים כאלה, קריאה למתודה בממשק ישן מחזירה UNKNOWN_TRANSACTION
.
עם AIDL יציב, ללקוחות יש יותר שליטה. בצד הלקוח, אתה יכול להגדיר מימוש ברירת מחדל לממשק AIDL. שיטה ביישום ברירת המחדל מופעלת רק כאשר השיטה אינה מיושמת בצד המרוחק (מכיוון שהיא נבנתה עם גרסה ישנה יותר של הממשק). מכיוון שברירות המחדל מוגדרות באופן גלובלי, אין להשתמש בהן מהקשרים שעלולים להיות משותפים.
דוגמה ב-C++ באנדרואיד 13 ואילך:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
דוגמה ב-Java:
IFoo.Stub.setDefaultImpl(new IFoo.Default() {
@Override
public xxx anAddedMethod(...) throws RemoteException {
// do something default
}
}); // once per an interface in a process
foo.anAddedMethod(...);
אינך צריך לספק את יישום ברירת המחדל של כל השיטות בממשק AIDL. שיטות שמובטחות להיות מיושמות בצד המרוחק (כי אתה בטוח שהשלט נבנה כשהשיטות היו בתיאור ממשק AIDL) אינן צריכות להיות מחולקות במחלקת ברירת המחדל impl
.
המרת AIDL קיים ל-AIDL מובנה/יציב
אם יש לך ממשק AIDL וקוד קיים שמשתמשים בו, השתמש בשלבים הבאים כדי להמיר את הממשק לממשק AIDL יציב.
זהה את כל התלות של הממשק שלך. עבור כל חבילה שהממשק תלוי בה, קבע אם החבילה מוגדרת ב-AIDL יציב. אם לא מוגדר, יש להמיר את החבילה.
המר את כל ה-parcelables בממשק שלך ל-parcelables יציבים (קבצי הממשק עצמם יכולים להישאר ללא שינוי). עשה זאת על ידי ביטוי המבנה שלהם ישירות בקבצי AIDL. יש לשכתב כיתות ניהול כדי להשתמש בסוגים חדשים אלה. ניתן לעשות זאת לפני שתיצור חבילת
aidl_interface
(למטה).צור חבילת
aidl_interface
(כמתואר לעיל) המכילה את שם המודול שלך, התלות שלו וכל מידע אחר שאתה צריך. כדי לייצב אותו (לא רק מובנה), יש צורך גם בגרסה. למידע נוסף, ראה ממשקי גירסאות .