แบ็กเอนด์ AIDL

แบ็กเอนด์ AIDL เป็นเป้าหมายสำหรับการสร้างโค้ด Stub ใช้ไฟล์ AIDL ในภาษาหนึ่งๆ กับรันไทม์ที่เฉพาะเจาะจงเสมอ คุณควรใช้แบ็กเอนด์ AIDL ที่แตกต่างกันตามบริบท

ในตารางต่อไปนี้ ความเสถียรของ API Surface หมายถึงความสามารถในการคอมไพล์โค้ดกับ API Surface นี้ในลักษณะที่สามารถส่งโค้ดแยกจากไบนารี system.img libbinder.so ได้

AIDL มีแบ็กเอนด์ต่อไปนี้

แบ็กเอนด์ ภาษา แพลตฟอร์ม API ระบบบิลด์
Java Java SDK หรือ SystemApi (เสถียร*) ทั้งหมด
NDK C++ libbinder_ndk (เสถียร*) aidl_interface
CPP C++ libbinder (ไม่เสถียร) ทั้งหมด
Rust Rust libbinder_rs (เสถียร*) aidl_interface
  • API เหล่านี้มีความเสถียร แต่ API หลายรายการ เช่น API สำหรับการจัดการบริการ สงวนไว้สำหรับการใช้งานแพลตฟอร์มภายในและไม่พร้อมใช้งานสำหรับแอป ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้ AIDL ในแอปได้ที่ ภาษาที่ใช้สื่อสารข้อมูลระหว่างคอมโพเนนต์ของ Android (AIDL) (AIDL)
  • เราได้เปิดตัวแบ็กเอนด์ Rust ใน Android 12 และแบ็กเอนด์ NDK พร้อมใช้งานตั้งแต่ Android 10
  • Crate ของ Rust สร้างขึ้นบน libbinder_ndk ซึ่งช่วยให้ Crate มีความเสถียรและพกพาได้ APEX ใช้ Binder Crate ในลักษณะมาตรฐาน ในฝั่งระบบ ส่วน Rust จะรวมอยู่ใน APEX และจัดส่ง ภายใน ส่วนนี้ขึ้นอยู่กับ libbinder_ndk.so ในพาร์ติชันระบบ

ระบบบิลด์

การคอมไพล์ AIDL เป็นโค้ด Stub ทำได้ 2 วิธี ทั้งนี้ขึ้นอยู่กับแบ็กเอนด์ ดูรายละเอียดเพิ่มเติมเกี่ยวกับระบบบิลด์ได้ที่ข้อมูลอ้างอิงโมดูล Soong

ระบบบิลด์หลัก

ใน cc_ หรือ java_ Android.bp module (หรือในAndroid.mk เทียบเท่า) คุณสามารถระบุไฟล์ AIDL (.aidl) เป็นไฟล์ต้นฉบับได้ ในกรณีนี้ ระบบจะใช้แบ็กเอนด์ Java หรือ CPP ของ AIDL (ไม่ใช่แบ็กเอนด์ NDK) และจะเพิ่มคลาสที่จะใช้ไฟล์ AIDL ที่เกี่ยวข้องลงในโมดูลโดยอัตโนมัติ คุณระบุตัวเลือกต่างๆ เช่น local_include_dirs (ซึ่งจะบอกเส้นทางรูทไปยังไฟล์ AIDL ในโมดูลนั้นแก่ระบบบิลด์) ในโมดูลเหล่านี้ได้ภายใต้กลุ่ม aidl:

แบ็กเอนด์ Rust ใช้ได้กับ Rust เท่านั้น rust_ จะได้รับการจัดการแตกต่างกันเนื่องจากไม่ได้ระบุไฟล์ AIDL เป็นไฟล์ต้นฉบับ แต่aidl_interfaceโมดูลจะสร้างrustlibที่เรียกว่า aidl_interface_name-rust ซึ่งสามารถลิงก์ได้ โปรดดูรายละเอียดในตัวอย่าง Rust AIDL

