AIDL אחורי

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

ל-AIDL יש את החלקים האחוריים הבאים:

אחורי שפה משטח API בניית מערכות
Java Java SDK/SystemApi (יציב*) את כל
NDK C++ libbinder_ndk (יציב*) aidl_interface
CPP C++ libbinder (לא יציב) את כל
חֲלוּדָה חֲלוּדָה libbinder_rs (לא יציב) aidl_interface
  • משטחי API אלה יציבים, אך רבים ממשקי ה-API, כגון אלו לניהול שירות, שמורים לשימוש פנימי בפלטפורמה ואינם זמינים לאפליקציות. למידע נוסף על אופן השימוש ב-AIDL באפליקציות, עיין בתיעוד למפתחים .
  • החלק האחורי של Rust הוצג באנדרואיד 12; הקצה האחורי של NDK היה זמין החל מ-Android 10.
  • ארגז Rust בנוי על גבי libbinder_ndk . APEXs משתמשים בארגז הקלסר באותו אופן כמו כל אחד אחר בצד המערכת. חלק החלודה נצרף לתוך APEX ונשלח בתוכו. זה תלוי ב- libbinder_ndk.so במחיצת המערכת.

בניית מערכות

בהתאם ל-backend, ישנן שתי דרכים להדר AIDL לקוד stub. לפרטים נוספים על מערכות הבנייה, עיין ב- Soong Module Reference .

מערכת בניית ליבה

בכל מודול cc_ או java_ Android.bp (או המקבילות שלהם Android.mk ), ניתן לציין קבצי .aidl כקובצי מקור. במקרה זה, נעשה שימוש ב-Java/CPP backends של AIDL (לא ב-NDK backend), והמחלקות לשימוש בקבצי AIDL המתאימים מתווספות למודול באופן אוטומטי. אפשרויות כגון local_include_dirs , שאומר למערכת ה-build את נתיב הבסיס לקבצי AIDL באותו מודול ניתן לציין במודולים אלו תחת aidl: group. שימו לב שהחלק האחורי של Rust מיועד רק לשימוש עם Rust. מודולי rust_ מטופלים בצורה שונה בכך שקובצי AIDL אינם מצוינים כקובצי מקור. במקום זאת, מודול aidl_interface מייצר rustlib בשם <aidl_interface name>-rust שניתן לקשר אליו. לפרטים נוספים, עיין בדוגמה של Rust AIDL .

aidl_interface

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

סוגים

אתה יכול לשקול את מהדר aidl כיישום ייחוס עבור טיפוסים. כאשר אתה יוצר ממשק, הפעל את aidl --lang=<backend> ... כדי לראות את קובץ הממשק שנוצר. כאשר אתה משתמש במודול aidl_interface , אתה יכול להציג את הפלט ב- out/soong/.intermediates/<path to module>/ .

סוג Java/AIDL סוג C++ סוג NDK סוג חלודה
בוליאני bool bool bool
בייט int8_t int8_t i8
לְהַשְׁחִיר char16_t char16_t u16
int int32_t int32_t i32
ארוך int64_t int64_t i64
לָצוּף לָצוּף לָצוּף f32
לְהַכפִּיל לְהַכפִּיל לְהַכפִּיל f64
חוּט android::String16 std::string חוּט
android.os.Parcelable android::ניתן לחלק לא לא
IBinder אנדרואיד::IBinder ndk::SpAIBinder קלסר::SpIBinder
T[] std::vector<T> std::vector<T> ב: &[T]
בחוץ: Vec<T>
בייט[] std::vector<uint8_t> std::vector<int8_t> 1 ב: &[u8]
בחוץ: Vec<u8>
רשימה<T> std::vector<T> 2 std::vector<T> 3 ב: &[T] 4
בחוץ: Vec<T>
FileDescriptor android::base::unique_fd לא קלסר::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor קלסר::parcel::ParcelFileDescriptor
סוג ממשק (T) אנדרואיד::sp<T> std::shared_ptr<T> קלסר::חזק
סוג ניתן לחלוקה (T) ט ט ט
סוג איגוד (T) 5 ט ט ט
T[N] 6 std::array<T, N> std::array<T, N> [ת; נ]

