AIDL יציב

ב-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 שמרכיבים את הממשק. הנתיב לסוג AIDL Foo שמוגדר בחבילה, 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 12
  • versions: הגרסאות הקודמות של הממשק קפוא מתחת לסכום 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 יציב.

  1. מזהים את כל יחסי התלות של הממשק. לכל חבילה שהממשק תלוי ב-AIDL, לקבוע אם החבילה מוגדרת ב-AIDL יציב. אם המיקום לא מוגדר, יש להמיר את החבילה.

  2. צריך להמיר את כל מגרשי המגרשים למגרשים יציבים (קובצי ממשק לא משתנים). עושים זאת עד לבטא את המבנה שלהם ישירות בקובצי AIDL. כיתות הניהול חייבות לשכתוב כדי להשתמש בסוגים חדשים אלה. אפשר לעשות את זה לפני שיוצרים חבילת aidl_interface (בהמשך).

  3. יוצרים חבילת aidl_interface (כפי שמתואר למעלה) שמכילה את שם המודול, יחסי התלות שלו וכל מידע אחר שדרוש לך. כדי לייצב אותו (לא רק מובנה), צריך גם להיות לו גרסאות. למידע נוסף, ראו ממשקי ניהול גרסאות.