aidl_interface

ต้องจัดโครงสร้างประเภทที่ใช้กับaidl_interfaceระบบบิลด์ หากต้องการมีโครงสร้าง Parcelable ต้องมีฟิลด์โดยตรงและไม่ใช่การประกาศประเภทที่กำหนดโดยตรงในภาษาเป้าหมาย ดูว่า AIDL ที่มีโครงสร้าง เหมาะกับ AIDL ที่เสถียรอย่างไรได้ที่AIDL ที่มีโครงสร้างเทียบกับ AIDL ที่เสถียร

ประเภท

พิจารณาใช้aidlคอมไพเลอร์เป็นข้อมูลอ้างอิงในการใช้งานสำหรับประเภทต่างๆ เมื่อสร้างอินเทอร์เฟซ ให้เรียกใช้ aidl --lang=<backend> ... เพื่อดูไฟล์อินเทอร์เฟซที่ได้ เมื่อใช้โมดูล aidl_interface คุณจะดูเอาต์พุตได้ใน out/soong/.intermediates/<path to module>/

ประเภท Java หรือ AIDL ประเภท C++ ประเภท NDK ประเภทสนิม
boolean bool bool bool
byte8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string เข้า: &str
ออก: String
android.os.Parcelable android::Parcelable ไม่มี ไม่มี
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> เข้า: &[T]
ออก: Vec<T>
byte[] std::vector std::vector1 เข้า: &[u8]
ออก: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 ใน: In: &[T]4
ออก: Vec<T>
FileDescriptor android::base::unique_fd ไม่มี ไม่มี
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
ประเภทอินเทอร์เฟซ (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
ประเภท Parcelable (T) T T T
ประเภทสหภาพ (T)5 T T T
T[N]6 std::array<T, N> std::array<T, N> [T; N]

1. ใน Android 12 ขึ้นไป อาร์เรย์ไบต์จะใช้ uint8_t แทน int8_t เพื่อเหตุผลด้านความเข้ากันได้

2. แบ็กเอนด์ C++ รองรับ List<T> โดยที่ T เป็นหนึ่งใน String, IBinder, ParcelFileDescriptor หรือ Parcelable ใน Android 13 ขึ้นไป T จะเป็นประเภทที่ไม่ใช่ประเภทดั้งเดิมก็ได้ (รวมถึงประเภทอินเทอร์เฟซ) ยกเว้นอาร์เรย์ AOSP ขอแนะนำให้ใช้ประเภทอาร์เรย์ เช่น T[] เนื่องจาก ใช้ได้ในทุกแบ็กเอนด์

3. แบ็กเอนด์ NDK รองรับ List<T> โดยที่ T เป็นหนึ่งใน String, ParcelFileDescriptor หรือ Parcelable ใน Android 13 ขึ้นไป T จะเป็นประเภทที่ไม่ใช่ประเภทดั้งเดิมใดก็ได้ ยกเว้นอาร์เรย์

4. ระบบจะส่งประเภทสำหรับโค้ด Rust แตกต่างกันไปตามว่าโค้ดนั้นเป็นอินพุต (อาร์กิวเมนต์) หรือเอาต์พุต (ค่าที่ส่งคืน)

5. Android 12 ขึ้นไปรองรับประเภท Union

6. ใน Android 13 ขึ้นไป ระบบจะรองรับอาร์เรย์ที่มีขนาดคงที่ อาร์เรย์ขนาดคงที่อาจมีหลายมิติ (เช่น int[3][4]) ในแบ็กเอนด์ Java อาร์เรย์ขนาดคงที่จะแสดงเป็นประเภทอาร์เรย์

7. หากต้องการสร้างออบเจ็กต์ Binder SharedRefBase ให้ใช้ SharedRefBase::make\<My\>(... args ...) ฟังก์ชันนี้จะสร้างออบเจ็กต์ std::shared_ptr\<T\> ซึ่งมีการจัดการภายในด้วย ในกรณีที่ binder เป็นของกระบวนการอื่น การสร้างออบเจ็กต์ด้วยวิธีอื่นจะทำให้เกิด การเป็นเจ้าของซ้ำซ้อน

8. ดูประเภท Java หรือ AIDL byte[] ด้วย

ทิศทาง (in, out และ inout)

เมื่อระบุประเภทของอาร์กิวเมนต์ในฟังก์ชัน คุณจะระบุเป็น in, out หรือ inout ได้ ซึ่งจะควบคุมทิศทางที่ส่งข้อมูล สำหรับการเรียก IPC

  • ตัวระบุอาร์กิวเมนต์ in ระบุว่ามีการส่งข้อมูลจากผู้โทรไปยัง ผู้รับสาย ตัวระบุ in เป็นทิศทางเริ่มต้น แต่หากประเภทข้อมูลเป็น out ได้ด้วย คุณต้องระบุทิศทาง

  • out ตัวระบุอาร์กิวเมนต์หมายความว่ามีการส่งข้อมูลจากฟังก์ชันที่ถูกเรียกไปยัง ฟังก์ชันที่เรียก

  • inout ตัวระบุอาร์กิวเมนต์คือการรวมกันของทั้ง 2 อย่างนี้ อย่างไรก็ตาม เราขอแนะนำให้หลีกเลี่ยงการใช้ตัวระบุอาร์กิวเมนต์ inout หากคุณใช้ inout กับอินเทอร์เฟซที่มีการควบคุมเวอร์ชันและผู้รับสายที่เก่ากว่า ระบบจะรีเซ็ต ฟิลด์เพิ่มเติมที่มีเฉพาะในผู้โทรเป็น ค่าเริ่มต้น สำหรับ Rust ประเภท inout ปกติจะรับ &mut T และประเภทรายการ inout จะรับ &mut Vec<T>

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF-8 และ UTF-16

เมื่อใช้แบ็กเอนด์ CPP คุณจะเลือกได้ว่าสตริงจะเป็น UTF-8 หรือ UTF-16 ประกาศสตริงเป็น @utf8InCpp String ใน AIDL เพื่อแปลงเป็น UTF-8 โดยอัตโนมัติ แบ็กเอนด์ NDK และ Rust จะใช้สตริง UTF-8 เสมอ ดูข้อมูลเพิ่มเติมเกี่ยวกับutf8InCppคำอธิบายประกอบได้ที่ utf8InCpp

ความสามารถในการเว้นว่าง

คุณใส่คำอธิบายประกอบประเภทที่อาจเป็น Null ได้ด้วย @nullable ดูข้อมูลเพิ่มเติมเกี่ยวกับnullableคำอธิบายประกอบได้ที่ nullable

Parcelable ที่กำหนดเอง

Parcelable ที่กำหนดเองคือ Parcelable ที่ติดตั้งใช้งานด้วยตนเองในแบ็กเอนด์เป้าหมาย ใช้ Parcelable ที่กำหนดเองเฉพาะเมื่อคุณพยายามเพิ่มการรองรับภาษาอื่นๆ สำหรับ Parcelable ที่กำหนดเองที่มีอยู่ซึ่งเปลี่ยนแปลงไม่ได้

ตัวอย่างการประกาศ Parcelable ของ AIDL มีดังนี้

    package my.pack.age;
    parcelable Foo;

โดยค่าเริ่มต้น การประกาศนี้จะประกาศ Java parcelable ที่ my.pack.age.Foo เป็นคลาส Java ที่ใช้การติดตั้งใช้งานอินเทอร์เฟซ Parcelable

หากต้องการประกาศ CPP แบ็กเอนด์ที่กำหนดเองซึ่งเป็น Parcelable ใน 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);
    };