1. באנדרואיד 12 ומעלה, מערכי בתים משתמשים ב-uint8_t במקום ב-int8_t מטעמי תאימות.

2. הקצה האחורי של C++ תומך ב- List<T> כאשר T הוא אחד מ- String , IBinder , ParcelFileDescriptor או parcelable. באנדרואיד 13 ומעלה, T יכול להיות כל סוג לא פרימיטיבי (כולל סוגי ממשקים) למעט מערכים. AOSP ממליצה להשתמש בסוגי מערכים כמו T[] , מכיוון שהם עובדים בכל הקצה האחורי.

3. הקצה האחורי של NDK תומך ב- List<T> כאשר T הוא אחד מ- String , ParcelFileDescriptor או parcelable. באנדרואיד 13 ומעלה, T יכול להיות כל סוג לא פרימיטיבי למעט מערכים.

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

5. סוגי איגוד נתמכים באנדרואיד 12 ומעלה.

6. באנדרואיד 13 ומעלה, מערכים בגודל קבוע נתמכים. למערכים בגודל קבוע יכולים להיות מימדים מרובים (למשל int[3][4] ). ב-Java backend, מערכים בגודל קבוע מיוצגים כסוגי מערכים.

כיווניות (פנימה/ החוצה/פנימה)

כאשר מציינים את סוגי הארגומנטים לפונקציות, ניתן לציין אותם כמו in , out או inout . זה שולט באיזה כיוון מועבר מידע עבור שיחת IPC. in הוא כיוון ברירת המחדל, והוא מציין שהנתונים מועברים מהמתקשר אל המותקשר. out פירושו שהנתונים מועברים מהמתקשר אל המתקשר. inout הוא השילוב של שניהם. עם זאת, צוות אנדרואיד ממליץ להימנע משימוש במפרט הארגומנטים inout . אם אתה משתמש inout עם ממשק גרסה ועם מתקשר ישן יותר, השדות הנוספים שקיימים רק אצל המתקשר יאופסו לערכי ברירת המחדל שלהם. לגבי Rust, סוג inout רגיל מקבל &mut Vec<T> , וסוג inout רשימה מקבל &mut Vec<T> .

UTF8/UTF16

עם ה-CPP backend אתה יכול לבחור אם מחרוזות הן utf-8 או utf-16. הכריז על מחרוזות כמחרוזת @utf8InCpp String ב-AIDL כדי להמיר אותן אוטומטית ל-utf-8. הקצה האחורי של NDK ו-Rust תמיד משתמש במחרוזות utf-8. למידע נוסף על ההערה utf8InCpp , ראה הערות ב-AIDL .

בטלות

אתה יכול להוסיף הערות לסוגים שיכולים להיות null ב-Java backend עם @nullable כדי לחשוף ערכי null ל-CPP ו-NDK. ב-Rust backend סוגי @nullable אלה נחשפים כאפשרות Option<T> . שרתים מקוריים דוחים ערכי null כברירת מחדל. היוצאים מן הכלל היחידים לכך הם סוגי interface ו- IBinder , שיכולים תמיד להיות null עבור קריאות NDK וכתיבת CPP/NDK. למידע נוסף על ההערה nullable , ראה הערות ב-AIDL .

פריטים מותאמים אישית

פריט מותאם אישית הוא פריט שמיושם באופן ידני ב-backend של יעד. השתמש ב-parcelables מותאמים אישית רק כאשר אתה מנסה להוסיף תמיכה לשפות אחרות עבור parcelable מותאם אישית קיים שלא ניתן לשנות.

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

    package my.pack.age;
    parcelable Foo;

כברירת מחדל, זה מכריז על Java parcelable כאשר my.pack.age.Foo היא מחלקת Java המיישמת את ממשק Parcelable .

להצהרה על CPP backend מותאם אישית הניתן לחלקה ב-AIDL, השתמש cpp_header :

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

