แบ็กเอนด์ 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 (เสถียร*) |
aidl_interface |
- แพลตฟอร์ม API เหล่านี้มีความเสถียร แต่ API จํานวนมาก เช่น API สําหรับการจัดการบริการ สงวนไว้สําหรับการใช้งานแพลตฟอร์มภายในและไม่พร้อมให้บริการในแอป ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้ AIDL ในแอปได้ที่ภาษาที่ใช้สื่อสารข้อมูลระหว่างคอมโพเนนต์ของ Android (AIDL)
- แบ็กเอนด์ Rust เปิดตัวใน Android 12 ส่วนแบ็กเอนด์ NDK มีให้บริการใน Android 10
- แพ็กเกจ Rust สร้างขึ้นจาก
libbinder_ndk
ซึ่งทำให้แพ็กเกจมีความเสถียรและนำไปใช้กับแพลตฟอร์มอื่นๆ ได้ APEX ใช้ Binder Crate ในลักษณะมาตรฐานในฝั่งระบบ ส่วน Rust จะรวมอยู่ใน APEX และจัดส่งภายใน ส่วนนี้ขึ้นอยู่กับlibbinder_ndk.so
ในพาร์ติชันระบบ
ระบบบิลด์
การคอมไพล์ AIDL เป็นโค้ดสแต็บมี 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
ต้องมีโครงสร้าง รายการที่แบ่งกลุ่มได้ต้องมีช่องโดยตรงและไม่ใช่การประกาศประเภทที่กําหนดในภาษาเป้าหมายโดยตรง จึงจะจัดเป็น Structured ดูว่า Structured AIDL ทำงานร่วมกับ AIDL ที่เสถียรได้อย่างไรที่หัวข้อ Structured AIDL กับ AIDL ที่เสถียร
ประเภท
โปรดใช้คอมไพเลอร์ aidl
เป็นการใช้งานอ้างอิงสำหรับประเภท
เมื่อสร้างอินเทอร์เฟซ ให้เรียกใช้ aidl --lang=<backend> ...
เพื่อดูไฟล์อินเทอร์เฟซที่ได้ เมื่อใช้โมดูล aidl_interface
คุณจะดูเอาต์พุตได้ใน out/soong/.intermediates/<path to module>/
ประเภท Java หรือ AIDL | ประเภท C++ | ประเภท NDK | ประเภทสนิม |
---|---|---|---|
boolean |
bool |
bool |
bool |
byte 8 |
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::vector 1 |
ขาเข้า: &[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 ขึ้นไป
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
บ่งบอกว่ามีการส่งข้อมูลจากผู้เรียกไปยังผู้รับ ตัวระบุ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
ความสามารถในการเว้นว่าง
คุณสามารถใส่คำอธิบายประกอบประเภทที่อาจเป็นค่าว่างได้โดยใช้ @nullable
ดูข้อมูลเพิ่มเติมเกี่ยวกับคำอธิบายประกอบ nullable
ได้ที่nullable
รายการที่แบ่งออกเป็นส่วนๆ ที่กำหนดเอง
Parcelable ที่กําหนดเองคือ 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
ค่าเริ่มต้น
Structured Parcelable สามารถประกาศค่าเริ่มต้นของแต่ละช่องสำหรับค่าพื้นฐาน 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 ได้ที่ตัวอย่าง 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)
ติดตั้งใช้งานอินเทอร์เฟซ
หากต้องการใช้อินเทอร์เฟซ คุณต้องรับช่วงมาจากคลาสสแต็บแบบเนทีฟ การใช้งานอินเทอร์เฟซมักเรียกว่าบริการเมื่อมีการลงทะเบียนกับเครื่องมือจัดการบริการหรือ android.app.ActivityManager
และเรียกว่าการเรียกกลับเมื่อมีการลงทะเบียนโดยไคลเอ็นต์ของบริการ อย่างไรก็ตาม มีการใช้ชื่อที่หลากหลายเพื่ออธิบายการใช้งานอินเทอร์เฟซ ทั้งนี้ขึ้นอยู่กับการใช้งานที่แน่นอน คลาสสแต็บจะอ่านคําสั่งจากไดรเวอร์ 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 ที่มีรันไทม์แบบเธรดเดียว
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 แบบแอ็กซิงโครนัสและรันไทม์แบบเธรดเดียว เนื่องจากคุณต้องให้ 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
ในบริการทั้งหมดที่ลงทะเบียนกับตัวจัดการบริการเพื่อถ่ายโอนข้อมูลไปยังรายงานข้อบกพร่อง นอกจากนี้ คุณยังใช้ 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 อยู่แล้ว คุณสามารถเปลี่ยนชื่อช่องได้อย่างปลอดภัยโดยที่โปรโตคอลยังคงเข้ากันได้ คุณอาจต้องอัปเดตโค้ดเพื่อบิลด์ต่อ แต่โปรแกรมที่บิลด์ไว้แล้วจะยังคงทํางานร่วมกันได้
ชื่อที่ควรหลีกเลี่ยง