หากต้องการประกาศ Parcelable 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);
    };

ใน Android 15 สำหรับการประกาศ Rust Parcelable ที่กำหนดเองใน 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 จะไม่สร้าง Parcelable นี้ ระบุโอเปอเรเตอร์ < และ == สำหรับ CPP และ NDK Backend custom parcelables เพื่อใช้ใน union

ค่าเริ่มต้น

Structured Parcelable สามารถประกาศค่าเริ่มต้นต่อช่องสำหรับ Primitive, String และอาร์เรย์ของประเภทเหล่านี้

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

ในแบ็กเอนด์ Java เมื่อไม่มีค่าเริ่มต้น ระบบจะเริ่มต้นช่องเป็น ค่าศูนย์สำหรับประเภทข้อมูลพื้นฐานและ null สำหรับประเภทข้อมูลที่ไม่ใช่ประเภทข้อมูลพื้นฐาน

ในแบ็กเอนด์อื่นๆ ระบบจะเริ่มต้นฟิลด์ด้วยค่าเริ่มต้นเมื่อไม่ได้กำหนดค่าเริ่มต้น เช่น ในแบ็กเอนด์ C++ Stringฟิลด์ จะได้รับการเริ่มต้นเป็นสตริงว่าง และList<T>ฟิลด์จะได้รับการเริ่มต้นเป็นvector<T>ว่าง ระบบจะเริ่มต้นช่อง @nullable เป็นช่องค่า Null