יישום C++ ב- my/pack/age/Foo.h נראה כך:

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

להצהרה על NDK מותאם אישית שניתן לחלק ב-AIDL, השתמש ndk_header :

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

יישום NDK ב- android/pack/age/Foo.h נראה כך:

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

באנדרואיד 15 (AOSP ניסיוני), להכרזה על חלודה מותאמת אישית שניתן לחלוקה ב-AIDL, השתמש rust_type :

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

היישום Rust ב- rust_crate/src/lib.rs נראה כך:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

לאחר מכן תוכל להשתמש ב-parcelable זה כסוג בקובצי AIDL, אך הוא לא יופק על ידי AIDL. ספק אופרטורים < ו == עבור רכיבי רכיבים מותאמים אישית של CPP/NDK כדי להשתמש בהם union .

ערכי ברירת מחדל

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

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

ב-Java backend כאשר חסרים ערכי ברירת מחדל, שדות מאותחלים כערכים אפס עבור טיפוסים פרימיטיביים ו- null עבור טיפוסים לא פרימיטיביים.

ב-backends אחרים, שדות מאותחלים עם ערכי ברירת מחדל מאתחלים כאשר ערכי ברירת מחדל אינם מוגדרים. לדוגמה, ב-C++ backend, שדות String מאותחלים כמחרוזת ריקה ושדות List<T> מאותחלים vector<T> . שדות @nullable מאותחלים כשדות null-value.

טיפול בשגיאות

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

פרמטרי פלט עם שגיאות

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

באילו ערכי שגיאה להשתמש

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

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

ב-Java, השגיאות ממפות לחריגים, כגון android.os.RemoteException . עבור חריגים ספציפיים לשירות, Java משתמשת ב- android.os.ServiceSpecificException יחד עם השגיאה המוגדרת על ידי המשתמש.

קוד מקורי באנדרואיד אינו משתמש בחריגים. הקצה העורפי של CPP משתמש android::binder::Status . הקצה האחורי של NDK משתמש ndk::ScopedAStatus . כל שיטה שנוצרת על ידי AIDL מחזירה אחת מאלה, המייצגת את מצב השיטה. החלק האחורי של Rust משתמש באותם ערכי קוד חריג כמו ה-NDK, אך ממיר אותם לשגיאות Rust מקוריות ( StatusCode , ExceptionCode ) לפני מסירתם למשתמש. עבור שגיאות ספציפיות לשירות, Status או ScopedAStatus שהוחזר משתמש EX_SERVICE_SPECIFIC יחד עם השגיאה המוגדרת על ידי המשתמש.

ניתן למצוא את סוגי השגיאות המובנים בקבצים הבאים:

אחורי הַגדָרָה
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
חֲלוּדָה android/binder_status.h

שימוש ב-backends שונים

הוראות אלה הן ספציפיות לקוד פלטפורמת אנדרואיד. דוגמאות אלה משתמשות בסוג מוגדר, my.package.IFoo . להוראות כיצד להשתמש ב-Rust Backend, עיין בדוגמה של Rust AIDL בדף של Android Rust Patterns .

ייבוא ​​סוגים

בין אם הסוג המוגדר הוא ממשק, ניתן לחלוקה או איחוד, אתה יכול לייבא אותו ב-Java:

import my.package.IFoo;

או ב-CPP backend:

#include <my/package/IFoo.h>

או בקצה האחורי של NDK (שימו לב למרחב השמות הנוסף aidl ):

#include <aidl/my/package/IFoo.h>

או בחלק האחורי של Rust:

use my_package::aidl::my::package::IFoo;

למרות שאתה יכול לייבא סוג מקונן ב-Java, בקצה האחורי של CPP/NDK עליך לכלול את הכותרת עבור סוג השורש שלו. לדוגמה, בעת ייבוא Bar מסוג מקונן המוגדר ב- my/package/IFoo.aidl ( IFoo הוא סוג השורש של הקובץ) עליך לכלול את <my/package/IFoo.h> עבור ה-CPP backend (או <aidl/my/package/IFoo.h> עבור הקצה האחורי של NDK).

