แบ็กเอนด์ AIDL เป็นเป้าหมายสำหรับการสร้างโค้ด stub เมื่อใช้ไฟล์ AIDL คุณจะใช้ไฟล์เหล่านี้ในภาษาใดภาษาหนึ่งโดยมีรันไทม์เฉพาะเสมอ คุณควรใช้แบ็กเอนด์ AIDL ที่แตกต่างกัน ทั้งนี้ขึ้นอยู่กับบริบท
AIDL มีแบ็กเอนด์ดังต่อไปนี้:
แบ็กเอนด์ | ภาษา | พื้นผิว API | สร้างระบบ |
---|---|---|---|
ชวา | ชวา | SDK/SystemApi (เสถียร*) | ทั้งหมด |
เอ็นดีเค | ซี++ | libbinder_ndk (เสถียร*) | Aidl_อินเทอร์เฟซ |
ซีพีพี | ซี++ | libbinder (ไม่เสถียร) | ทั้งหมด |
สนิม | สนิม | libbinder_rs (ไม่เสถียร) | Aidl_อินเทอร์เฟซ |
- พื้นผิว API เหล่านี้มีเสถียรภาพ แต่ API จำนวนมาก เช่น API สำหรับการจัดการบริการ สงวนไว้สำหรับการใช้งานแพลตฟอร์มภายใน และไม่พร้อมใช้งานสำหรับแอป สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้ AIDL ในแอพ โปรดดู เอกสารสำหรับนักพัฒนา
- แบ็กเอนด์ Rust เปิดตัวใน Android 12; แบ็กเอนด์ NDK พร้อมใช้งานตั้งแต่ Android 10
- ลัง Rust ถูกสร้างขึ้นบน
libbinder_ndk
APEX ใช้ลัง Binder เช่นเดียวกับคนอื่นๆ ในฝั่งระบบ ส่วนที่เป็นสนิมจะรวมอยู่ใน APEX และจัดส่งไว้ภายในนั้น ขึ้นอยู่กับlibbinder_ndk.so
บนพาร์ติชันระบบ
สร้างระบบ
มีสองวิธีในการรวบรวม AIDL ลงในโค้ด stub ทั้งนี้ขึ้นอยู่กับแบ็กเอนด์ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับระบบบิลด์ โปรดดูที่ การอ้างอิงโมดูล Soong
ระบบการสร้างแกนกลาง
ในโมดูล cc_
หรือ java_
Android.bp ใดๆ (หรือในโมดูล 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
เป็นการใช้งานอ้างอิงสำหรับประเภทต่างๆ เมื่อคุณสร้างอินเทอร์เฟซ ให้เรียกใช้ 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 |
ภายใน | int32_t | int32_t | i32 |
ยาว | int64_t | int64_t | i64 |
ลอย | ลอย | ลอย | f32 |
สองเท่า | สองเท่า | สองเท่า | f64 |
สตริง | หุ่นยนต์::String16 | มาตรฐาน::string | สตริง |
android.os.พาร์เซลได้ | android::พาร์เซลได้ | ไม่มี | ไม่มี |
ไอบินเดอร์ | android::IBinder | ndk::SpAIBinder | เครื่องผูก::SpIBinder |
ที[] | มาตรฐาน::เวกเตอร์<T> | มาตรฐาน::เวกเตอร์<T> | ใน: &[T] ออก: Vec<T> |
ไบต์[] | มาตรฐาน::เวกเตอร์<uint8_t> | มาตรฐาน::เวกเตอร์<int8_t> 1 | ใน: &[u8] ออก: Vec<u8> |
รายการ<T> | มาตรฐาน::เวกเตอร์<T> 2 | มาตรฐาน::เวกเตอร์<T> 3 | ใน: &[T] 4 ออก: Vec<T> |
FileDescriptor | หุ่นยนต์::ฐาน::unique_fd | ไม่มี | เครื่องผูก::พัสดุ::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | เครื่องผูก::พัสดุ::ParcelFileDescriptor |
ประเภทอินเทอร์เฟซ (T) | หุ่นยนต์::sp<T> | มาตรฐาน::shared_ptr<T> | เครื่องผูก::แข็งแรง |
ประเภทพัสดุ (T) | ต | ต | ต |
ประเภทสหภาพ (T) 5 | ต | ต | ต |
ที[เอ็น] 6 | มาตรฐาน::อาร์เรย์<T, N> | มาตรฐาน::อาร์เรย์<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 ขึ้นอยู่กับว่าเป็นอินพุต (อาร์กิวเมนต์) หรือเอาต์พุต (ค่าที่ส่งคืน)
5. รองรับประเภทยูเนี่ยนใน Android 12 ขึ้นไป
6. ใน Android 13 หรือสูงกว่า รองรับอาร์เรย์ขนาดคงที่ อาร์เรย์ขนาดคงที่สามารถมีได้หลายมิติ (เช่น int[3][4]
) ในแบ็กเอนด์ Java อาร์เรย์ที่มีขนาดคงที่จะแสดงเป็นประเภทอาร์เรย์
ทิศทาง (เข้า/ออก/เข้า)
เมื่อระบุประเภทของอาร์กิวเมนต์ให้กับฟังก์ชัน คุณสามารถระบุเป็น in
, out
หรือ inout
วิธีนี้จะควบคุมว่าข้อมูลทิศทางใดจะถูกส่งผ่านสำหรับการโทร IPC in
เป็นทิศทางเริ่มต้น และระบุว่าข้อมูลถูกส่งจากผู้โทรไปยังผู้โทร out
หมายความว่าข้อมูลถูกส่งจากผู้โทรไปยังผู้โทร inout
คือการรวมกันของทั้งสองสิ่งนี้ อย่างไรก็ตาม ทีมงาน Android ขอแนะนำให้คุณหลีกเลี่ยงการใช้ตัวระบุอาร์กิวเมนต์ inout
หากคุณใช้ inout
กับอินเทอร์เฟซที่มีเวอร์ชันและผู้เรียกรุ่นเก่า ฟิลด์เพิ่มเติมที่ปรากฏเฉพาะในตัวผู้เรียกจะถูกรีเซ็ตเป็นค่าเริ่มต้น ด้วยความเคารพต่อ Rust ประเภท inout
ปกติจะได้รับ &mut Vec<T>
และรายการ inout
ประเภทจะได้รับ &mut Vec<T>
UTF8/UTF16
ด้วยแบ็กเอนด์ CPP คุณสามารถเลือกได้ว่าจะให้สตริงเป็น utf-8 หรือ utf-16 ประกาศสตริงเป็น @utf8InCpp String
ใน AIDL เพื่อแปลงเป็น utf-8 โดยอัตโนมัติ แบ็กเอนด์ NDK และ Rust จะใช้สตริง utf-8 เสมอ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคำอธิบายประกอบ utf8InCpp
โปรดดู คำอธิบายประกอบใน AIDL
ความเป็นโมฆะ
คุณสามารถใส่คำอธิบายประกอบประเภทที่สามารถเป็นค่าว่างได้ในแบ็กเอนด์ Java ด้วย @nullable
เพื่อแสดงค่าว่างให้กับแบ็กเอนด์ CPP และ NDK ในแบ็กเอนด์ของ Rust ประเภท @nullable
เหล่านี้จะถูกเปิดเผยเป็น Option<T>
เซิร์ฟเวอร์ดั้งเดิมปฏิเสธค่า Null ตามค่าเริ่มต้น ข้อยกเว้นเพียงอย่างเดียวคือประเภท interface
และ IBinder
ซึ่งสามารถเป็นค่าว่างได้เสมอสำหรับการอ่าน NDK และการเขียน CPP/NDK สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคำอธิบาย nullable
โปรดดู คำอธิบายประกอบใน AIDL
พัสดุภัณฑ์แบบกำหนดเอง
พัสดุแบบกำหนดเอง คือพัสดุที่นำไปใช้ด้วยตนเองในแบ็กเอนด์เป้าหมาย ใช้การแบ่งส่วนแบบกำหนดเองได้เฉพาะเมื่อคุณพยายามเพิ่มการรองรับภาษาอื่นสำหรับส่วนแบ่งส่วนแบบกำหนดเองที่มีอยู่ซึ่งไม่สามารถเปลี่ยนแปลงได้
เพื่อที่จะประกาศพัสดุแบบกำหนดขึ้นเองเพื่อให้ AIDL ทราบ การสำแดงแบบพัสดุได้ของ 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 แบบกำหนดเองที่สามารถแยกส่วนได้ใน 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 เมื่อค่าเริ่มต้นหายไป ฟิลด์จะถูกเตรียมใช้งานเป็นค่าศูนย์สำหรับประเภทดั้งเดิมและ 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 เป็น const int
หรือ int
-backed enum
และจะไม่แยกวิเคราะห์โดย Binder
ใน Java ข้อผิดพลาดจะแมปกับข้อยกเว้น เช่น android.os.RemoteException
สำหรับข้อยกเว้นเฉพาะบริการ Java จะใช้ android.os.ServiceSpecificException
ร่วมกับข้อผิดพลาดที่ผู้ใช้กำหนด
รหัสเนทิฟใน Android ไม่ได้ใช้ข้อยกเว้น แบ็กเอนด์ CPP ใช้ android::binder::Status
แบ็กเอนด์ NDK ใช้ ndk::ScopedAStatus
ทุกวิธีที่สร้างโดย AIDL จะส่งคืนวิธีใดวิธีหนึ่งซึ่งแสดงถึงสถานะของวิธีการ แบ็กเอนด์ Rust ใช้ค่ารหัสข้อยกเว้นเดียวกันกับ NDK แต่แปลงเป็นข้อผิดพลาด Rust ดั้งเดิม ( StatusCode
, ExceptionCode
) ก่อนที่จะส่งให้กับผู้ใช้ สำหรับข้อผิดพลาดเฉพาะบริการ Status
หรือ ScopedAStatus
ที่ส่งคืนจะใช้ EX_SERVICE_SPECIFIC
ร่วมกับข้อผิดพลาดที่ผู้ใช้กำหนด
ประเภทข้อผิดพลาดในตัวสามารถพบได้ในไฟล์ต่อไปนี้:
แบ็กเอนด์ | คำนิยาม |
---|---|
ชวา | android/os/Parcel.java |
ซีพีพี | binder/Status.h |
เอ็นดีเค | android/binder_status.h |
สนิม | android/binder_status.h |
การใช้แบ็กเอนด์ต่างๆ
คำแนะนำเหล่านี้มีไว้สำหรับโค้ดแพลตฟอร์ม Android โดยเฉพาะ ตัวอย่างเหล่านี้ใช้ประเภทที่กำหนดไว้ my.package.IFoo
สำหรับคำแนะนำเกี่ยวกับวิธีใช้แบ็กเอนด์ Rust โปรดดู ตัวอย่าง Rust AIDL ในหน้า Android Rust Patterns
การนำเข้าประเภท
ไม่ว่าประเภทที่กำหนดจะเป็นอินเทอร์เฟซ แบ่งแยกได้ หรือแบบรวม คุณสามารถนำเข้าใน Java ได้:
import my.package.IFoo;
หรือในแบ็กเอนด์ CPP:
#include <my/package/IFoo.h>
หรือในแบ็กเอนด์ NDK (สังเกตเนมสเปซ aidl
เพิ่มเติม):
#include <aidl/my/package/IFoo.h>
หรือในแบ็กเอนด์ของ Rust:
use my_package::aidl::my::package::IFoo;
แม้ว่าคุณจะสามารถนำเข้าประเภทที่ซ้อนกันใน Java ได้ แต่ในแบ็กเอนด์ CPP/NDK คุณต้องรวมส่วนหัวสำหรับประเภทรูทด้วย ตัวอย่างเช่น เมื่อนำเข้า Bar
ประเภทที่ซ้อนกันซึ่งกำหนดไว้ใน my/package/IFoo.aidl
( IFoo
คือประเภทรูทของไฟล์) คุณต้องรวม <my/package/IFoo.h>
สำหรับแบ็กเอนด์ CPP (หรือ <aidl/my/package/IFoo.h>
สำหรับแบ็กเอนด์ NDK)
การดำเนินการบริการ
หากต้องการใช้บริการ คุณต้องสืบทอดจากคลาส stub ดั้งเดิม คลาสนี้อ่านคำสั่งจากไดรเวอร์ 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(())
}
}
หรือด้วย async 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 บางตัวยังตรวจสอบบริการ (ซึ่งหมายความว่า API จะกลับมาทันทีหากไม่มีบริการ) ตรวจสอบอินเท servicemanager
ที่เกี่ยวข้องเพื่อดูรายละเอียดที่แน่นอน การดำเนินการเหล่านี้สามารถทำได้เมื่อคอมไพล์กับแพลตฟอร์ม Android เท่านั้น
ในชวา:
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()
}
ในแบ็กเอนด์ async 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
เมื่อใช้ async Rust และรันไทม์แบบเธรดเดียว เนื่องจากคุณจำเป็นต้องให้เธรดแก่ Tokio ซึ่งสามารถดำเนินการงานที่เกิดได้ ในตัวอย่างนี้ เธรดหลักจะตอบสนองวัตถุประสงค์ดังกล่าว งานใดๆ ที่สร้างโดยใช้ tokio::spawn
จะดำเนินการบนเธรดหลัก
ในแบ็กเอนด์ async 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
- ใน Java ให้ใช้
android.os.IBinder::linkToDeath
- ในแบ็กเอนด์ CPP ให้ใช้
android::IBinder::linkToDeath
- ในแบ็กเอนด์ NDK ให้ใช้
AIBinder_linkToDeath
- ในแบ็กเอนด์ Rust ให้สร้างวัตถุ
DeathRecipient
จากนั้นเรียกmy_binder.link_to_death(&mut my_death_recipient)
โปรดทราบว่าเนื่องจากDeathRecipient
เป็นเจ้าของการโทรกลับ คุณต้องรักษาวัตถุนั้นให้คงอยู่ตราบเท่าที่คุณต้องการรับการแจ้งเตือน
ข้อมูลผู้โทร
เมื่อได้รับการเรียกเคอร์เนลไบเดอร์ ข้อมูลผู้เรียกจะมีอยู่ใน API หลายตัว PID (หรือ ID กระบวนการ) หมายถึง ID กระบวนการ Linux ของกระบวนการที่กำลังส่งธุรกรรม UID (หรือ ID ผู้ใช้) หมายถึง 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
บน commandline เพื่อรับข้อมูลจากบริการด้วย 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<()>
รับคำอธิบายอินเทอร์เฟซแบบไดนามิก
ตัวอธิบายอินเทอร์เฟซระบุประเภทของอินเทอร์เฟซ สิ่งนี้มีประโยชน์เมื่อทำการดีบั๊กหรือเมื่อคุณมีสารประสานที่ไม่รู้จัก
ใน 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 คุณสามารถรับ descriptor ได้โดยเพิ่มโค้ดเช่น:
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 สามารถทำได้ เนื่องจากการพิจารณาขนาดโค้ด จึงยังไม่รองรับ Java ในปัจจุบัน
สำหรับ enum MyEnum
ที่กำหนดใน AIDL การวนซ้ำมีดังต่อไปนี้
ในแบ็กเอนด์ CPP:
::android::enum_range<MyEnum>()
ในแบ็กเอนด์ NDK:
::ndk::enum_range<MyEnum>()
ในแบ็กเอนด์ของ Rust:
MyEnum::enum_values()
การจัดการเธรด
ทุกอินสแตนซ์ของ libbinder
ในกระบวนการจะรักษาหนึ่งเธรดพูล สำหรับกรณีการใช้งานส่วนใหญ่ นี่ควรเป็นเธรดพูลเดียวเท่านั้น และใช้ร่วมกันระหว่างแบ็กเอนด์ทั้งหมด ข้อยกเว้นเพียงอย่างเดียวคือเมื่อโค้ดผู้ขายอาจโหลดสำเนาของ libbinder
อีกชุดเพื่อพูดคุยกับ /dev/vndbinder
เนื่องจากสิ่งนี้อยู่บนโหนด Binder ที่แยกจากกัน เธรดพูลจึงไม่ถูกแชร์
สำหรับแบ็กเอนด์ 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();
ด้วยแบ็คเอนด์ async Rust คุณต้องมีเธรดพูลสองตัว: Binder และ Tokio ซึ่งหมายความว่าแอปพลิเคชันที่ใช้ async Rust จำเป็นต้องพิจารณาเป็นพิเศษ โดยเฉพาะอย่างยิ่งเมื่อพูดถึงการใช้ join_thread_pool
ดู ส่วนการลงทะเบียนบริการ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้
ชื่อที่สงวนไว้
C++, Java และ Rust สงวนชื่อบางส่วนไว้เป็นคีย์เวิร์ดหรือสำหรับการใช้งานเฉพาะภาษา แม้ว่า AIDL จะไม่บังคับใช้ข้อจำกัดตามกฎภาษา แต่การใช้ชื่อฟิลด์หรือประเภทที่ตรงกับชื่อที่สงวนไว้อาจส่งผลให้เกิดความล้มเหลวในการคอมไพล์สำหรับ C++ หรือ Java สำหรับ Rust ฟิลด์หรือประเภทจะถูกเปลี่ยนชื่อโดยใช้ไวยากรณ์ "raw identifier" ซึ่งสามารถเข้าถึงได้โดยใช้คำนำหน้า r#
เราขอแนะนำให้คุณหลีกเลี่ยงการใช้ชื่อที่สงวนไว้ในคำจำกัดความ AIDL ของคุณหากเป็นไปได้ เพื่อหลีกเลี่ยงการเชื่อมโยงที่ไม่เหมาะสมหรือความล้มเหลวในการรวบรวมโดยสิ้นเชิง
หากคุณมีชื่อที่สงวนไว้ในคำจำกัดความของ AIDL คุณสามารถเปลี่ยนชื่อฟิลด์ได้อย่างปลอดภัยในขณะที่ยังรองรับโปรโตคอลได้ คุณอาจต้องอัปเดตโค้ดของคุณเพื่อสร้างต่อ แต่โปรแกรมที่สร้างไว้แล้วจะยังคงทำงานร่วมกันต่อไป
ชื่อที่ควรหลีกเลี่ยง: * คำหลัก C++ * คำหลัก Java * คำหลักสนิม