สหภาพ

ระบบจะติดแท็กสหภาพ AIDL และฟีเจอร์ของสหภาพจะคล้ายกันในแบ็กเอนด์ทั้งหมด โดยจะสร้างขึ้นตามค่าเริ่มต้นของช่องแรก และมีวิธีเฉพาะภาษาในการโต้ตอบกับฟิลด์เหล่านั้น ดังนี้

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

ตัวอย่าง Java

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setStringField("abc");               // setter

ตัวอย่าง C++ และ NDK

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

ตัวอย่าง Rust

ใน Rust ยูเนียนจะได้รับการติดตั้งใช้งานเป็น Enum และไม่มี Getter และ Setter ที่ชัดเจน

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

การจัดการข้อผิดพลาด

ระบบปฏิบัติการ Android มีประเภทข้อผิดพลาดในตัวเพื่อให้บริการใช้เมื่อรายงาน ข้อผิดพลาด โดย Binder จะใช้ข้อมูลเหล่านี้ และบริการใดๆ ที่ใช้ อินเทอร์เฟซ Binder ก็ใช้ข้อมูลเหล่านี้ได้เช่นกัน การใช้งานได้รับการบันทึกไว้อย่างดีในคำจำกัดความ AIDL และไม่จำเป็นต้องมีสถานะหรือประเภทการคืนค่าที่ผู้ใช้กำหนด

พารามิเตอร์เอาต์พุตที่มีข้อผิดพลาด

เมื่อฟังก์ชัน AIDL รายงานข้อผิดพลาด ฟังก์ชันอาจไม่เริ่มต้นหรือ แก้ไขพารามิเตอร์เอาต์พุต โดยเฉพาะอย่างยิ่ง พารามิเตอร์เอาต์พุตอาจได้รับการแก้ไขหากเกิดข้อผิดพลาดระหว่างการแยกวิเคราะห์ แทนที่จะเกิดขึ้นระหว่างการประมวลผลธุรกรรมเอง โดยทั่วไป เมื่อได้รับข้อผิดพลาดจากฟังก์ชัน AIDL พารามิเตอร์ inout และ out ทั้งหมด รวมถึงค่าที่ส่งคืน (ซึ่งทําหน้าที่เหมือนพารามิเตอร์ out ในแบ็กเอนด์บางรายการ) ควรพิจารณาว่าอยู่ในสถานะที่ไม่แน่นอน

ค่าข้อผิดพลาดที่ควรใช้

ค่าข้อผิดพลาดในตัวหลายค่าสามารถใช้ในอินเทอร์เฟซ AIDL ใดก็ได้ แต่บางค่า จะได้รับการจัดการในลักษณะพิเศษ เช่น EX_UNSUPPORTED_OPERATION และ EX_ILLEGAL_ARGUMENT ใช้ได้เมื่ออธิบายเงื่อนไขข้อผิดพลาด แต่ห้ามใช้ EX_TRANSACTION_FAILED เนื่องจากโครงสร้างพื้นฐาน ที่อยู่เบื้องหลังจะถือว่าคำนี้เป็นคำพิเศษ โปรดดูคําจํากัดความเฉพาะของแบ็กเอนด์เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับค่าในตัวเหล่านี้

