ב-Android 10 נוספה תמיכה בממשק Android יציב Definition Language (AIDL), דרך חדשה לעקוב אחרי תוכנת האפליקציה (API) וממשק בינארי של אפליקציה (ABI) שמסופקים על ידי AIDL ממשקים. AIDL יציב פועל בדיוק כמו AIDL, אבל מערכת ה-build עוקבת תאימות ממשק, ויש מגבלות על מה שאפשר לעשות:
- הממשקים מוגדרים במערכת ה-build עם
aidl_interfaces
. - ממשקים יכולים להכיל רק נתונים מובְנים. מגרשי מגרשים שמייצגים הסוגים המועדפים נוצרים באופן אוטומטי על סמך הגדרת ה-AIDL שלהם מעודכנים ולא מעודכנים באופן אוטומטי.
- אפשר להצהיר על ממשקים כיציבים (תואמים לאחור). כשהפעולה הזו קורה, מתבצע מעקב אחרי ה-API והגרסה שלהם בקובץ ליד ה-AIDL גרפי.
AIDL מובנה לעומת יציב
המונח Structured AIDL מתייחס לסוגים שמוגדרים רק ב-AIDL. לדוגמה, הצהרה בנוגע ל-Parcelable (חבילה בהתאמה אישית) לא מובנית ב-AIDL. מגרשים שהשדות שלהם מוגדרים ב-AIDL, נקראים חבילות סרטונים מובנות.
ל-Stable AIDL נדרש AIDL מובנה, כך שמערכת ה-build והמהדר (compiler)
יכול להבין אם שינויים שבוצעו במגרשים תואמים לאחור.
עם זאת, לא כל הממשקים המובנים הם יציבים. כדי לשמור על יציבות,
ממשק חייב להשתמש בסוגים מובנים בלבד, ובנוסף צריך להשתמש גם
לניהול גרסאות. לעומת זאת, ממשק לא יציב אם גרסת ה-build הבסיסית
משמש לבניית המכשיר, או אם מגדירים את unstable:true
.
הגדרה של ממשק 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 שמרכיבים את הממשק. הנתיב לסוג AIDLFoo
שמוגדר בחבילה,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 משתמשים בממשק או ב-חבילה של אחרaidl_interface
, יש להזין את השם כאן. זה יכול להיות השם עצמו, לעיין הגרסה, או השם עם סיומת הגרסה (למשל-V1
) שאליה רוצים להתייחס או גרסה ספציפית. ציון הגרסה נתמך החל מ-Android 12versions
: הגרסאות הקודמות של הממשק קפוא מתחת לסכוםapi_dir
, החל מ-Android 11. הערכים שלversions
הוקפאו מתחת ל-aidl_api/name
. אם אין גרסאות קפואות של ממשק, אין לציין זאת, ולא יתבצעו בדיקות תאימות. השדה הזה הוחלף בשדהversions_with_info
ל-Android 13 ומעלה.versions_with_info
: רשימת צמדים, שכל אחד מהם מכיל את השם של גרסה שהוקפאה ורשימה עם ייבוא גרסאות של aidl_interface אחר את המודולים שהגרסה הזו של aidl_interface יובאה. ההגדרה של גרסה V של ממשק AIDL, נמצאת ב-aidl_api/IFACE/V
השדה הזה נוסף ל-Android 13, ואסור לשנות אותו ישירות ב-Android.bp
. השדה הוא נוספו או עודכנו על ידי הפעלה של*-update-api
או*-freeze-api
. בנוסף,versions
שדות מועברים באופן אוטומטי לשדותversions_with_info
כשמשתמש מפעיל את*-update-api
או*-freeze-api
.stability
: הדגל האופציונלי להבטחת היציבות של הממשק הזה. הפעולה הזו תומכת רק ב-"vintf"
. אם המדיניותstability
לא מוגדרת, גרסת ה-build בודקת שהממשק תואם לאחור, אלא אם השדהunstable
צוין. אם המדיניות לא מוגדרת, היא תואמת לממשק עם והיציבות בהקשר של ההידור הזה (כלומר כל הדברים במערכת, לדוגמה, דברים ב-system.img
ומחיצות קשורות או כל הספקים דברים, לדוגמה, דברים ב-vendor.img
ומחיצות קשורות). אם המיקוםstability
מוגדר כ-"vintf"
, דבר שתואם להבטחה ליציבות: יש לשמור על יציבות בממשק כל עוד משתמשים בו.gen_trace
: הדגל האופציונלי להפעלה או להשבתה של אפשרות המעקב. מתחיל בעוד ב-Android 14, ברירת המחדל היאtrue
עבורcpp
java
קצוות עורפיים.host_supported
: הדגל האופציונלי שכאשר מגדירים אותו כ-true
הופך את שנוצרות וזמינות לסביבת המארח.unstable
: הדגל האופציונלי שמשמש לסימון שהממשק הזה לא צריכים להיות יציבים. כשהערך מוגדר ל-true
, מערכת ה-build לא יוצר את תמונת ה-API של הממשק ולא דורש לעדכן אותו.frozen
: הדגל האופציונלי שכאשר הוא מוגדר ל-true
פירושו שהממשק לא בוצעו שינויים מאז הגרסה הקודמת של הממשק. הפעולה הזאת מאפשרת יותר בדיקות בזמן ה-build. כשהערך הואfalse
, הממשק ויש בה שינויים חדשים, לכן הרצת הפקודהfoo-freeze-api
יוצרת גרסה חדשה ולשנות את הערך באופן אוטומטי ל-true
. הופעה ראשונה Android מגרסה 14.backend.<type>.enabled
: הדגלים האלה מחליפים את כל הקצוות העורפיים המהדר (compiler) AIDL יוצר קוד בשבילו. ארבעה קצוות עורפיים תמיכה: Java, C++ , NDK ו-Rust. הקצוות העורפיים של Java, C++ ו-NDK מופעלים כברירת מחדל. אם אין צורך באחד משלושת הקצוות העורפיים האלה, מושבתת באופן מפורש. החלודה מושבתת כברירת מחדל עד Android 15.backend.<type>.apex_available
: הרשימה של שמות APEX שנוצרו ספריית ה-stub זמינה עבור.backend.[cpp|java].gen_log
: הדגל האופציונלי שקובע אם ליצור קוד נוסף לאיסוף מידע על העסקה.backend.[cpp|java].vndk.enabled
: הדגל האופציונלי ליצירת הממשק הזה חלק מ-VNDK. ברירת המחדל היאfalse
.backend.[cpp|ndk].additional_shared_libraries
: הופעה ראשונה ב-Android 14, הדגל הזה מוסיף יחסי תלות של ספריות מקוריות. הסימון הזה מועיל עםndk_header
ועםcpp_header
.backend.java.sdk_version
: הדגל האופציונלי לציון הגרסה של ה-SDK שמובנה ספריית ה-stub של Java. ברירת המחדל היא"system_current"
לא צריך להגדיר את האפשרות הזו כאשרbackend.java.platform_apis
true
.backend.java.platform_apis
: הדגל האופציונלי שצריך להגדיר בתורtrue
כשהספריות שנוצרות צריכות להיבנות מול ה-API של הפלטפורמה ולא ב-SDK.
לכל שילוב של הגרסאות והקצוות העורפיים שמופעלים, stub הספרייה נוצרת. לקבלת הסבר על הגרסה הספציפית של ספריית ה-stub לקצה עורפי ספציפי, ראו כללים למתן שמות למודולים.
כתיבת קובצי AIDL
ממשקים ב-AIDL יציב דומים לממשקים מסורתיים, חריג לכך שהם לא מורשים להשתמש במגרשים לא מובנים (כי הם לא יציבים. ראו פורמט מובנה לעומת יציב AIDL). ההבדל העיקרי ב-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 גם אם קיים
אין ספירת אפס.
שימוש בספריות stub
אחרי שמוסיפים ספריות stub כתלות למודול,
יכול לכלול אותם בקבצים שלכם. כאן יש דוגמאות לספריות stub של
build (אפשר להשתמש ב-Android.mk
גם להגדרות של מודולים מדור קודם):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if your preference 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: ...,
rustlibs: ["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
, בהתאם לגרסת Android, וגם
מוסיף קובץ .hash
, שניהם מייצגים את הגרסה החדשה שהוקפאה
גרפי. foo-freeze-api גם מעדכן את המאפיין versions_with_info
כדי לשקף את הגרסה הנוספת ואת imports
לגרסה. זהו זה בגדול,
השדה imports
בשדה versions_with_info
הועתק מהשדה imports
. אבל
הגרסה היציבה האחרונה מצוינת ב-imports
ב-versions_with_info
עבור
ייבוא, שאין בו גרסה מפורשת.
אחרי ציון המאפיין versions_with_info
, מערכת ה-build תרוץ
בדיקות תאימות בין גרסאות שהוקפאו ובין 'ראש העץ' (ToT)
ואת הגרסה האחרונה שהוקפאה.
בנוסף, עליך לנהל את הגדרת ה-API של גרסת ToT. בכל פעם ש-API
עודכן, הריצו את foo-update-api כדי לעדכן
aidl_api/name/current
שכולל את הגדרת ה-API של גרסת ToT.
כדי לשמור על היציבות של הממשק, הבעלים יכולים להוסיף:
- שיטות לסוף הממשק (או שיטות עם הגדרות חדשות באופן מפורש) מספר סידורי)
- אלמנטים בסוף החבילה (יש להוסיף ברירת מחדל לכל אחד מהם רכיב)
- ערכים קבועים
- ב-Android 11, סופרים
- ב-Android 12, שדות עד סוף איחוד
אסור לבצע פעולות אחרות, ואף אחד אחר לא יכול לשנות ממשק (אחרת הם עלולים להיתקל בהתנגשות עם שינויים שבעלים מבצעים).
כדי לבדוק שכל הממשקים הוקפאו להשקה, אפשר לפתח באמצעות המשתנים הסביבתיים הבאים מוגדרים:
AIDL_FROZEN_REL=true m ...
- ה-build דורש את כל ממשקי ה-AIDL היציבים כדי להיות מוקפאות שלא צוין בהן השדהowner:
.AIDL_FROZEN_OWNERS="aosp test"
- גרסת ה-build דורשת את כל ממשקי AIDL היציבים להקפאה באמצעות השדהowner:
שצוין כ-"aosp" או 'בדיקה'.
יציבות הייבוא
עדכון גרסאות הייבוא של גרסאות קפואות של ממשק בתאימות לאחור בשכבת ה-AIDL היציבה. אבל כדי לעדכן אותם ומעדכנים את כל השרתים והלקוחות שמשתמשים בגרסה קודמת של הממשק, ויש אפליקציות שעלולות לבלבל בין גרסאות שונות של סוגים. באופן כללי, האפשרות הזו בטוחה לחבילות מסוג מסוים או נפוצות, כי הקוד צריך כבר נכתבו כדי לטפל בסוגים לא ידועים מעסקאות IPC.
בקוד פלטפורמת Android android.hardware.graphics.common
הוא
דוגמה לשדרוג גרסה כזה.
שימוש בממשקים עם גרסאות
שיטות ממשק
בזמן הריצה, כשמנסים לקרוא לשיטות חדשות בשרת ישן, לקוחות חדשים מקבלים שגיאה או חריגה, בהתאם לקצה העורפי.
- הקצה העורפי
cpp
מקבל::android::UNKNOWN_TRANSACTION
. - הקצה העורפי
ndk
מקבלSTATUS_UNKNOWN_TRANSACTION
. - הקצה העורפי של
java
מקבלandroid.os.RemoteException
עם ההודעה הבאה ה-API לא מוטמע.
מידע על אסטרטגיות לטיפול בנושא הזה זמין במאמר הרצת שאילתות על גרסאות באמצעות ברירות מחדל.
מגרשים
כשמוסיפים שדות חדשים לחבילות, לקוחות ישנים ושרתים משמיטים אותם. כשלקוחות ושרתים חדשים מקבלים מגרשים ישנים, ערכי ברירת המחדל של מגרשים חדשים השדות ימולאו באופן אוטומטי. כלומר, ברירות המחדל צריכות שצוינו לכל השדות החדשים במגרש.
הלקוחות לא אמורים לצפות מהשרתים להשתמש בשדות החדשים אלא אם הם יודעים השרת מיישם את הגרסה שבה מוגדר השדה (ראו הרצת שאילתות על גרסאות).
ערכים של טיפוסים בני מנייה (enum) וקבועים
באופן דומה, לקוחות ושרתים צריכים לדחות מוצרים לא מזוהים או להתעלם מהם קבועים וספירות לפי הצורך, מכיוון שייתכן שיתווספו עוד לעתיד. לדוגמה, שרת לא אמור להתבטל כאשר הוא מקבל ומספר שהוא לא יודע עליו. השרת צריך להתעלם מה או, או להחזיר משהו כדי שהלקוח ידע שהוא לא נתמך יישום זה.
איגודים
ניסיון לשלוח איחוד עם שדה חדש ייכשל אם המקבל ישן ו
לא יודע על השדה. היישום אף פעם לא יראה את האיחוד עם
לשדה החדש. המערכת מתעלמת מהכשל אם הוא
עסקה חד-כיוונית; אחרת השגיאה היא BAD_VALUE
(ל-C++ או ל-NDK
את הקצה העורפי) או IllegalArgumentException
(לקצה העורפי של Java). השגיאה היא
התקבל אם הלקוח שולח איחוד המוגדר לשדה החדש לשדה חדש
או כאשר הוא לקוח ישן שמקבל את האיחוד משרת חדש.
ניהול כמה גרסאות
למרחב שמות של מקשר ב-Android יכולה להיות רק גרסה אחת של aidl
ספציפי
ממשק כדי להימנע ממצבים שבהם לסוגים של aidl
שנוצרו יש
הגדרות. ל-C++ יש כלל הגדרה אחת שדורש הגדרה אחת בלבד
של כל סמל.
גרסת ה-build של Android מספקת שגיאה כשהמודול תלוי
גרסאות של אותה ספריית aidl_interface
. ייתכן שהמודול תלוי
באמצעות יחסי התלות של הספריות האלה, באופן ישיר או עקיף,
של יחסי התלות. השגיאות האלה מציגות את תרשים התלות מהמודול שנכשל
בין הגרסאות המתנגשות של הספרייה aidl_interface
. כל
את יחסי התלות צריך לעדכן כך שיכללו את אותה הגרסה (בדרך כלל את הגרסה האחרונה)
בכל הספריות האלה.
אם ספריית הממשק משמשת במודולים רבים ושונים, היא יכולה להיות שימושית
כדי ליצור את cc_defaults
, java_defaults
ו-rust_defaults
לכל קבוצה של
של הספריות והתהליכים שצריכים להשתמש באותה הגרסה. כאשר מכניסים
גרסה חדשה של הממשק, ניתן לעדכן את ברירות המחדל האלה ואת כל המודולים
שמשתמשים בהן מעודכנים יחד, כדי לוודא שהם לא משתמשים בגרסאות שונות.
של הממשק.
cc_defaults {
name: "my.aidl.my-process-group-ndk-shared",
shared_libs: ["my.aidl-V3-ndk"],
...
}
cc_library {
name: "foo",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
cc_binary {
name: "bar",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
כאשר מודולים של aidl_interface
מייבאים מודולים אחרים של aidl_interface
, נוצרים
יחסי תלות נוספים שמחייבים שימוש בגרסאות ספציפיות. הזה
יכול להיות קשה יותר לנהל את המצב כאשר יש aidl_interface
מודולים המיובאים בכמה מודולים של aidl_interface
שנעשה בהם שימוש
יחד באותם התהליכים.
אפשר להשתמש ב-aidl_interfaces_defaults
כדי לשמור אחת מההגדרה של
את הגרסאות האחרונות של יחסי תלות של aidl_interface
שאפשר לעדכן
מקום אחד, ומשמש את כל המודולים של aidl_interface
שרוצים לייבא
הממשק המשותף הזה.
aidl_interface_defaults {
name: "android.popular.common-latest-defaults",
imports: ["android.popular.common-V3"],
...
}
aidl_interface {
name: "android.foo",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
aidl_interface {
name: "android.bar",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
פיתוח מבוסס-סימונים
אי אפשר להשתמש בממשקים בתהליך פיתוח במכשירי הפצה, כי לא מובטח שהם יהיו תואמים לאחור.
AIDL תומך בחלופה לזמן ריצה בספריות הממשק האלה שלא הוקפאו לפי הסדר לקוד שייכתב בהתאם לגרסה האחרונה שלא הוקפאה, ועדיין ייעשה בו שימוש במכשירי הפצה. ההתנהגות של לקוחות בתאימות לאחור דומה להתנהגות של הלקוחות התנהגות קיימת, ועם החלופה, גם ההטמעות צריכות לפעול בהתאם של ההתנהגויות האלה. צפייה להשתמש בממשקים עם גרסאות.
סימון build של AIDL
הדגל ששולט בהתנהגות הזו הוא RELEASE_AIDL_USE_UNFROZEN
הוגדרה ב-build/release/build_flags.bzl
. true
פירושו הגרסה שלא הוקפאה של
הממשק משמש בזמן הריצה והמשמעות של false
היא הספריות של
כל הגרסאות שלא הוקפאו פועלות כמו הגרסה האחרונה שהוקפאה.
אפשר לשנות את הדגל ל-true
עבור
פיתוח מקומי, אבל צריך להחזיר אותו לגרסה false
לפני הפרסום. בדרך כלל
הפיתוח מתבצעת באמצעות הגדרה שבה הדגל מוגדר ל-true
.
מטריצת תאימות ומניפסטים
אובייקטים של ממשק ספק (אובייקטים של VINTF) מגדירים מהן הגרסאות הצפויות ואילו גרסאות מסופקות בכל צד ממשק הספק.
רוב המכשירים שאינם Cuttlefish מטרגטים את מטריצת התאימות העדכנית
רק אחרי שהממשקים הוקפאו, כך שאין הבדל
ספריות המבוססות על RELEASE_AIDL_USE_UNFROZEN
.
מטריצה
ממשקים בבעלות השותף מתווספים לממשקים ספציפיים למכשיר או למוצרים ספציפיים
מטריצות תאימות שהמכשיר מטרגט אליהן במהלך הפיתוח. כך שכאשר
גרסה חדשה ולא קפואה של ממשק מתווספת למטריצת תאימות,
הגרסאות הקודמות שהוקפאו צריכות להישאר למשך
RELEASE_AIDL_USE_UNFROZEN=false
אפשר לעשות זאת באמצעות
קובצי מטריצת תאימות עבור RELEASE_AIDL_USE_UNFROZEN
שונים
או התרת שתי הגרסאות בקובץ מטריצת תאימות אחד
שנמצא בשימוש בכל התצורות.
לדוגמה, כשמוסיפים גרסה 4 שלא הוקפאה, צריך להשתמש ב-<version>3-4</version>
.
כשגרסה 4 מוקפאת, אפשר להסיר את גרסה 3 ממטריצת התאימות
כי נעשה שימוש בגרסה 4 שהוקפאה כאשר RELEASE_AIDL_USE_UNFROZEN
false
.
מניפסטים
ב-Android 15 מוצג שינוי ב-libvintf
תשנה את קובצי המניפסט בזמן ה-build על סמך הערך של
RELEASE_AIDL_USE_UNFROZEN
.
במניפסטים ובקטעי המניפסט מוצהר על גרסת הממשק
שירות שמטמיע. כשמשתמשים בגרסה האחרונה של הממשק שלא הוקפאה,
יש לעדכן את המניפסט כדי שישקף את הגרסה החדשה הזו. מתי
RELEASE_AIDL_USE_UNFROZEN=false
ערכי המניפסט ישתנו לפי
libvintf
כדי לשקף את השינוי בספריית AIDL שנוצרה. הגרסה
שונה מהגרסה שלא הוקפאה, N
, ל-
הגרסה האחרונה שהוקפאה N - 1
. לכן משתמשים לא צריכים לנהל כמה משתמשים
מניפסטים או קטעי מניפסט לכל אחד מהשירותים שלהם.
שינויים בלקוח HAL
קוד לקוח HAL חייב להיות תואם לאחור לכל קוד שהוקפא בעבר ושנתמך בעבר
. כשהערך בשדה RELEASE_AIDL_USE_UNFROZEN
הוא false
, השירותים תמיד מוצגים
כמו הגרסה האחרונה שהוקפאה או גרסה קודמת (לדוגמה, קריאה של
ה-methods מחזירה UNKNOWN_TRANSACTION
, או בשדות parcelable
חדשים יש את
). לקוחות framework של Android נדרשים לפעול לאחור
תואמים לגרסאות קודמות נוספות. עם זאת, זהו פרט חדש לגבי
לקוחות של ספק מסוים ולקוחות של ממשקים בבעלות שותפים.
שינויים בהטמעת HAL
ההבדל הגדול ביותר בפיתוח HAL עם פיתוח מבוסס דגלים הוא
דרישה להטמעה לאחור של יישומי HAL
הגרסה שהוקפאה תפעל כשהערך של RELEASE_AIDL_USE_UNFROZEN
הוא false
.
תאימות לאחור בהטמעות וקוד מכשירים היא טכנולוגיה חדשה
תרגיל. ראו שימוש בגרסאות
ממשקים.
שיקולי התאימות לאחור הם בדרך כלל זהים עבור לקוחות ושרתים, ובקוד של framework וקוד ספק, אבל יש להבדלים קלים שצריך להיות מודעים להם, מאחר שעכשיו באופן יעיל להטמיע שתי גרסאות שמשתמשות באותו קוד מקור (הגרסה הנוכחית, הגרסה שלא הוקפאה ).
דוגמה: בממשק יש שלוש גרסאות קפואות. הממשק מעודכן עם
שיטה חדשה. הלקוח והשירות מעודכנים שניהם לשימוש בגרסה 4 החדשה
לספרייה. מכיוון שספריית V4 מבוססת על גרסה לא קפואה של
הוא מתנהג כמו הגרסה האחרונה שהוקפאה, גרסה 3,
RELEASE_AIDL_USE_UNFROZEN
היא false
, והיא מונעת את השימוש בשיטה החדשה.
כשהממשק קפוא, כל הערכים של RELEASE_AIDL_USE_UNFROZEN
משתמשים בפונקציה הזו
של הגרסה שהוקפאה, ניתן להסיר את הקוד שמטפל בתאימות לאחור.
כשמבצעים קריאה ל-methods בקריאות חוזרות (callback), צריך לטפל בפנייה באלגנטיות כאשר:
השדה UNKNOWN_TRANSACTION
הוחזר. ייתכן שהלקוחות מטמיעים שני סוגים של
של קריאה חוזרת (callback) לפי תצורת הגרסה, כך שאי אפשר
מניחים שהלקוח שולח את הגרסה החדשה ביותר, ושיטות חדשות עשויות להחזיר
הזה. המצב הזה דומה לאופן שבו לקוחות AIDL יציבים שומרים על תרגיל אחורה
תאימות לשרתים מתוארות במאמר שימוש בגרסאות
ממשקים.
// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
mMyCallback = cb;
// Get the version of the callback for later when we call methods on it
auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
return status;
}
// Example of using the callback later
void NotifyCallbackLater() {
// From the latest frozen version (V2)
mMyCallback->foo();
// Call this method from the unfrozen V3 only if the callback is at least V3
if (mMyCallbackVersion >= 3) {
mMyCallback->bar();
}
}
שדות חדשים בסוגים קיימים (parcelable
, enum
, union
) עשויים
לא קיימים או מכילים את ערכי ברירת המחדל שלהם כאשר RELEASE_AIDL_USE_UNFROZEN
false
והערכים של שדות חדשים ששירות מנסה לשלוח מושמטים
לצאת מהתהליך.
אי אפשר לשלוח סוגים חדשים שנוספו בגרסה שלא הוקפאה או שהתקבלו דרך הממשק.
היישום אף פעם לא מקבל קריאה לשיטות חדשות מלקוחות כאשר
RELEASE_AIDL_USE_UNFROZEN
היא false
.
חשוב להקפיד להשתמש בספירות חדשות רק בגרסה שבה הם נוספו, ולא את הגרסה הקודמת.
בדרך כלל, משתמשים ב-foo->getInterfaceVersion()
כדי לראות מה הגרסה של השלט הרחוק
שבו נעשה שימוש. אבל עם תמיכה בניהול גרסאות מבוססות דגלים:
יישום שתי גרסאות שונות, לכן ייתכן שתרצו לקבל את הגרסה של
בממשק הנוכחי. כדי לעשות זאת, אפשר לקבל את גרסת הממשק של
האובייקט הנוכחי, לדוגמה, this->getInterfaceVersion()
או האובייקט האחר
ל-my_ver
. ראו שאילתות על גרסת הממשק של השלט הרחוק
אובייקט
אפשר לקבל מידע נוסף.
ממשקים יציבים חדשים של VINTF
כשמוסיפים חבילת ממשק AIDL חדשה, אין גרסה אחרונה שהוקפאה, לכן
אין התנהגות שצריך לחזור אליה כש-RELEASE_AIDL_USE_UNFROZEN
false
. אל תשתמשו בממשקים האלה. כשהערך בשדה RELEASE_AIDL_USE_UNFROZEN
הוא
false
, מנהל השירות לא יאפשר לשירות לרשום את הממשק
והלקוחות לא ימצאו אותו.
אפשר להוסיף את השירותים באופן מותנה לפי הערך של
הדגל RELEASE_AIDL_USE_UNFROZEN
בקובץ makefile של המכשיר:
ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
android.hardware.health.storage-service
endif
אם השירות הוא חלק מתהליך גדול יותר, לכן אי אפשר להוסיף אותו למכשיר
באופן מותנה, אפשר לבדוק אם השירות מוצהר באמצעות
IServiceManager::isDeclared()
אם ההצהרה מוצהרת והרישום נכשל,
לבטל את התהליך. אם לא הוצהרה בעלות, סביר להניח שהרישום לא יתבצע.
דיונון ככלי פיתוח
בכל שנה אחרי הקפאת ה-VINTF, אנחנו משנים את תאימות המסגרת
מטריצה (FCM) target-level
ו-PRODUCT_SHIPPING_API_LEVEL
של דיונון
ולכן הן משקפים מכשירים שיושקו במהדורה של השנה הבאה. אנחנו מבצעים את ההתאמה
target-level
וגם PRODUCT_SHIPPING_API_LEVEL
כדי לוודא שיש כמה
במכשיר חדש שייבדק ועומד בדרישות החדשות
גרסה חדשה.
כשהערך בשדה RELEASE_AIDL_USE_UNFROZEN
הוא true
, הדיונון הוא
משמש לפיתוח גרסאות עתידיות של Android. היא מטרגטת את מכשירי Android של השנה הבאה
את רמת ה-FCM ו-PRODUCT_SHIPPING_API_LEVEL
, מה שמחייב עמידה בדרישות
דרישות התוכנה לספקים של הגרסה הבאה (VSR).
כאשר RELEASE_AIDL_USE_UNFROZEN
הוא false
, לדיונון יש את הפונקציה הקודמת
target-level
ו-PRODUCT_SHIPPING_API_LEVEL
כדי לשקף מכשיר הפצה.
ב-Android מגרסה 14 ומטה, ההבחנה הזו תהיה
באמצעות הסתעפויות שונות של Git שלא קולטות את השינוי ל-FCM.
target-level
, רמת API למשלוחים או כל קוד אחר שמטרגט את השלב הבא
גרסה חדשה.
כללים למתן שמות למודולים
ב-Android 11, לכל שילוב של הגרסאות
הקצוות העורפיים מופעלים, מודול של ספריית stub נוצר באופן אוטומטי. כדי להפנות
למודול של ספריית stub ספציפית לצורך קישור, אל תשתמשו בשם
aidl_interface
, אבל השם של המודול של ספריית ה-stub,
ifacename-version-backend, כאשר
ifacename
: שם המודולaidl_interface
version
הוא אחד מ-Vversion-number
לגרסאות שהוקפאוVlatest-frozen-version-number + 1
ל- גרסת קצה עץ (עדיין לא להקפא)
backend
הוא אחד מ-java
לקצה העורפי של Java,cpp
עבור הקצה העורפי של C++,ndk
אוndk_platform
בשביל הקצה העורפי של NDK. הראשון מיועד לאפליקציות, השני נועד לשימוש בפלטפורמה עד Android 13. לחשבון ב-Android מגרסה 13 ואילך, יש להשתמש רק ב-ndk
.rust
לקצה העורפי של חלודה.
נניח שיש מודול בשם 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)
בהשוואה ל-Android 11:
foo-backend
, שמייצג את היציבות העדכנית ביותר הגרסה הופכת ל-foo-V2-backend
foo-unstable-backend
, שהפנו למסמך התנאים וההגבלות הגרסה הופכת ל-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
שימו לב שהמהדר (compiler) AIDL לא יוצר מודול של גרסת unstable
,
או מודול ללא גרסאות לממשק AIDL יציב.
החל מגרסה 12 של Android, שם המודול שנוצר ממודל
ממשק AIDL היציב תמיד כולל את הגרסה שלו.
שיטות חדשות של מטא-ממשק
ב-Android 10 נוספו כמה שיטות מטא-ממשק של הוא מודל AIDL יציב.
שאילתה לגבי גרסת הממשק של האובייקט המרוחק
הלקוחות יכולים לשלוח שאילתה על הגרסה והגיבוב של הממשק שבו האובייקט המרוחק מטמיעה את הערכים המוחזרים ומשווים אותם לערכים של הממשק שהלקוח משתמש בו.
דוגמה עם הקצה העורפי cpp
:
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
:
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
וכו') משותפות
בין הלקוח לשרת (לדוגמה, המחלקות יכולות להיות
). כשמשתפים מחלקות, השרת מקושר גם
את הגרסה החדשה ביותר של הכיתות, למרות שייתכן שהיא נוצרה עם
גרסת הממשק. אם המטא ממשק הזה מיושם בתפריט המשותף
class, היא תמיד מחזירה את הגרסה החדשה ביותר. אבל על ידי הטמעת השיטה,
כפי שצוין למעלה, מספר הגרסה של הממשק מוטמע בקוד של השרת
(כי IFoo.VERSION
הוא static final int
שמתווסף אליו יש הפניה)
ולכן השיטה יכולה להחזיר את הגרסה המדויקת שאיתה נבנה השרת.
התמודדות עם ממשקים ישנים
יכול להיות שהלקוח עודכן בגרסה החדשה של AIDL
אבל השרת משתמש בממשק AIDL הישן. במקרים כאלה,
קריאה ל-method בממשק ישן מחזירה את הערך UNKNOWN_TRANSACTION
.
עם AIDL יציב, ללקוחות יש יותר שליטה. בצד הלקוח, אפשר להגדיר הטמעה שמוגדרת כברירת מחדל בממשק AIDL. שיטה בברירת המחדל ההטמעה מתבצעת רק כשהשיטה לא מוטמעת בשלט הרחוק צד שלישי (כי הוא נוצר עם גרסה ישנה יותר של הממשק). מאז ברירות המחדל מוגדרות באופן גלובלי, אין להשתמש בהן מתוך הקשרים שונים.
דוגמה ב-C++ ב-Android 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, לקבוע אם החבילה מוגדרת ב-AIDL יציב. אם המיקום לא מוגדר, יש להמיר את החבילה.
צריך להמיר את כל מגרשי המגרשים למגרשים יציבים (קובצי ממשק לא משתנים). עושים זאת עד לבטא את המבנה שלהם ישירות בקובצי AIDL. כיתות הניהול חייבות לשכתוב כדי להשתמש בסוגים חדשים אלה. אפשר לעשות את זה לפני שיוצרים חבילת
aidl_interface
(בהמשך).יוצרים חבילת
aidl_interface
(כפי שמתואר למעלה) שמכילה את שם המודול, יחסי התלות שלו וכל מידע אחר שדרוש לך. כדי לייצב אותו (לא רק מובנה), צריך גם להיות לו גרסאות. למידע נוסף, ראו ממשקי ניהול גרסאות.