הטמעת שירותים

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

    package my.package;
    interface IFoo {
        int doFoo();
    }

ב-Java, עליך להרחיב מהמחלקה הזו:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

ב-CPP backend:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

בקצה האחורי של NDK (שימו לב למרחב השמות הנוסף aidl ):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

בגב החלודה:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

או עם חלודה אסינכרית:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

רישום וקבלת שירותים

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

ב-Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

ב-CPP backend:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

בקצה האחורי של NDK (שימו לב למרחב השמות הנוסף aidl ):

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

בגב החלודה:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

בקצה האחורי של Rust אסינכרון, עם זמן ריצה עם חוט יחיד:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks will run on this thread.
    std::future::pending().await
}

הבדל חשוב אחד מהאפשרויות האחרות הוא שאנחנו לא קוראים join_thread_pool בעת שימוש ב-Rust אסינכרון ובזמן ריצה עם חוט יחיד. הסיבה לכך היא שאתה צריך לתת ל-Tokio שרשור שבו הוא יכול לבצע משימות שהורדו. בדוגמה זו, השרשור הראשי ישרת את המטרה הזו. כל משימות שנוצרו באמצעות tokio::spawn יבוצעו בשרשור הראשי.

ב-Rust backend האסינכרון, עם זמן ריצה מרובה הליכי:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

עם זמן הריצה מרובה הליכי Tokio, משימות שהורדו אינן מבוצעות בשרשור הראשי. לכן, הגיוני יותר לקרוא ל- join_thread_pool בשרשור הראשי כדי שהשרשור הראשי לא יהיה רק ​​בטל. עליך לעטוף את השיחה ב- block_in_place כדי לעזוב את ההקשר האסינכרון.

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

  • ב-Java, השתמש ב- android.os.IBinder::linkToDeath .
  • בקצה העורפי של CPP, השתמש android::IBinder::linkToDeath .
  • בקצה העורפי של NDK, השתמש AIBinder_linkToDeath .
  • ב-Rust, צור אובייקט DeathRecipient , ולאחר מכן קרא my_binder.link_to_death(&mut my_death_recipient) . שים לב שבגלל שנמען DeathRecipient הוא הבעלים של ההתקשרות חזרה, עליך לשמור על האובייקט הזה בחיים כל עוד אתה רוצה לקבל התראות.

מידע מתקשר

בעת קבלת קריאת איגד ליבה, מידע מתקשר זמין במספר ממשקי API. ה-PID (או מזהה התהליך) מתייחס למזהה תהליך לינוקס של התהליך ששולח עסקה. ה-UID (או User ID) מתייחס למזהה המשתמש של לינוקס. בעת קבלת שיחה חד-כיוונית, ה-PID המתקשר הוא 0. כאשר הם מחוץ להקשר של עסקת קלסר, פונקציות אלו מחזירות את ה-PID וה-UID של התהליך הנוכחי.

ב-Java backend:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

ב-CPP backend:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

בקצה האחורי של NDK:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

ב-Rust backend, בעת הטמעת הממשק, ציין את הדברים הבאים (במקום לאפשר לו כברירת מחדל):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

דוחות באגים וממשק API לניפוי באגים לשירותים

כשדוחות באגים פועלים (לדוגמה, עם adb bugreport ), הם אוספים מידע מכל רחבי המערכת כדי לסייע באיתור בעיות שונות. עבור שירותי AIDL, דיווחי באגים משתמשים ב- dumpsys הבינארי בכל השירותים הרשומים במנהל השירות כדי לזרוק את המידע שלהם לדוח הבאג. אתה יכול גם להשתמש dumpsys בשורת הפקודה כדי לקבל מידע משירות עם dumpsys SERVICE [ARGS] . ב-C++ ו-Java backends, אתה יכול לשלוט בסדר שבו השירותים יוזרקו על ידי שימוש בארגומנטים נוספים ל- addService . אתה יכול גם להשתמש dumpsys --pid SERVICE כדי לקבל את ה-PID של שירות בזמן ניפוי באגים.