หากอินเทอร์เฟซ AIDL ต้องการค่าข้อผิดพลาดเพิ่มเติมที่ไม่ได้ครอบคลุมโดย ประเภทข้อผิดพลาดในตัว ก็สามารถใช้ข้อผิดพลาดในตัวพิเศษเฉพาะบริการ ที่อนุญาตให้รวมค่าข้อผิดพลาดเฉพาะบริการ ที่ผู้ใช้กำหนดได้ โดยปกติแล้ว ข้อผิดพลาดเฉพาะบริการเหล่านี้จะกำหนดไว้ ในอินเทอร์เฟซ AIDL เป็น const int หรือ int ที่มี enum และ Binder จะไม่แยกวิเคราะห์

ใน Java ข้อผิดพลาดจะแมปกับข้อยกเว้น เช่น android.os.RemoteException สำหรับข้อยกเว้นเฉพาะบริการ Java จะใช้ android.os.ServiceSpecificException ร่วมกับข้อผิดพลาดที่ผู้ใช้กำหนด

รหัสดั้งเดิมใน Android ไม่ใช้ข้อยกเว้น แบ็กเอนด์ 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
Rust android/binder_status.h

ใช้แบ็กเอนด์ต่างๆ

วิธีการเหล่านี้มีไว้สำหรับโค้ดแพลตฟอร์ม Android โดยเฉพาะ ตัวอย่างเหล่านี้ใช้ประเภทที่กำหนด my.package.IFoo ดูวิธีการใช้แบ็กเอนด์ Rust ได้ที่ตัวอย่าง AIDL ของ Rust ในรูปแบบ Android Rust

ประเภทการนำเข้า

ไม่ว่าประเภทที่กำหนดจะเป็นอินเทอร์เฟซ Parcelable หรือ Union คุณก็สามารถนำเข้า ใน Java ได้

import my.package.IFoo;

หรือในแบ็กเอนด์ของ CPP

#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 (หรือ <aidl/my/package/IFoo.h> สำหรับแบ็กเอนด์ NDK)

ติดตั้งใช้งานอินเทอร์เฟซ

หากต้องการใช้การเชื่อมต่อ คุณต้องรับค่าจากคลาส Stub ดั้งเดิม โดยทั่วไปแล้ว การใช้งานอินเทอร์เฟซจะเรียกว่าบริการเมื่อลงทะเบียนกับ Service Manager หรือ android.app.ActivityManager และเรียกว่าการเรียกกลับเมื่อไคลเอ็นต์ของบริการลงทะเบียน อย่างไรก็ตาม มีการใช้ชื่อต่างๆ เพื่ออธิบายการใช้งานอินเทอร์เฟซ ทั้งนี้ขึ้นอยู่กับการใช้งานที่แน่นอน คลาส Stub จะอ่านคำสั่งจากไดรเวอร์ Binder และเรียกใช้เมธอดที่คุณใช้ สมมติว่าคุณมีไฟล์ AIDL ดังนี้

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

ใน Java คุณต้องขยายจากคลาส Stub ที่สร้างขึ้น

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

ในแบ็กเอนด์ของ CPP ให้ทำดังนี้

    #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;
    }

ในแบ็กเอนด์ Rust

    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(())
        }
    }

หรือใช้ Rust แบบไม่พร้อมกัน

    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(())
        }
    }

ลงทะเบียนและรับบริการ

