איידל יציב

אנדרואיד 10 מוסיפה תמיכה בשפת הגדרת ממשק אנדרואיד יציבה (AIDL), דרך חדשה לעקוב אחר ממשק תוכניות היישום (API)/ממשק הבינארי של היישום (ABI) המסופק על ידי ממשקי AIDL. ל-AIDL יציב יש את ההבדלים העיקריים הבאים מ-AIDL:

  • ממשקים מוגדרים במערכת הבנייה עם aidl_interfaces .
  • ממשקים יכולים להכיל נתונים מובנים בלבד. פריטי חבילות המייצגים את הסוגים הרצויים נוצרים אוטומטית על סמך הגדרת ה-AIDL שלהם, והם מסודרים באופן אוטומטי ולא מסודרים.
  • ניתן להכריז על ממשקים כיציבים (תואמי אחורה). כאשר זה קורה, ה-API שלהם נמצא במעקב ובגירסאות בקובץ ליד ממשק AIDL.

AIDL מובנה לעומת יציב

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

AIDL יציב דורש AIDL מובנה כדי שמערכת הבנייה והמהדר יוכלו להבין אם שינויים שבוצעו ב-parcelables תואמים לאחור. עם זאת, לא כל הממשקים המובנים יציבים. כדי להיות יציב, ממשק חייב להשתמש רק בסוגים מובנים, והוא חייב להשתמש גם בתכונות ניהול הגירסאות הבאות. לעומת זאת, ממשק אינו יציב אם מערכת בניית הליבה משמשת לבנייתו או אם מוגדרת 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 המרכיבים את הממשק. הנתיב עבור 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 : הדגל האופציונלי להפעיל או לכבות את המעקב. החל באנדרואיד 14 ברירת המחדל true עבור ה- cpp ו- java backends.
  • host_supported : הדגל האופציונלי שכאשר מוגדר כ- true הופך את הספריות שנוצרות לזמינות לסביבת המארח.
  • unstable : הדגל האופציונלי המשמש לסימון שממשק זה אינו צריך להיות יציב. כאשר זה מוגדר כ- true , מערכת ה-build לא יוצרת את ה-API dump עבור הממשק וגם לא דורשת את עדכון זה.
  • frozen : הדגל האופציונלי שכאשר מוגדר כ- true פירושו שלממשק אין שינויים מאז הגרסה הקודמת של הממשק. זה מאפשר יותר בדיקות בזמן בנייה. כאשר מוגדר כ- false זה אומר שהממשק נמצא בפיתוח ויש בו שינויים חדשים כך שהפעלת foo-freeze-api תיצור גרסה חדשה ותשנה אוטומטית את הערך ל- true . הוצג באנדרואיד 14.
  • 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.[cpp|ndk].additional_shared_libraries : הוצג באנדרואיד 14, דגל זה מוסיף תלות לספריות המקוריות. הדגל הזה שימושי עם ndk_header ו- cpp_header .
  • 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 יציב הוא אופן הגדרת החבילות. בעבר הוכרזו חבילות חבילות קדימה ; ב-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: ...,
    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 , בהתאם לגרסת האנדרואיד, ומוסיף קובץ .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). השגיאה מתקבלת אם הלקוח שולח ערכת איחוד לשדה החדש לשרת ישן, או כאשר מדובר בלקוח ישן שמקבל את האיחוד משרת חדש.

פיתוח מבוסס דגל

לא ניתן להשתמש בממשקים בפיתוח (לא קפואים) במכשירי שחרור, מכיוון שלא מובטח שהם תואמים לאחור.

AIDL תומך ב-fallback של זמן ריצה עבור ספריות ממשק לא מוקפאות אלה על מנת שקוד ייכתב מול הגרסה האחרונה שלא מוקפאה ועדיין ישמש בהתקני שחרור. ההתנהגות התואמת לאחור של לקוחות דומה להתנהגות הקיימת ועם החזרה, גם ההטמעות צריכות לעקוב אחר התנהגויות אלו. ראה שימוש בממשקים עם גרסאות .

דגל בניית AIDL

הדגל ששולט בהתנהגות זו הוא RELEASE_AIDL_USE_UNFROZEN המוגדר ב- build/release/build_flags.bzl . true פירושו שהגרסה הלא-מוקפאת של הממשק נמצאת בשימוש בזמן ריצה ו- false פירושו שהספריות של הגרסאות הלא-מוקפאות מתנהגות כולן כמו הגרסה המוקפאת האחרונה שלהן. אתה יכול לעקוף את הדגל למצב true עבור פיתוח מקומי, אך עליך להחזיר אותו ל- false לפני השחרור. בדרך כלל הפיתוח נעשה עם תצורה שבה הדגל מוגדר כ- true .

מטריצת תאימות ומניפסטים

אובייקטי ממשק ספקים (אובייקטי VINTF) מגדירים אילו גרסאות צפויות, ואילו גרסאות מסופקות משני צדי ממשק הספק.