כדי להוסיף פלט מותאם אישית לשירות שלך, אתה יכול לעקוף את שיטת dump באובייקט השרת שלך כמו שאתה מיישם כל שיטת IPC אחרת המוגדרת בקובץ AIDL. כשאתה עושה זאת, עליך להגביל את ההשלכה להרשאת האפליקציה android.permission.DUMP או להגביל את ההשלכה למזהי UID ספציפיים.

ב-Java backend:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

ב-CPP backend:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

בקצה האחורי של NDK:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

ב-Rust backend, בעת הטמעת הממשק, ציין את הדברים הבאים (במקום לאפשר לו כברירת מחדל):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

מקבל באופן דינמי מתאר ממשק

מתאר הממשק מזהה את סוג הממשק. זה שימושי בעת איתור באגים או כאשר יש לך קלסר לא ידוע.

ב-Java, אתה יכול לקבל את מתאר הממשק עם קוד כגון:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

ב-CPP backend:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

ה-NDK וה-Rust לא תומכים בפונקציונליות זו.

מקבל באופן סטטי מתאר ממשק

לפעמים (כגון בעת ​​רישום שירותי @VintfStability ), אתה צריך לדעת מהו מתאר הממשק באופן סטטי. ב-Java, אתה יכול לקבל את המתאר על ידי הוספת קוד כגון:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

ב-CPP backend:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

בקצה האחורי של NDK (שימו לב למרחב השמות הנוסף aidl ):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

בגב החלודה:

    aidl::my::package::BnFoo::get_descriptor()

Enum Range

בקצה עורפי מקורי, אתה יכול לחזור על הערכים האפשריים ש-enum יכול לקחת על עצמו. עקב שיקולי גודל קוד, זה לא נתמך ב-Java כרגע.

עבור enum MyEnum המוגדר ב-AIDL, האיטרציה מסופקת כדלקמן.

ב-CPP backend:

    ::android::enum_range<MyEnum>()

בקצה האחורי של NDK:

   ::ndk::enum_range<MyEnum>()

בגב החלודה:

    MyEnum::enum_values()

ניהול שרשור

כל מופע של libbinder בתהליך שומר על threadpool אחד. ברוב מקרי השימוש, זה צריך להיות בדיוק מאגר אשכול אחד, המשותף לכל הקצה האחורי. החריג היחיד לכך הוא כאשר קוד הספק עשוי לטעון עותק נוסף של libbinder כדי לדבר עם /dev/vndbinder . מכיוון שזה נמצא בצומת מקשר נפרד, ה-Threadpool אינו משותף.

עבור ה-Java backend, מאגר ה-Threadpool יכול רק להגדיל את גודלו (מכיוון שהוא כבר התחיל):

    BinderInternal.setMaxThreads(<new larger value>);

עבור ה-CPP backend, הפעולות הבאות זמינות:

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

באופן דומה, בקצה האחורי של NDK:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

בקצה האחורי של חלודה:

    binder::ProcessState::start_thread_pool();
    binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
    binder::ProcessState::join_thread_pool();

עם החלק האחורי של Rust אסינכרון, אתה צריך שני חיבורים: קלסר ו-Tokio. המשמעות היא שיישומים המשתמשים ב-Async Rust זקוקים לשיקולים מיוחדים, במיוחד כשמדובר בשימוש ב- join_thread_pool . עיין בסעיף רישום שירותים למידע נוסף על כך.

שמות שמורים

C++, Java ו-Rust שומרים כמה שמות כמילות מפתח או לשימוש ספציפי לשפה. בעוד ש-AIDL לא אוכפת הגבלות על סמך כללי שפה, שימוש בשמות שדות או סוגים התואמים לשם שמור עלול לגרום לכישלון קומפילציה עבור C++ או Java. עבור Rust, שם השדה או הסוג שונה באמצעות תחביר "מזהה גולמי", הנגיש באמצעות הקידומת r# .

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

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

שמות שיש להימנע מהם: * מילות מפתח C++ * מילות מפתח Java * מילות מפתח חלודה