Phần phụ trợ AIDL là mục tiêu để tạo mã gốc. Luôn sử dụng các tệp AIDL bằng một ngôn ngữ cụ thể với một thời gian chạy cụ thể. Tuỳ thuộc vào ngữ cảnh, bạn nên sử dụng các phần phụ trợ AIDL khác nhau.
Trong bảng sau, độ ổn định của giao diện API đề cập đến khả năng biên dịch mã dựa trên giao diện API này theo cách mà mã có thể được phân phối độc lập với tệp nhị phân system.img
libbinder.so
.
AIDL có các chương trình phụ trợ sau:
Phụ trợ | Ngôn ngữ | Nền tảng API | Hệ thống xây dựng |
---|---|---|---|
Java | Java | SDK hoặc SystemApi (ổn định*) |
Tất cả |
NDK | C++ | libbinder_ndk (ổn định*) |
aidl_interface |
CPP | C++ | libbinder (không ổn định) |
Tất cả |
Rust | Rust | libbinder_rs (ổn định*) |
aidl_interface |
- Các giao diện API này ổn định, nhưng nhiều API (chẳng hạn như API để quản lý dịch vụ) được dành riêng cho việc sử dụng nền tảng nội bộ và không có sẵn cho các ứng dụng. Để biết thêm thông tin về cách sử dụng AIDL trong các ứng dụng, hãy xem bài viết Ngôn ngữ định nghĩa giao diện Android (AIDL).
- Phần phụ trợ Rust được ra mắt trong Android 12; phần phụ trợ NDK đã có sẵn kể từ Android 10.
- Rust crate được xây dựng dựa trên
libbinder_ndk
, cho phép crate này ổn định và di động. APEX sử dụng binder crate theo cách tiêu chuẩn ở phía hệ thống. Phần Rust được gói vào một APEX và được vận chuyển bên trong APEX đó. Phần này phụ thuộc vàolibbinder_ndk.so
trên phân vùng hệ thống.
Hệ thống xây dựng
Tuỳ thuộc vào phần phụ trợ, có hai cách để biên dịch AIDL thành mã gốc. Để biết thêm thông tin về các hệ thống bản dựng, hãy xem Tài liệu tham khảo về các mô-đun Soong.
Hệ thống xây dựng cốt lõi
Trong mọi cc_
hoặc java_
Android.bp module
(hoặc trong các tệp Android.mk
tương đương), bạn có thể chỉ định các tệp AIDL (.aidl
) làm tệp nguồn. Trong trường hợp này, các phần phụ trợ Java hoặc CPP của AIDL sẽ được dùng (không phải phần phụ trợ NDK) và các lớp dùng các tệp AIDL tương ứng sẽ được tự động thêm vào mô-đun. Bạn có thể chỉ định các lựa chọn như local_include_dirs
(cho biết hệ thống xây dựng đường dẫn gốc đến các tệp AIDL trong mô-đun đó) trong các mô-đun này trong nhóm aidl:
.
Phần phụ trợ Rust chỉ dùng được với Rust. Các mô-đun rust_
được xử lý theo cách khác là các tệp AIDL không được chỉ định làm tệp nguồn. Thay vào đó, mô-đun aidl_interface
tạo ra một rustlib
có tên là aidl_interface_name-rust
. Mô-đun này có thể được liên kết. Để biết thông tin chi tiết, hãy xem ví dụ về AIDL Rust.
aidl_interface
Các loại được dùng với hệ thống bản dựng aidl_interface
phải có cấu trúc. Để được cấu trúc, các đối tượng có thể phân chia phải chứa các trường trực tiếp và không phải là khai báo các loại được xác định trực tiếp bằng ngôn ngữ đích. Để biết cách AIDL có cấu trúc phù hợp với AIDL ổn định, hãy xem bài viết AIDL có cấu trúc so với AIDL ổn định.
Loại
Hãy xem xét trình biên dịch aidl
như một cách triển khai tham chiếu cho các loại.
Khi bạn tạo một giao diện, hãy gọi aidl --lang=<backend> ...
để xem tệp giao diện kết quả. Khi sử dụng mô-đun aidl_interface
, bạn có thể xem đầu ra trong out/soong/.intermediates/<path to module>/
.
Loại Java hoặc AIDL | Loại C++ | Loại NDK | Loại gỉ sét |
---|---|---|---|
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 |
Đầu vào: &str Đầu ra: String |
android.os.Parcelable |
android::Parcelable |
Không áp dụng | Không áp dụng |
IBinder |
android::IBinder |
ndk::SpAIBinder |
binder::SpIBinder |
T[] |
std::vector<T> |
std::vector<T> |
Đầu vào: &[T] Đầu ra: Vec<T> |
byte[] |
std::vector |
std::vector 1 |
Đầu vào: &[u8] Đầu ra: Vec<u8> |
List<T> |
std::vector<T> 2 |
std::vector<T> 3 |
Trong: In: &[T] 4Ngoài: Vec<T> |
FileDescriptor |
android::base::unique_fd |
Không áp dụng | Không áp dụng |
ParcelFileDescriptor |
android::os::ParcelFileDescriptor |
ndk::ScopedFileDescriptor |
binder::parcel::ParcelFileDescriptor |
Loại giao diện (T ) |
android::sp<T> |
std::shared_ptr<T> 7 |
binder::Strong |
Loại có thể đóng gói (T ) |
T |
T |
T |
Loại hợp (T )5 |
T |
T |
T |
T[N] 6 |
std::array<T, N> |
std::array<T, N> |
[T; N] |
1. Trong Android 12 trở lên, các mảng byte sử dụng uint8_t
thay vì int8_t
vì lý do tương thích.
2. Phần phụ trợ C++ hỗ trợ List<T>
, trong đó T
là một trong các String
, IBinder
, ParcelFileDescriptor
hoặc có thể đóng gói. Trong Android 13 trở lên, T
có thể là bất kỳ loại không nguyên thuỷ nào (bao gồm cả các loại giao diện) ngoại trừ mảng. AOSP đề xuất sử dụng các loại mảng như T[]
vì chúng hoạt động ở mọi chương trình phụ trợ.
3. Phần phụ trợ NDK hỗ trợ List<T>
, trong đó T
là một trong các String
, ParcelFileDescriptor
hoặc parcelable. Trong Android 13 trở lên, T
có thể là bất kỳ loại nào không phải là loại nguyên thuỷ, ngoại trừ mảng.
4. Các loại được truyền theo cách khác nhau cho mã Rust, tuỳ thuộc vào việc chúng là đầu vào (một đối số) hay đầu ra (một giá trị được trả về).
5. Các loại hợp nhất được hỗ trợ trong Android 12 trở lên.
6. Trong Android 13 trở lên, các mảng có kích thước cố định sẽ được hỗ trợ. Mảng có kích thước cố định có thể có nhiều phương diện (ví dụ: int[3][4]
). Trong phần phụ trợ Java, mảng có kích thước cố định được biểu thị dưới dạng các loại mảng.
7. Để tạo thực thể cho một đối tượng liên kết SharedRefBase
, hãy dùng SharedRefBase::make\<My\>(... args ...)
. Hàm này tạo một đối tượng std::shared_ptr\<T\>
(cũng được quản lý nội bộ) trong trường hợp trình liên kết thuộc sở hữu của một quy trình khác. Việc tạo đối tượng theo những cách khác sẽ gây ra tình trạng trùng lặp quyền sở hữu.
8. Xem thêm loại Java hoặc AIDL byte[]
.
Hướng (vào, ra và vào/ra)
Khi chỉ định các loại đối số cho hàm, bạn có thể chỉ định chúng là in
, out
hoặc inout
. Chế độ này kiểm soát hướng truyền thông tin cho một lệnh gọi IPC.
Bộ chỉ định đối số
in
cho biết dữ liệu được truyền từ phương thức gọi đến phương thức được gọi. Bộ chỉ địnhin
là hướng mặc định, nhưng nếu các loại dữ liệu cũng có thể làout
, thì bạn phải chỉ định hướng.Bộ chỉ định đối số
out
có nghĩa là dữ liệu được truyền từ phương thức được gọi đến phương thức gọi.Bộ chỉ định đối số
inout
là sự kết hợp của cả hai. Tuy nhiên, bạn nên tránh sử dụng bộ chỉ định đối sốinout
. Nếu bạn sử dụnginout
với một giao diện có phiên bản và một đối tượng được gọi cũ, thì các trường bổ sung chỉ có trong đối tượng gọi sẽ được đặt lại về giá trị mặc định. Đối với Rust, loạiinout
thông thường sẽ nhận được&mut T
và loạiinout
danh sách sẽ nhận được&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 và UTF-16
Với phần phụ trợ CPP, bạn có thể chọn xem các chuỗi có phải là UTF-8 hay UTF-16 hay không.
Khai báo các chuỗi dưới dạng @utf8InCpp String
trong AIDL để tự động chuyển đổi các chuỗi đó thành UTF-8. NDK và các phần phụ trợ Rust luôn sử dụng các chuỗi UTF-8. Để biết thêm thông tin về chú thích utf8InCpp
, hãy xem utf8InCpp.
Tính chất rỗng
Bạn có thể chú thích các loại có thể rỗng bằng @nullable
.
Để biết thêm thông tin về chú thích nullable
, hãy xem nullable.
Parcelable tuỳ chỉnh
Đối tượng có thể chuyển đổi tuỳ chỉnh là đối tượng có thể chuyển đổi được triển khai theo cách thủ công trong một phần phụ trợ mục tiêu. Chỉ sử dụng các đối tượng có thể phân phối tuỳ chỉnh khi bạn đang cố gắng thêm chế độ hỗ trợ cho các ngôn ngữ khác đối với một đối tượng có thể phân phối tuỳ chỉnh hiện có mà bạn không thể thay đổi.
Sau đây là ví dụ về khai báo AIDL có thể chuyển đổi thành parcel:
package my.pack.age;
parcelable Foo;
Theo mặc định, thao tác này sẽ khai báo một đối tượng có thể chuyển đổi tuần tự Java, trong đó my.pack.age.Foo
là một lớp Java triển khai giao diện Parcelable
.
Để khai báo một đối tượng có thể chuyển đổi CPP tuỳ chỉnh trong AIDL, hãy sử dụng cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
Quy trình triển khai C++ trong my/pack/age/Foo.h
có dạng như sau:
#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);
};
Để khai báo một đối tượng có thể chuyển đổi thành gói NDK tuỳ chỉnh trong AIDL, hãy dùng ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
Quy trình triển khai NDK trong android/pack/age/Foo.h
có dạng như sau:
#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);
};
Trong Android 15, để khai báo một đối tượng có thể chuyển đổi thành gói Rust tuỳ chỉnh trong AIDL, hãy dùng rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
Cách triển khai Rust trong rust_crate/src/lib.rs
có dạng như sau:
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);
Sau đó, bạn có thể dùng đối tượng có thể chuyển đổi này làm một loại trong tệp AIDL, nhưng AIDL sẽ không tạo đối tượng này. Cung cấp các toán tử <
và ==
cho các đối tượng có thể chuyển đổi thành gói tuỳ chỉnh CPP và NDK để sử dụng chúng trong union
.
Giá trị mặc định
Các đối tượng có thể chuyển đổi tuần tự có cấu trúc có thể khai báo giá trị mặc định cho mỗi trường đối với các kiểu dữ liệu nguyên thuỷ, các trường String
và mảng thuộc các loại này.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Trong phần phụ trợ Java, khi thiếu giá trị mặc định, các trường sẽ được khởi tạo dưới dạng giá trị 0 cho các kiểu nguyên thuỷ và null
cho các kiểu không nguyên thuỷ.
Trong các phần phụ trợ khác, các trường được khởi chạy bằng các giá trị khởi chạy mặc định khi các giá trị mặc định không được xác định. Ví dụ: trong phần phụ trợ C++, các trường String
được khởi chạy dưới dạng một chuỗi trống và các trường List<T>
được khởi chạy dưới dạng một vector<T>
trống. Các trường @nullable
được khởi tạo dưới dạng các trường có giá trị rỗng.
Liên minh
Các union AIDL được gắn thẻ và các tính năng của chúng tương tự nhau ở mọi phần phụ trợ. Chúng được tạo thành từ giá trị mặc định của trường đầu tiên và có một cách tương tác dành riêng cho ngôn ngữ:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Ví dụ về Java
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setStringField("abc"); // setter
Ví dụ về C++ và 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)
Ví dụ về Rust
Trong Rust, các union được triển khai dưới dạng enum và không có phương thức truy xuất và phương thức thiết lập rõ ràng.
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
Xử lý lỗi
Hệ điều hành Android cung cấp các loại lỗi tích hợp để các dịch vụ sử dụng khi báo cáo lỗi. Các đối tượng này được các liên kết sử dụng và có thể được mọi dịch vụ triển khai giao diện liên kết sử dụng. Việc sử dụng các loại này được ghi lại đầy đủ trong định nghĩa AIDL và không yêu cầu bất kỳ trạng thái hoặc loại dữ liệu trả về nào do người dùng xác định.
Tham số đầu ra có lỗi
Khi một hàm AIDL báo cáo lỗi, hàm đó có thể không khởi chạy hoặc sửa đổi các tham số đầu ra. Cụ thể, các tham số đầu ra có thể bị sửa đổi nếu lỗi xảy ra trong quá trình huỷ đóng gói, thay vì xảy ra trong quá trình xử lý giao dịch. Nói chung, khi gặp lỗi từ một hàm AIDL, tất cả các tham số inout
và out
cũng như giá trị trả về (hoạt động như một tham số out
trong một số phần phụ trợ) đều được coi là ở trạng thái không xác định.
Nên sử dụng giá trị lỗi nào
Bạn có thể dùng nhiều giá trị lỗi tích hợp sẵn trong mọi giao diện AIDL, nhưng một số giá trị được xử lý theo cách đặc biệt. Ví dụ: bạn có thể sử dụng EX_UNSUPPORTED_OPERATION
và EX_ILLEGAL_ARGUMENT
khi chúng mô tả điều kiện lỗi, nhưng bạn không được sử dụng EX_TRANSACTION_FAILED
vì cơ sở hạ tầng cơ bản sẽ xử lý đặc biệt. Hãy kiểm tra các định nghĩa cụ thể về phần phụ trợ để biết thêm thông tin về những giá trị tích hợp này.
Nếu giao diện AIDL yêu cầu các giá trị lỗi bổ sung không thuộc các loại lỗi tích hợp, thì giao diện đó có thể sử dụng lỗi tích hợp đặc biệt dành riêng cho dịch vụ cho phép đưa vào giá trị lỗi dành riêng cho dịch vụ do người dùng xác định. Những lỗi dành riêng cho dịch vụ này thường được xác định trong giao diện AIDL dưới dạng const int
hoặc int
được hỗ trợ enum
và không được trình liên kết phân tích cú pháp.
Trong Java, các lỗi liên kết đến các trường hợp ngoại lệ, chẳng hạn như android.os.RemoteException
. Đối với các trường hợp ngoại lệ dành riêng cho dịch vụ, Java sử dụng android.os.ServiceSpecificException
cùng với lỗi do người dùng xác định.
Mã gốc trong Android không sử dụng các ngoại lệ. Phần phụ trợ CPP sử dụng android::binder::Status
. Phần phụ trợ NDK sử dụng ndk::ScopedAStatus
. Mọi phương thức do AIDL tạo đều trả về một trong những phương thức này, biểu thị trạng thái của phương thức. Phần phụ trợ Rust sử dụng các giá trị mã ngoại lệ giống như NDK, nhưng sẽ chuyển đổi các giá trị đó thành lỗi Rust gốc (StatusCode
, ExceptionCode
) trước khi gửi cho người dùng. Đối với các lỗi dành riêng cho dịch vụ, Status
hoặc ScopedAStatus
được trả về sẽ sử dụng EX_SERVICE_SPECIFIC
cùng với lỗi do người dùng xác định.
Bạn có thể tìm thấy các loại lỗi tích hợp trong các tệp sau:
Phụ trợ | Định nghĩa |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
Sử dụng nhiều phần phụ trợ
Đây là hướng dẫn dành riêng cho mã nền tảng Android. Các ví dụ này sử dụng một loại được xác định, my.package.IFoo
. Để biết hướng dẫn về cách sử dụng phần phụ trợ Rust, hãy xem ví dụ về AIDL Rust trong các mẫu Rust trên Android.
Loại nhập
Cho dù loại được xác định là giao diện, có thể phân chia hoặc liên kết, bạn đều có thể nhập loại đó trong Java:
import my.package.IFoo;
Hoặc trong phần phụ trợ CPP:
#include <my/package/IFoo.h>
Hoặc trong phần phụ trợ NDK (lưu ý không gian tên aidl
bổ sung):
#include <aidl/my/package/IFoo.h>
Hoặc trong phần phụ trợ Rust:
use my_package::aidl::my::package::IFoo;
Mặc dù có thể nhập một loại lồng nhau trong Java, nhưng trong các phần phụ trợ CPP và NDK, bạn phải thêm tiêu đề cho loại gốc của loại đó. Ví dụ: khi nhập một loại lồng nhau Bar
được xác định trong my/package/IFoo.aidl
(IFoo
là loại gốc của tệp), bạn phải thêm <my/package/IFoo.h>
cho phần phụ trợ CPP (hoặc <aidl/my/package/IFoo.h>
cho phần phụ trợ NDK).
Triển khai một giao diện
Để triển khai một giao diện, bạn phải kế thừa từ lớp gốc. Việc triển khai một giao diện thường được gọi là dịch vụ khi được đăng ký với trình quản lý dịch vụ hoặc android.app.ActivityManager
và được gọi là lệnh gọi lại khi được một ứng dụng của dịch vụ đăng ký. Tuy nhiên, nhiều tên được dùng để mô tả việc triển khai giao diện, tuỳ thuộc vào cách sử dụng chính xác. Lớp stub sẽ đọc các lệnh từ trình điều khiển liên kết và thực thi các phương thức mà bạn triển khai. Hãy tưởng tượng bạn có một tệp AIDL như sau:
package my.package;
interface IFoo {
int doFoo();
}
Trong Java, bạn phải mở rộng từ lớp Stub
đã tạo:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
Trong phần phụ trợ CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
Trong phần phụ trợ NDK (lưu ý không gian tên aidl
bổ sung):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
Trong phần phụ trợ 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(())
}
}
Hoặc với Rust không đồng bộ:
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(())
}
}
Đăng ký và nhận dịch vụ
Các dịch vụ trong nền tảng Android thường được đăng ký bằng quy trình servicemanager
. Ngoài các API sau, một số API sẽ kiểm tra dịch vụ (tức là chúng sẽ trả về ngay lập tức nếu dịch vụ không có sẵn).
Hãy kiểm tra giao diện servicemanager
tương ứng để biết thông tin chi tiết chính xác. Bạn chỉ có thể thực hiện các thao tác này khi biên dịch bằng Android nền tảng.
Trong 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"));
Trong phần phụ trợ 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"));
Trong phần phụ trợ NDK (lưu ý không gian tên aidl
bổ sung):
#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")));
Trong phần phụ trợ 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()
}
Trong phần phụ trợ Rust không đồng bộ, với thời gian chạy một luồng:
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
}
Một điểm khác biệt quan trọng so với các lựa chọn khác là bạn không gọi join_thread_pool
khi dùng Rust không đồng bộ và một thời gian chạy đơn luồng. Nguyên nhân là do bạn cần cung cấp cho Tokio một luồng để thực thi các tác vụ được tạo. Trong ví dụ sau, luồng chính sẽ đảm nhận mục đích đó. Mọi tác vụ được tạo bằng tokio::spawn
đều thực thi trên luồng chính.
Trong phần phụ trợ Rust không đồng bộ, với thời gian chạy đa luồng:
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();
});
}
Với thời gian chạy Tokio đa luồng, các tác vụ được tạo sẽ không thực thi trên luồng chính. Do đó, bạn nên gọi join_thread_pool
trên luồng chính để luồng chính không ở trạng thái rảnh. Bạn phải kết thúc lệnh gọi trong block_in_place
để rời khỏi ngữ cảnh không đồng bộ.
Liên kết đến cái chết
Bạn có thể yêu cầu nhận thông báo khi một dịch vụ lưu trữ một liên kết bị lỗi. Điều này có thể giúp tránh rò rỉ các proxy lệnh gọi lại hoặc hỗ trợ khôi phục lỗi. Thực hiện các lệnh gọi này trên các đối tượng proxy liên kết.
- Trong Java, hãy sử dụng
android.os.IBinder::linkToDeath
. - Trong phần phụ trợ CPP, hãy sử dụng
android::IBinder::linkToDeath
. - Trong phần phụ trợ NDK, hãy sử dụng
AIBinder_linkToDeath
. - Trong phần phụ trợ Rust, hãy tạo một đối tượng
DeathRecipient
, sau đó gọimy_binder.link_to_death(&mut my_death_recipient)
. Xin lưu ý rằng vìDeathRecipient
sở hữu lệnh gọi lại, nên bạn phải giữ cho đối tượng đó hoạt động miễn là bạn muốn nhận thông báo.
Thông tin về người gọi
Khi nhận được một lệnh gọi liên kết nhân, thông tin về người gọi sẽ có trong một số API. Mã nhận dạng quy trình (PID) đề cập đến mã nhận dạng quy trình Linux của quy trình đang gửi một giao dịch. Mã nhận dạng người dùng (UI) đề cập đến mã nhận dạng người dùng Linux. Khi nhận cuộc gọi một chiều, PID gọi là 0. Ngoài ngữ cảnh giao dịch liên kết, các hàm này sẽ trả về PID và UID của quy trình hiện tại.
Trong phần phụ trợ Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
Trong phần phụ trợ CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
Trong phần phụ trợ NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Trong phần phụ trợ Rust, khi triển khai giao diện, hãy chỉ định những nội dung sau (thay vì cho phép giao diện này mặc định):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Báo cáo lỗi và API gỡ lỗi cho các dịch vụ
Khi chạy báo cáo lỗi (ví dụ: với adb bugreport
), báo cáo sẽ thu thập thông tin từ toàn bộ hệ thống để hỗ trợ gỡ lỗi nhiều vấn đề.
Đối với các dịch vụ AIDL, báo cáo lỗi sẽ sử dụng tệp nhị phân dumpsys
trên tất cả các dịch vụ đã đăng ký với trình quản lý dịch vụ để kết xuất thông tin của chúng vào báo cáo lỗi. Bạn cũng có thể dùng dumpsys
trên dòng lệnh để lấy thông tin từ một dịch vụ bằng dumpsys SERVICE [ARGS]
. Trong các phần phụ trợ C++ và Java, bạn có thể kiểm soát thứ tự mà các dịch vụ được kết xuất bằng cách sử dụng các đối số bổ sung cho addService
. Bạn cũng có thể dùng dumpsys --pid SERVICE
để lấy PID của một dịch vụ trong khi gỡ lỗi.
Để thêm đầu ra tuỳ chỉnh vào dịch vụ, hãy ghi đè phương thức dump
trong đối tượng máy chủ như khi bạn đang triển khai bất kỳ phương thức IPC nào khác được xác định trong tệp AIDL. Khi thực hiện việc này, hãy hạn chế việc kết xuất đối với quyền android.permission.DUMP
của ứng dụng hoặc hạn chế việc kết xuất đối với các UID cụ thể.
Trong phần phụ trợ Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
Trong phần phụ trợ CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
Trong phần phụ trợ NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
Trong phần phụ trợ Rust, khi triển khai giao diện, hãy chỉ định những nội dung sau (thay vì cho phép giao diện này mặc định):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Sử dụng con trỏ yếu
Bạn có thể giữ một tham chiếu yếu đến một đối tượng liên kết.
Mặc dù Java hỗ trợ WeakReference
, nhưng không hỗ trợ các tham chiếu liên kết yếu ở lớp gốc.
Trong phần phụ trợ CPP, kiểu yếu là wp<IFoo>
.
Trong phần phụ trợ NDK, hãy sử dụng ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
Trong phần phụ trợ Rust, hãy sử dụng WpIBinder
hoặc Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Lấy bộ mô tả giao diện một cách linh động
Trình mô tả giao diện xác định loại giao diện. Điều này rất hữu ích khi gỡ lỗi hoặc khi bạn có một liên kết không xác định.
Trong Java, bạn có thể lấy nội dung mô tả giao diện bằng mã như:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
Trong phần phụ trợ CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
NDK và các chương trình phụ trợ Rust không hỗ trợ khả năng này.
Lấy tĩnh bộ mô tả giao diện
Đôi khi (chẳng hạn như khi đăng ký các dịch vụ @VintfStability
), bạn cần biết tĩnh mô tả giao diện là gì. Trong Java, bạn có thể lấy giá trị mô tả bằng cách thêm mã như:
import my.package.IFoo;
... IFoo.DESCRIPTOR
Trong phần phụ trợ CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
Trong phần phụ trợ NDK (lưu ý không gian tên aidl
bổ sung):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
Trong phần phụ trợ Rust:
aidl::my::package::BnFoo::get_descriptor()
Phạm vi enum
Trong các phần phụ trợ gốc, bạn có thể lặp lại các giá trị có thể mà một enum có thể nhận được. Do cân nhắc về kích thước mã, nên tính năng này không được hỗ trợ trong Java.
Đối với một enum MyEnum
được xác định trong AIDL, quá trình lặp lại được cung cấp như sau.
Trong phần phụ trợ CPP:
::android::enum_range<MyEnum>()
Trong phần phụ trợ NDK:
::ndk::enum_range<MyEnum>()
Trong phần phụ trợ Rust:
MyEnum::enum_values()
Quản lý luồng
Mọi phiên bản của libbinder
trong một quy trình đều duy trì một nhóm luồng. Đối với hầu hết các trường hợp sử dụng, đây phải là chính xác một nhóm luồng, được chia sẻ trên tất cả các phần phụ trợ.
Trường hợp ngoại lệ duy nhất là khi mã nhà cung cấp tải một bản sao khác của libbinder
để giao tiếp với /dev/vndbinder
. Việc này diễn ra trên một nút liên kết riêng biệt, vì vậy, threadpool không được chia sẻ.
Đối với phần phụ trợ Java, nhóm luồng chỉ có thể tăng kích thước (vì nhóm này đã bắt đầu):
BinderInternal.setMaxThreads(<new larger value>);
Đối với phần phụ trợ CPP, bạn có thể thực hiện các thao tác sau:
// 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();
Tương tự, trong phần phụ trợ NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
Trong phần phụ trợ Rust:
binder::ProcessState::start_thread_pool();
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
binder::ProcessState::join_thread_pool();
Với phần phụ trợ Rust không đồng bộ, bạn cần có 2 nhóm luồng: binder và Tokio.
Điều này có nghĩa là các ứng dụng sử dụng Rust không đồng bộ cần có những điểm đặc biệt cần cân nhắc, đặc biệt là khi sử dụng join_thread_pool
. Hãy xem phần về việc đăng ký dịch vụ để biết thêm thông tin về vấn đề này.
Tên dành riêng
C++, Java và Rust dành riêng một số tên làm từ khoá hoặc để sử dụng cho ngôn ngữ cụ thể. Mặc dù AIDL không thực thi các hạn chế dựa trên quy tắc ngôn ngữ, nhưng việc sử dụng tên trường hoặc tên loại trùng với tên dành riêng có thể dẫn đến lỗi biên dịch cho C++ hoặc Java. Đối với Rust, trường hoặc loại được đổi tên bằng cú pháp giá trị nhận dạng thô, có thể truy cập bằng tiền tố r#
.
Bạn nên tránh sử dụng các tên dành riêng trong định nghĩa AIDL (nếu có thể) để tránh các liên kết không phù hợp hoặc lỗi biên dịch hoàn toàn.
Nếu đã có tên dành riêng trong các định nghĩa AIDL, bạn có thể đổi tên các trường một cách an toàn trong khi vẫn tương thích với giao thức. Bạn có thể cần cập nhật mã để tiếp tục tạo, nhưng mọi chương trình đã được tạo sẽ tiếp tục tương tác.
Tên cần tránh: