แบ็กเอนด์ AIDL

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

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

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

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

ระบบบิลด์

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

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

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

aidl_interface

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

ประเภท

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

ประเภท Java/AIDL ประเภท C++ ประเภท NDK ประเภทสนิม
บูลีน bool bool bool
ไบต์ 8 int8_t int8_t i8
char char16_t char16_t u16
Int int32_t int32_t i32
ยาว int64_t int64_t i64
float float float f32
คู่ คู่ คู่ f64
สตริง android::String16 std::string อินพุต: &str
เอาต์พุต: สตริง
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<uint8_t> std::vector<int8_t>1 อิน: &[u8]
เอาต์: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 อิน: &[T]4
เอาต์: Vec<T>
FileDescriptor android::base::unique_fd ไม่มี binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
ประเภทอินเทอร์เฟซ (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
ประเภทที่แบ่งพาร์เซลได้ (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 ขึ้นไป

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

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

8. โปรดดูประเภท Java/AIDL byte[] ด้วย

ทิศทาง (เข้า/ออก/เข้าออก)

เมื่อระบุประเภทของอาร์กิวเมนต์ให้กับฟังก์ชัน คุณสามารถระบุเป็น in, out หรือ inout ตัวเลือกนี้ควบคุมทิศทางที่ข้อมูลจะส่งสำหรับคอล IPC in เป็นทิศทางเริ่มต้น และบ่งบอกว่ามีการส่งข้อมูลจากผู้เรียกไปยังผู้รับสาย out หมายความว่ามีการส่งข้อมูลจากผู้ที่รับสายไปยังผู้โทร inout คือทั้ง 2 รายการนี้รวมกัน อย่างไรก็ตาม ทีม Android ขอแนะนําให้คุณหลีกเลี่ยงการใช้ตัวระบุอาร์กิวเมนต์ inout หากคุณใช้ inout กับอินเทอร์เฟซเวอร์ชันที่มีและหมายเลขผู้รับสายเวอร์ชันเก่า ระบบจะรีเซ็ตช่องเพิ่มเติมที่มีเฉพาะในรายการที่เรียกให้ค่าเริ่มต้น สำหรับ Rust ประเภท inout ปกติจะรับ &mut Vec<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);
}

UTF8/UTF16

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

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

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

รายการที่แบ่งออกเป็นส่วนๆ ที่กำหนดเอง

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

หากต้องการประกาศ Parcelable ที่กําหนดเองเพื่อให้ AIDL ทราบ การประกาศ Parcelable ของ AIDL จะมีลักษณะดังนี้

    package my.pack.age;
    parcelable Foo;

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

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

ใน Android 15 หากต้องการประกาศ Rust แบบกำหนดเองที่แยกส่วนได้ใน 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 และ Parcelable ที่กําหนดเองเพื่อใช้ใน union

ค่าเริ่มต้น

โครงสร้างที่แยกส่วนได้สามารถประกาศค่าเริ่มต้นต่อช่องสำหรับค่าพื้นฐาน, String และอาร์เรย์ของประเภทเหล่านี้

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

ในแบ็กเอนด์ Java เมื่อไม่มีค่าเริ่มต้น ระบบจะเริ่มต้นค่าของฟิลด์เป็นค่า 0 สำหรับประเภทพื้นฐานและ 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.setSringField("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 เนื่องจากโครงสร้างพื้นฐานที่เกี่ยวข้องจะถือว่า EX_TRANSACTION_FAILED เป็นค่าพิเศษ ดูคำจำกัดความเฉพาะของแบ็กเอนด์เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับค่าในตัวเหล่านี้

หากอินเทอร์เฟซ AIDL ต้องใช้ค่าข้อผิดพลาดเพิ่มเติมที่ไม่ได้ครอบคลุมโดยประเภทข้อผิดพลาดในตัว อินเทอร์เฟซอาจใช้ข้อผิดพลาดในตัวที่เฉพาะเจาะจงสำหรับบริการพิเศษ ซึ่งอนุญาตให้รวมค่าข้อผิดพลาดที่เฉพาะเจาะจงสำหรับบริการที่ผู้ใช้กำหนดไว้ โดยทั่วไปแล้ว ข้อผิดพลาดเฉพาะบริการเหล่านี้จะกำหนดไว้ในอินเทอร์เฟซ AIDL เป็น enum ที่รองรับ const int หรือ int และจะไม่ได้รับการแยกวิเคราะห์โดย 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 ในหน้ารูปแบบ Rust ของ Android

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

คุณสามารถนําเข้าประเภทที่กําหนดใน 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)

บริการติดตั้งใช้งาน

หากต้องการใช้บริการ คุณต้องรับช่วงมาจากคลาสสตับเนทีฟ คลาสนี้จะอ่านคําสั่งจากไดรเวอร์ Binder และดําเนินการตามเมธอดที่คุณติดตั้งใช้งาน สมมติว่าคุณมีไฟล์ AIDL ดังนี้

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

ใน Java คุณต้องขยายจากคลาสนี้

    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 ที่มีรันไทม์แบบเธรดเดียว

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 ที่มีรันไทม์แบบหลายเธรด

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 ของกระบวนการที่ส่งธุรกรรม UID (หรือรหัสผู้ใช้) หมายถึงรหัสผู้ใช้ 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 ในบริการทั้งหมดที่ลงทะเบียนกับตัวจัดการบริการเพื่อถ่ายโอนข้อมูลไปยังรายงานข้อบกพร่อง นอกจากนี้ คุณยังใช้ 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 แต่ละรายการในกระบวนการจะดูแลรักษาพูลเธรด 1 รายการ สําหรับ Use Case ส่วนใหญ่ สิ่งนี้ควรเป็น Threadpool เพียง 1 รายการที่ใช้ร่วมกันในแบ็กเอนด์ทั้งหมด ข้อยกเว้นเพียงอย่างเดียวคือเมื่อโค้ดของผู้ให้บริการอาจโหลด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 แบบแอ็กซิงโครนัส คุณต้องมีพูลเธรด 2 ชุด ได้แก่ Binder และ Tokio ซึ่งหมายความว่าแอปที่ใช้ Rust แบบแอซิงค์ต้องพิจารณาเป็นพิเศษ โดยเฉพาะเมื่อใช้ join_thread_pool ดูข้อมูลเพิ่มเติมได้ที่ส่วนการลงทะเบียนบริการ

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

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

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

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

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