โดยปกติแล้ว บริการในแพลตฟอร์ม Android จะลงทะเบียนกับservicemanager กระบวนการ นอกจาก API ต่อไปนี้แล้ว API บางตัวจะตรวจสอบ บริการ (หมายความว่าจะแสดงผลทันทีหากบริการไม่พร้อมใช้งาน) โปรดดูรายละเอียดที่แน่นอนในservicemanagerอินเทอร์เฟซที่เกี่ยวข้อง คุณ จะดำเนินการเหล่านี้ได้เมื่อคอมไพล์กับแพลตฟอร์ม Android เท่านั้น

ใน 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 ให้ทำดังนี้

    #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")));

ในแบ็กเอนด์ Rust

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 แบบไม่พร้อมกันที่มีรันไทม์แบบ Single-Thread

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 run on this thread.
    std::future::pending().await
}

ความแตกต่างที่สำคัญอย่างหนึ่งจากตัวเลือกอื่นๆ คือคุณไม่ต้องเรียกใช้ join_thread_poolเมื่อใช้ Rust แบบอะซิงโครนัสและรันไทม์แบบ Single-Thread เนื่องจากคุณต้องระบุเธรดให้ Tokio เพื่อใช้ในการเรียกใช้งานงานที่แยกออกมา ใน ตัวอย่างต่อไปนี้ เทรดหลักจะทำหน้าที่ดังกล่าว งานที่สร้างขึ้น โดยใช้ tokio::spawn จะทำงานในเทรดหลัก

ในแบ็กเอนด์ Rust แบบอะซิงโครนัสที่มีรันไทม์แบบหลายเธรด

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 เพื่อออกจากบริบทแบบไม่พร้อมกัน

คุณขอรับการแจ้งเตือนเมื่อบริการที่โฮสต์ Binder หยุดทำงานได้ ซึ่งจะช่วยหลีกเลี่ยงการรั่วไหลของพร็อกซีการเรียกกลับหรือช่วยในการกู้คืนข้อผิดพลาด ทำการเรียกเหล่านี้ในออบเจ็กต์พร็อกซี Binder

  • ใน Java ให้ใช้ android.os.IBinder::linkToDeath
  • ในแบ็กเอนด์ของ CPP ให้ใช้ android::IBinder::linkToDeath
  • ในแบ็กเอนด์ NDK ให้ใช้ AIBinder_linkToDeath
  • ในแบ็กเอนด์ Rust ให้สร้างออบเจ็กต์ DeathRecipient จากนั้นเรียกใช้ my_binder.link_to_death(&mut my_death_recipient) โปรดทราบว่าเนื่องจาก DeathRecipient เป็นเจ้าของฟังก์ชันเรียกกลับ คุณจึงต้องเก็บออบเจ็กต์นั้นไว้ตราบเท่าที่ ต้องการรับการแจ้งเตือน

ข้อมูลผู้โทร

เมื่อได้รับ Binder ของเคอร์เนล ข้อมูลผู้โทรจะพร้อมใช้งานใน API หลายรายการ รหัสกระบวนการ (PID) หมายถึงรหัสกระบวนการ Linux ของ กระบวนการที่ส่งธุรกรรม รหัสผู้ใช้ (UI) หมายถึงรหัสผู้ใช้ Linux เมื่อรับสายแบบทางเดียว PID ของผู้โทรจะเป็น 0 นอกบริบทธุรกรรมของ Binder ฟังก์ชันเหล่านี้จะแสดง PID และ UID ของกระบวนการปัจจุบัน

ในแบ็กเอนด์ Java ให้ทำดังนี้

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

ในแบ็กเอนด์ของ CPP ให้ทำดังนี้

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

ในแบ็กเอนด์ NDK

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

ในแบ็กเอนด์ Rust เมื่อติดตั้งใช้งานอินเทอร์เฟซ ให้ระบุข้อมูลต่อไปนี้ (แทนที่จะปล่อยให้เป็นค่าเริ่มต้น)

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

รายงานข้อบกพร่องและ API การแก้ไขข้อบกพร่องสำหรับบริการ