רוב המכשירים שאינם של דיונון מתמקדים במטריצת התאימות העדכנית רק לאחר שהממשקים מוקפאים, כך שאין הבדל בספריות ה-AIDL המבוססות על 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 .

מניפסטים

באנדרואיד 15 (ניסיוני AOSP), הוצג שינוי ב- libvintf כדי לשנות את קבצי המניפסט בזמן הבנייה בהתבסס על הערך של RELEASE_AIDL_USE_UNFROZEN .

המניפסטים ושברי המניפסט מצהירים איזו גרסה של ממשק שירות מיישם. בעת שימוש בגרסה האחרונה שלא מוקפאה של ממשק, יש לעדכן את המניפסט כך שישקף את הגרסה החדשה הזו. כאשר RELEASE_AIDL_USE_UNFROZEN=false ערכי המניפסט מותאמים על ידי libvintf כדי לשקף את השינוי בספריית ה-AIDL שנוצרה. הגרסה שונה מהגרסה הלא-מוקפאת, N , לגרסה הקפואה האחרונה N - 1 . לכן, משתמשים לא צריכים לנהל מספר מניפסטים או קטעי מניפסט עבור כל אחד מהשירותים שלהם.

שינויים בלקוח HAL

קוד לקוח HAL חייב להיות תואם לאחור עם כל גרסה קפואה נתמכת קודמת. כאשר RELEASE_AIDL_USE_UNFROZEN הוא false השירותים תמיד נראים כמו הגרסה האחרונה שהוקפאה או קודמתה (לדוגמה, קריאה לשיטות לא מוקפאות מחזירה UNKNOWN_TRANSACTION , או לשדות חדשים parcelable יש ערכי ברירת מחדל). לקוחות מסגרת אנדרואיד נדרשים להיות תואמים לאחור עם גרסאות קודמות נוספות, אך זהו פרט חדש עבור לקוחות ספקים ולקוחות של ממשקים בבעלות שותף.

שינויים ביישום HAL

ההבדל הגדול ביותר בפיתוח HAL עם פיתוח מבוסס דגל הוא הדרישה שיישומי HAL יהיו תואמים לאחור עם הגרסה הקפואה האחרונה כדי לעבוד כאשר RELEASE_AIDL_USE_UNFROZEN הוא false . התחשבות בתאימות לאחור בהטמעות ובקוד מכשיר היא תרגיל חדש. ראה שימוש בממשקים עם גרסאות .

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

דוגמה: לממשק יש שלוש גרסאות קפואות. הממשק מתעדכן בשיטה חדשה. הלקוח והשירות מעודכנים שניהם לשימוש בספריית גרסה 4 החדשה. מכיוון שספריית ה-V4 מבוססת על גרסה לא קפואה של הממשק, היא מתנהגת כמו הגרסה הקפואה האחרונה, גרסה 3, כאשר RELEASE_AIDL_USE_UNFROZEN הוא false , ומונעת את השימוש בשיטה החדשה.

כאשר הממשק מוקפא, כל הערכים של RELEASE_AIDL_USE_UNFROZEN משתמשים בגרסה הקפואה הזו, וניתן להסיר את הקוד המטפל בתאימות לאחור.

בעת קריאה לשיטות בהתקשרות חוזרת, עליך לטפל בחן במקרה כאשר UNKNOWN_TRANSACTION מוחזר. ייתכן שלקוחות מיישמים שתי גרסאות שונות של התקשרות חוזרת בהתבסס על תצורת ההפצה, כך שאינך יכול להניח שהלקוח ישלח את הגרסה החדשה ביותר, ושיטות חדשות עשויות להחזיר זאת. זה דומה לאופן שבו לקוחות 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, אנו מתאימים את target-level מטריצת תאימות המסגרת (FCM) ואת PRODUCT_SHIPPING_API_LEVEL של דיונון כך שהם ישקפו מכשירים שיושקו עם ההשקה בשנה הבאה. אנו מתאימים target-level ואת PRODUCT_SHIPPING_API_LEVEL כדי לוודא שיש מכשיר השקה כלשהו שנבדק ועומד בדרישות החדשות לשחרור בשנה הבאה.

כאשר RELEASE_AIDL_USE_UNFROZEN true , Cuttlefish משמש לפיתוח של מהדורות אנדרואיד עתידיות. הוא מכוון לרמת ה-FCM של מהדורת אנדרואיד של השנה הבאה ול- PRODUCT_SHIPPING_API_LEVEL , מה שמחייב אותו לעמוד בדרישות התוכנה של הספקים של המהדורה הבאה (VSR).

כאשר RELEASE_AIDL_USE_UNFROZEN הוא false , ל-Cuttlefish יש את target-level הקודמת ו- PRODUCT_SHIPPING_API_LEVEL כדי לשקף התקן שחרור. באנדרואיד 14 ומטה, הבידול הזה יבוצע עם סניפים שונים של Git שאינם קולטים את השינוי target-level של FCM, רמת המשלוח של API או כל קוד אחר המכוון למהדורה הבאה.

כללי מתן שמות של מודול

ב-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 יציב.

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

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

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