แบ็กเอนด์ 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 เป็นโค้ด Stub ทั้งนี้ขึ้นอยู่กับแบ็กเอนด์ ดูรายละเอียดเพิ่มเติมเกี่ยวกับระบบบิลด์ได้ที่ข้อมูลอ้างอิงโมดูลของ 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. ประเภทสหภาพรองรับใน Android 12 ขึ้นไป
6. ใน Android 13 ขึ้นไป ระบบจะรองรับอาร์เรย์ขนาดคงที่ อาร์เรย์ขนาดคงที่อาจมีมิติข้อมูลได้หลายรายการ (เช่น int[3][4]
)
ในแบ็กเอนด์ของ Java อาร์เรย์ขนาดคงที่จะแสดงเป็นประเภทอาร์เรย์
7. หากต้องการสร้างอินสแตนซ์ออบเจ็กต์ Binder SharedRefBase
ให้ใช้ SharedRefBase::make\<My\>(... args ...)
ฟังก์ชันนี้จะสร้างออบเจ็กต์ std::shared_ptr\<T\>
ซึ่งมีการจัดการภายในด้วย ในกรณีที่ไฟล์ Binder เป็นของกระบวนการอื่น การสร้างออบเจ็กต์ด้วยวิธีอื่นจะทำให้เกิดการเป็นเจ้าของซ้ำ
ทิศทาง (เข้า/ออก/เข้า)
เมื่อระบุประเภทของอาร์กิวเมนต์ให้กับฟังก์ชัน คุณจะระบุอาร์กิวเมนต์เหล่านั้นเป็น in
, out
หรือ inout
ได้ ซึ่งจะควบคุมข้อมูลทิศทางที่จะส่งผ่าน
สำหรับการเรียก IPC in
เป็นทิศทางเริ่มต้นและระบุว่าส่งข้อมูลจากผู้โทรไปยังผู้รับสาย out
หมายความว่าระบบจะส่งข้อมูลจากผู้โทรไปยังผู้โทร inout
เป็นชุดค่าผสมของทั้ง 2 อย่าง อย่างไรก็ตาม ทีม Android ขอแนะนำให้คุณหลีกเลี่ยงการใช้ตัวระบุอาร์กิวเมนต์ inout
หากคุณใช้ inout
กับอินเทอร์เฟซที่มีเวอร์ชันและ Callee รุ่นเก่า ระบบจะรีเซ็ตช่องเพิ่มเติมซึ่งแสดงเฉพาะในผู้โทรเท่านั้นให้เป็นค่าเริ่มต้น ในกรณีของ 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
ซึ่งอาจเป็นค่าว่างสำหรับการอ่าน NDK และการเขียน CPP/NDK เสมอ ดูข้อมูลเพิ่มเติมเกี่ยวกับคำอธิบายประกอบ nullable
ได้ที่คำอธิบายประกอบใน AIDL
พาร์เซลที่กำหนดเอง
พัสดุที่กำหนดเองคือพาร์ทเมนต์ที่ใช้งานด้วยตนเองในแบ็กเอนด์เป้าหมาย ใช้พาร์เซลที่กำหนดเองเฉพาะเมื่อคุณพยายามเพิ่มการสนับสนุนในภาษาอื่นๆ สำหรับพัสดุที่กำหนดเองที่มีอยู่ซึ่งเปลี่ยนแปลงไม่ได้
ในการประกาศ Parcelable ที่กำหนดเองเพื่อให้ AIDL ทราบ การประกาศ Parcelable สำหรับ AIDL จะมีลักษณะดังนี้
package my.pack.age;
parcelable Foo;
โดยค่าเริ่มต้น การดำเนินการนี้จะประกาศพาร์ซ Java เมื่อมี 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 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);
จากนั้นคุณจะใช้พาร์เซลนี้เป็นประเภทในไฟล์ 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 ในหน้ารูปแบบ 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
เพื่อให้อยู่ในบริบทแบบไม่พร้อมกัน
ลิงก์ไปยังความตาย
คุณขอรับการแจ้งเตือนเมื่อบริการที่โฮสต์แฟ้มเสียชีวิตได้ วิธีนี้จะช่วยหลีกเลี่ยงการทำให้พร็อกซี 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 (หรือรหัสผู้ใช้) หมายถึงรหัสผู้ใช้ 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 รายการ สำหรับกรณีการใช้งานส่วนใหญ่ ชุดข้อความนี้ควรเป็น Threadpool รายการเดียวที่แชร์ระหว่างแบ็กเอนด์ทั้งหมด
ข้อยกเว้นเพียงอย่างเดียวคือเมื่อโค้ดของผู้ให้บริการอาจโหลดสำเนา 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 แล้ว คุณจะเปลี่ยนชื่อช่องได้อย่างปลอดภัยในขณะที่ยังคงใช้โปรโตคอลได้ คุณอาจต้องอัปเดตโค้ดเพื่อสร้างต่อ แต่โปรแกรมที่สร้างขึ้นแล้วจะยังคงทำงานร่วมกันต่อไปได้
ชื่อที่ควรหลีกเลี่ยง