เมื่อรายงานข้อบกพร่องทำงาน (เช่น ด้วย adb bugreport) ระบบจะรวบรวม ข้อมูลจากทั่วทั้งระบบเพื่อช่วยในการแก้ไขข้อบกพร่องต่างๆ สำหรับบริการ AIDL รายงานข้อบกพร่องจะใช้ไบนารี dumpsys ในบริการทั้งหมด ที่ลงทะเบียนกับ Service Manager เพื่อส่งข้อมูลไปยัง รายงานข้อบกพร่อง นอกจากนี้ คุณยังใช้ dumpsys ในบรรทัดคำสั่งเพื่อรับข้อมูล จากบริการที่มี dumpsys SERVICE [ARGS] ได้ด้วย ในแบ็กเอนด์ C++ และ Java คุณ สามารถควบคุมลำดับที่บริการจะได้รับการทิ้งโดยใช้อาร์กิวเมนต์เพิ่มเติม กับ addService นอกจากนี้ คุณยังใช้ dumpsys --pid SERVICE เพื่อรับ PID ของ บริการขณะแก้ไขข้อบกพร่องได้ด้วย

หากต้องการเพิ่มเอาต์พุตที่กำหนดเองลงในบริการ ให้ลบล้างdump เมธอดในออบเจ็กต์เซิร์ฟเวอร์เหมือนกับที่คุณใช้เมธอด IPC อื่นๆ ที่กำหนดไว้ในไฟล์ AIDL เมื่อดำเนินการนี้ ให้จำกัดการทิ้งข้อมูลไว้ที่สิทธิ์ของแอป android.permission.DUMP หรือจำกัดการทิ้งข้อมูลไว้ที่ UID ที่เฉพาะเจาะจง

ในแบ็กเอนด์ Java ให้ทำดังนี้

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

ในแบ็กเอนด์ของ CPP ให้ทำดังนี้

    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 เมื่อติดตั้งใช้งานอินเทอร์เฟซ ให้ระบุข้อมูลต่อไปนี้ (แทนที่จะปล่อยให้เป็นค่าเริ่มต้น)

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

ใช้พอยน์เตอร์อ่อน

คุณสามารถเก็บการอ้างอิงแบบอ่อนไปยังออบเจ็กต์ Binder ได้

แม้ว่า Java จะรองรับ WeakReference แต่ก็ไม่รองรับการอ้างอิง Binder ที่อ่อนแอ ในเลเยอร์เนทีฟ

ในแบ็กเอนด์ CPP ประเภทที่อ่อนแอคือ wp<IFoo>

ในแบ็กเอนด์ NDK ให้ใช้ ScopedAIBinder_Weak

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

ในแบ็กเอนด์ Rust ให้ใช้ WpIBinder หรือ Weak<IFoo>

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

รับตัวอธิบายอินเทอร์เฟซแบบไดนามิก

ตัวอธิบายอินเทอร์เฟซจะระบุประเภทของอินเทอร์เฟซ ซึ่งมีประโยชน์ เมื่อแก้ไขข้อบกพร่องหรือเมื่อคุณมี Binder ที่ไม่รู้จัก

ใน Java คุณสามารถรับตัวอธิบายอินเทอร์เฟซด้วยโค้ด เช่น

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

ในแบ็กเอนด์ของ CPP ให้ทำดังนี้

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

แบ็กเอนด์ NDK และ Rust ไม่รองรับความสามารถนี้

รับตัวอธิบายอินเทอร์เฟซแบบคงที่

ในบางครั้ง (เช่น เมื่อลงทะเบียนบริการ @VintfStability) คุณต้องทราบว่าตัวอธิบายอินเทอร์เฟซคืออะไรแบบคงที่ ใน Java คุณสามารถรับ ตัวอธิบายได้โดยการเพิ่มโค้ด เช่น

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

ในแบ็กเอนด์ของ CPP ให้ทำดังนี้

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

ในแบ็กเอนด์ NDK (โปรดสังเกตเนมสเปซ aidl เพิ่มเติม)

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

ในแบ็กเอนด์ Rust

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

ช่วง enum

