แบ็กเอนด์ AIDL

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

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

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

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

สร้างระบบ

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

ระบบ Core Build

ในโมดูล 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 ซึ่งลิงก์ได้ ดูรายละเอียดเพิ่มเติมได้ที่ ตัวอย่างของ Rust AIDL

อินเทอร์เฟซตัวช่วย

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

ประเภท

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

ประเภท Java/AIDL ประเภท C++ ประเภท NDK ประเภทสนิม
บูลีน บูลีน บูลีน บูลีน
ไบต์ int8_t int8_t i8
อักขระ ตัวอักษร 16_t ตัวอักษร 16_t U16
Int int32_t int32_t i32
ยาว int64_t int64_t i64
float float float F32
คู่ คู่ คู่ F64
สตริง android::สตริง16 std::string สตริง
android.os.Parcelable android::Parcelable ไม่มี ไม่มี
IBinder android::IBinder ndk::SpAIBinder Binder::SpIBinder
พ[] 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 ไม่มี binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
ประเภทอินเทอร์เฟซ (T) android::sp<T> std::shared_ptr<T>7 แฟ้ม::รัดกุม
ประเภทพาร์เซล (T) T T T
ประเภทการรวม (T)5 T T T
แ [N] 6 std::array<T, N> std::array<T, N> [ท; น]

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

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

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

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

5. ประเภท Union รองรับใน Android 12 และ สูงขึ้น

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

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

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

เมื่อระบุประเภทของอาร์กิวเมนต์ให้กับฟังก์ชัน คุณสามารถระบุ เป็น 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

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

คุณสามารถใส่คำอธิบายประกอบประเภทที่อาจเป็นค่าว่างในแบ็กเอนด์ Java ด้วย @nullable เพื่อแสดงค่า Null ในแบ็กเอนด์ CPP และ NDK ในแบ็กเอนด์ของ Rust @nullable ประเภทแสดงเป็น Option<T> เซิร์ฟเวอร์ดั้งเดิมปฏิเสธค่า Null โดยค่าเริ่มต้น ข้อยกเว้นมีเพียงประเภท interface และ IBinder ซึ่งสามารถเป็นค่า Null ได้เสมอสำหรับการอ่าน NDK และการเขียน CPP/NDK หากต้องการดูข้อมูลเพิ่มเติม เกี่ยวกับคำอธิบายประกอบ nullable โปรดดู คำอธิบายประกอบใน AIDL

พาร์เซลที่กำหนดเอง

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

ในการประกาศไฟล์พาร์เซลที่กำหนดเองเพื่อให้ AIDL รู้เรื่องนี้ 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 (เวอร์ชันทดลอง AOSP) สำหรับการประกาศ 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);

จากนั้นคุณจะใช้พาร์เซลนี้เป็นไฟล์ประเภท AIDL ได้ แต่จะไม่ สร้างโดย AIDL ระบุโอเปอเรเตอร์ < และ == สำหรับแบ็กเอนด์ CPP/NDK พาร์เซลที่กำหนดเองเพื่อใช้ใน 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

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

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

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

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

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

ค่าข้อผิดพลาดในตัวหลายรายการสามารถใช้ในอินเทอร์เฟซ AIDL ใดก็ได้ แต่บางค่า ได้รับการปฏิบัติเป็นพิเศษ เช่น EX_UNSUPPORTED_OPERATION และ คุณสามารถใช้ EX_ILLEGAL_ARGUMENT เมื่ออธิบายเงื่อนไขของข้อผิดพลาด แต่ ต้องไม่ใช้ 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 แต่ แปลงให้เป็นข้อผิดพลาดสนิมแบบเนทีฟ (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 ดูตัวอย่าง Rust AIDL ในรูปแบบสนิมของ 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 เพื่อออกจากบริบทแบบไม่พร้อมกัน

คุณขอรับการแจ้งเตือนเมื่อบริการที่โฮสต์แฟ้มเสียชีวิตได้ วิธีนี้จะช่วยหลีกเลี่ยงการทำให้พร็อกซี Callback รั่วไหลหรือช่วยในการกู้คืนข้อผิดพลาดได้ ทำการเรียกเหล่านี้ในออบเจ็กต์พร็อกซีของ 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 เป็นเจ้าของ Callback คุณต้องทำให้ออบเจ็กต์นั้นทำงานอยู่ได้นาน เมื่อต้องการรับการแจ้งเตือน

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

เมื่อรับการติดต่อจาก Kernel Binder ข้อมูลผู้โทรจะใช้ได้ใน API หลายรายการ PID (หรือรหัสกระบวนการ) หมายถึงรหัสกระบวนการของ Linux ซึ่งเป็นกระบวนการส่งธุรกรรม UID (หรือ User-ID) อ้างถึง รหัสผู้ใช้ 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 คุณจะได้รับตัวบอกอินเทอร์เฟซพร้อมโค้ด เช่น

    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 ในกระบวนการจะมี Thread Pool อยู่ 1 รายการ สำหรับ Use Case ได้ ซึ่งควรเป็น Threadpool รายการเดียวที่แชร์ร่วมกันในแบ็กเอนด์ทั้งหมด ข้อยกเว้นเพียงอย่างเดียวคือเมื่อรหัสผู้ให้บริการอาจโหลดสำเนา libbinder อีกชุดหนึ่ง เพื่อคุยกับ /dev/vndbinder เนื่องจากข้อมูลนี้อยู่ในโหนด Binder ที่แยกต่างหาก ไม่แชร์ Threadpool

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

    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 แล้ว คุณสามารถ เปลี่ยนชื่อช่องขณะที่ยังคงรองรับโปรโตคอล คุณอาจต้องอัปเดต ในการสร้างโค้ดต่อไป แต่โปรแกรมที่สร้างไว้แล้ว ทำงานร่วมกัน

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