ในแบ็กเอนด์ดั้งเดิม คุณสามารถวนซ้ำค่าที่เป็นไปได้ที่ enum สามารถใช้ได้ เนื่องจากข้อควรพิจารณาเกี่ยวกับขนาดโค้ด Java จึงไม่รองรับฟีเจอร์นี้

สำหรับ Enum MyEnum ที่กำหนดไว้ใน AIDL การทำซ้ำจะมีลักษณะดังนี้

ในแบ็กเอนด์ของ CPP ให้ทำดังนี้

    ::android::enum_range<MyEnum>()

ในแบ็กเอนด์ NDK

   ::ndk::enum_range<MyEnum>()

ในแบ็กเอนด์ Rust

    MyEnum::enum_values()

การจัดการชุดข้อความ

อินสแตนซ์ทั้งหมดของ libbinder ในกระบวนการจะใช้ Threadpool เดียว สำหรับกรณีการใช้งานส่วนใหญ่ ควรมี Threadpool เพียงรายการเดียวที่ใช้ร่วมกันใน Backend ทั้งหมด ข้อยกเว้นเพียงอย่างเดียวคือหากรหัสของผู้ให้บริการโหลดสำเนาอื่นของ libbinder เพื่อพูดคุยกับ /dev/vndbinder ซึ่งอยู่ในโหนด Binder แยกต่างหาก จึงไม่มีการแชร์ Threadpool

สำหรับแบ็กเอนด์ Java พูลของเธรดจะเพิ่มขนาดได้เท่านั้น (เนื่องจาก เริ่มทำงานแล้ว)

    BinderInternal.setMaxThreads(<new larger value>);

สำหรับการดำเนินการแบ็กเอนด์ CPP จะมีการดำเนินการต่อไปนี้

    // 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();

ในแบ็กเอนด์ Rust

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

แบ็กเอนด์ Rust แบบไม่พร้อมกันต้องใช้ Threadpool 2 รายการ ได้แก่ Binder และ Tokio ซึ่งหมายความว่าแอปที่ใช้ Rust แบบไม่พร้อมกันต้องได้รับการพิจารณาเป็นพิเศษ โดยเฉพาะอย่างยิ่งเมื่อพูดถึงการใช้ join_thread_pool ดูข้อมูลเพิ่มเติมได้ที่ส่วนการลงทะเบียนบริการ

ชื่อที่สงวนไว้

C++, Java และ Rust จะสงวนชื่อบางชื่อไว้เป็นคีย์เวิร์ดหรือสำหรับการใช้งานเฉพาะภาษา แม้ว่า AIDL จะไม่ได้บังคับใช้ข้อจำกัดตามกฎภาษา แต่การใช้ชื่อฟิลด์หรือชื่อประเภทที่ตรงกับชื่อที่สงวนไว้อาจทำให้การคอมไพล์ล้มเหลวสำหรับ C++ หรือ Java สำหรับ Rust ระบบจะเปลี่ยนชื่อฟิลด์หรือประเภทโดยใช้ ไวยากรณ์ตัวระบุดิบ ซึ่งเข้าถึงได้โดยใช้คำนำหน้า r#

เราขอแนะนำให้หลีกเลี่ยงการใช้ชื่อที่สงวนไว้ในคำจำกัดความ AIDL หากเป็นไปได้ เพื่อหลีกเลี่ยงการเชื่อมโยงที่ไม่สะดวกหรือการคอมไพล์ล้มเหลวโดยสิ้นเชิง

หากมีชื่อที่สงวนไว้ในคำจำกัดความ AIDL อยู่แล้ว คุณสามารถเปลี่ยนชื่อฟิลด์ได้อย่างปลอดภัย ในขณะที่ยังคงใช้โปรโตคอลร่วมกันได้ คุณอาจต้องอัปเดตโค้ดเพื่อสร้างต่อไป แต่โปรแกรมที่สร้างไว้แล้วจะยังคงทำงานร่วมกันได้

ชื่อที่ควรหลีกเลี่ยง