Phần phụ trợ AIDL là một mục tiêu để tạo mã mã giả lập. Khi sử dụng tệp AIDL, bạn luôn sử dụng các tệp đó bằng một ngôn ngữ cụ thể với thời gian chạy cụ thể. Tuỳ thuộc vào ngữ cảnh, bạn nên sử dụng nhiều phần phụ trợ AIDL.
Trong bảng sau, tính ổn định của nền tảng API nói đến khả năng
biên dịch mã dựa trên nền tảng API này theo cách mà mã có thể
được phân phối độc lập từ tệp nhị phân system.img
libbinder.so
.
AIDL có các phần phụ trợ sau:
Phần phụ trợ | Ngôn ngữ | Nền tảng API | Hệ thống xây dựng |
---|---|---|---|
Java | Java | SDK/SystemApi (bản ổn định*) | tất cả |
NDK | C++ | libbinder_ndk (phiên bản ổn định*) | aidl_interface |
CPP | C++ | libbinder (không ổn định) | tất cả |
Rust | Rust | libbinder_rs (ổn định*) | giao diện aidl |
- Các nền tảng API này là ổn định, nhưng nhiều API, chẳng hạn như API cho dịch vụ dành riêng cho việc sử dụng nền tảng nội bộ và không dành cho của chúng tôi. Để biết thêm thông tin về cách sử dụng AIDL trong ứng dụng, hãy xem tài liệu dành cho nhà phát triển.
- Phần phụ trợ Rust được ra mắt trong Android 12; thời gian Phần phụ trợ NDK đã có kể từ Android 10.
- Hộp Rust được xây dựng dựa trên
libbinder_ndk
, cho phép hộp này hoạt động ổn định và có thể di chuyển. Các APEX sử dụng thùng liên kết giống như bất kỳ ai khác về phía hệ thống. Phần Rust được nhóm vào một APEX và vận chuyển bên trong đó. Điều 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ã giả lập. Để biết thêm thông tin chi tiết về các hệ thống xây dựng, hãy xem Tài liệu tham khảo về mô-đun Soong.
Hệ thống xây dựng cốt lõi
Trong bất kỳ mô-đun Android.bp cc_
hoặc java_
nào (hoặc trong các mô-đun tương đương Android.mk
của chúng),
Có thể chỉ định tệp .aidl
làm tệp nguồn. Trong trường hợp này, phần phụ trợ Java/CPP của AIDL được sử dụng (không phải phần phụ trợ NDK) và các lớp để sử dụng tệp AIDL tương ứng sẽ được tự động thêm vào mô-đun. Tuỳ chọn
chẳng hạn như local_include_dirs
(cho hệ thống xây dựng biết đường dẫn gốc đến
Bạn có thể chỉ định tệp AIDL trong mô-đun đó trong các mô-đun này trong aidl:
nhóm. Xin lưu ý rằng phần phụ trợ của Rust chỉ dùng được với Rust. Các mô-đun rust_
được xử lý theo cách khác vì 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
có thể được liên kết. Để biết thêm thông tin, hãy xem
ví dụ về Rust AIDL.
giao diện aidl
Các loại được sử dụng với hệ thống xây dựng này phải được cấu trúc. Để được cấu trúc, gói hàng phải chứa các trường trực tiếp và không phải là nội dung khai báo về các loại được xác định trực tiếp bằng ngôn ngữ đích. 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 và ổn định.
Loại
Bạn có thể coi trình biên dịch aidl
là phương thức triển khai tham chiếu cho các kiểu.
Khi bạn tạo một giao diện, hãy gọi aidl --lang=<backend> ...
để xem tệp giao diện thu được. Khi sử dụng mô-đun aidl_interface
, bạn có thể xem
dữ liệu đầu ra trong out/soong/.intermediates/<path to module>/
.
Loại Java/AIDL | Loại C++ | Loại NDK | Loại gỉ |
---|---|---|---|
boolean | bool | bool | bool |
byte | int8_t | int8_t | i8 |
ký tự | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
long | int64_t | int64_t | i64 |
số thực dấu phẩy động | số thực dấu phẩy động | số thực dấu phẩy động | f32 |
gấp đôi | gấp đôi | gấp đôi | f64 |
Chuỗi | android::String16 | std::chuỗi | Chuỗi |
android.os.Parcelable | android::Theo gói | Không áp dụng | Không áp dụng |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
Đ[] | std::vector<T> | std::vector<T> | Trong: &[T] Ra: Vec<T> |
byte[] | std::vector<uint8_t> | std::vector<int8_t>1 | Trong: &[u8] Ra: Vec<u8> |
Danh sách<T> | std::vector<T>2 | std::vector<T>3 | Trong: &[T]4 Ra: Vec<T> |
Mô tả tệp | android::base::: duy nhất_fd | Không áp dụng | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
loại giao diện (T) | android::sp<T> | std::shared_ptr<T>7 | liên kết::Mạnh |
loại theo gói (T) | T5 | T5 | T5 |
loại liên kết (T)5 | T5 | T5 | T5 |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; B] |
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 String
,
IBinder
, ParcelFileDescriptor
hoặc theo gói. Trong Android 13 trở lên, T
có thể là bất kỳ loại không phải loại gốc nào (bao gồm cả loại giao diện) ngoại trừ mảng. AOSP khuyến nghị bạn
sử dụng các loại mảng như T[]
vì các loại mảng này hoạt động trong mọi phần phụ trợ.
3. Phần phụ trợ NDK hỗ trợ List<T>
, trong đó T
là một trong String
,
ParcelFileDescriptor
hoặc theo gói. Trong Android 13 trở lên, T
có thể là bất kỳ loại không phải loại gốc nào ngoại trừ mảng.
4. Các kiểu đượ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 (đối số) hoặc đầu ra (giá trị được trả về).
5. Các loại liên kết được hỗ trợ trong Android 12 và cao hơn.
6. Trong Android 13 trở lên, các mảng có kích thước cố định đượ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, các mảng có kích thước cố định được biểu thị dưới dạng loại mảng.
7. Để tạo thực thể cho đối tượng SharedRefBase
liên kết, hãy sử dụng
SharedRefBase::make\<My\>(... args ...)
. Hàm này tạo một
std::shared_ptr\<T\>
đối tượng
cũng được quản lý nội bộ trong trường hợp liên kết thuộc sở hữu của một tài khoản khác
của chúng tôi. Việc tạo đối tượng theo cách khác sẽ dẫn đến tình trạng sở hữu hai lần.
Hướng (vào/ra/vào ra)
Khi chỉ định loại đối số cho hàm, bạn có thể chỉ định các đối số đó là in
, out
hoặc inout
. Chế độ này kiểm soát việc thông tin về hướng nào
được truyền cho một lệnh gọi IPC. in
là hướng mặc định và cho biết dữ liệu được truyền từ phương thức gọi đến phương thức được gọi. out
có nghĩa là dữ liệu được truyền từ
hàm được gọi cho người gọi. inout
là sự kết hợp của cả hai giá trị này. Tuy nhiên, nhóm Android khuyên bạn nên tránh sử dụng chỉ định đối số inout
.
Nếu bạn sử dụng inout
với giao diện có phiên bản và đối tượng được gọi cũ, thì các trường bổ sung chỉ có trong phương thức gọi sẽ được đặt lại về giá trị mặc định. Đối với Rust, loại inout
thông thường sẽ nhận được &mut Vec<T>
và
một loại danh sách inout
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);
}
UTF8/UTF16
Với phần phụ trợ CPP, bạn có thể chọn chuỗi là utf-8 hay utf-16. 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.
Phần phụ trợ NDK và Rust luôn sử dụng chuỗi utf-8. Để biết thêm thông tin về
chú thích utf8InCpp
, hãy xem phần Chú thích trong AIDL.
Tính chất rỗng
Bạn có thể chú thích các loại có thể có giá trị rỗng bằng @nullable
.
Để biết thêm thông tin về chú thích nullable
, hãy xem phần Chú thích trong AIDL.
Gói hàng tuỳ chỉnh
Gói tuỳ chỉnh là một gói có thể phân phối được triển khai theo cách thủ công trong 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 tính năng hỗ trợ cho các ngôn ngữ khác cho một đối tượng có thể phân phối tuỳ chỉnh hiện có mà không thể thay đổi.
Để khai báo một gói tuỳ chỉnh để AIDL biết về gói đó, nội dung khai báo gói AIDL sẽ có dạng như sau:
package my.pack.age;
parcelable Foo;
Theo mặc định, lệnh này khai báo một gói Java, trong đó my.pack.age.Foo
là một lớp Java triển khai giao diện Parcelable
.
Để khai báo một phần phụ trợ CPP tuỳ chỉnh có thể phân phối 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
sẽ 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 về một gói NDK tuỳ chỉnh trong AIDL, hãy sử dụng ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
Cách triển khai NDK trong android/pack/age/Foo.h
sẽ 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 Rust tuỳ chỉnh có thể phân đoạn trong AIDL, hãy sử 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
sẽ 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ể sử dụng gói này làm loại trong tệp AIDL, nhưng AIDL sẽ không tạo gói này. Cung cấp toán tử <
và ==
cho các gói tuỳ chỉnh phần phụ trợ CPP/NDK để sử dụng các toán tử này trong union
.
Giá trị mặc định
Các gói có cấu trúc có thể khai báo giá trị mặc định theo từng trường cho dữ liệu gốc,
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 được khởi tạo dưới dạng
giá trị bằng 0 cho loại nguyên hàm và null
đối với loại không nguyên gốc.
Trong các phần phụ trợ khác, các trường được khởi tạo bằng giá trị khởi tạo mặc định khi 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 tạo dưới dạng một chuỗi trống và các trường List<T>
được khởi tạo dưới dạng một vector<T>
trống. Các trường @nullable
được khởi tạo dưới dạng trường có giá trị rỗng.
Xử lý lỗi
Hệ điều hành Android cung cấp các loại lỗi tích hợp sẵn để các dịch vụ sử dụng khi báo cáo lỗi. Các chỉ số này được liên kết sử dụng và có thể dùng trong bất kỳ dịch vụ nào triển khai giao diện liên kết. Cách sử dụng các phương thức này được ghi nhận rõ ràng trong định nghĩa AIDL và các phương thức này 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 hàm AIDL báo cáo lỗi, hàm này không thể khởi chạy hoặc
sửa đổi các thông số đầu ra. Cụ thể, các tham số đầu ra có thể được sửa đổi nếu
lỗi xảy ra trong quá trình huỷ phân vùng chứ không phải xảy ra trong quá trình xử lý
của giao dịch. Nhìn chung, khi gặp lỗi từ AIDL
tất cả tham số inout
và out
cũng như giá trị trả về (giá trị này
đóng vai trò như một tham số out
trong một số phần phụ trợ) cần được cân nhắc ở trong
trạng thái không xác định.
Giá trị lỗi cần sử dụng
Nhiều giá trị lỗi tích hợp sẵn có thể được sử dụng trong bất kỳ giao diện AIDL nào, nhưng một số giá trị lỗi
được đối xử theo cách đặc biệt. Ví dụ: EX_UNSUPPORTED_OPERATION
và
Có thể sử dụng EX_ILLEGAL_ARGUMENT
khi mô tả điều kiện lỗi, nhưng
Không được sử dụng EX_TRANSACTION_FAILED
vì hàm này được coi là đặc biệt bởi
cơ sở hạ tầng cơ bản. Hãy xem các định nghĩa cụ thể về phần phụ trợ để biết thêm thông tin về các giá trị tích hợp sẵn 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ì chúng 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 một 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 enum
được hỗ trợ bởi const int
hoặc int
và không được trình liên kết phân tích cú pháp.
Trong Java, lỗi liên kết đến các trường hợp ngoại lệ, chẳng hạn như android.os.RemoteException
. Cho
ngoại lệ dành riêng cho từng 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 ngoại lệ. Phần phụ trợ CPP sử dụng android::binder::Status
. Phần phụ trợ của NDK sử dụng ndk::ScopedAStatus
. Mọi phương thức do AIDL tạo ra đều trả về một trong những giá trị này, đại diện cho trạng thái của phương thức. Phần phụ trợ của Rust sử dụng các giá trị mã ngoại lệ giống như NDK, nhưng
chuyển đổi chúng thành lỗi Rust gốc (StatusCode
, ExceptionCode
) trước
phân phối quảng cáo đến người dùng. Đối với các lỗi cụ thể theo dịch vụ, giá trị trả về
Status
hoặc ScopedAStatus
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ần 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ợ
Những hướng dẫn này dành riêng cho mã nền tảng Android. Những ví dụ này sử dụng
loại đã 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ề Rust AIDL trên trang Mẫu Rust cho Android.
Loại nhập
Bạn có thể nhập cho dù loại đã xác định là giao diện, theo gói hay hợp nhất 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 (chú ý 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 phần phụ trợ CPP/NDK, bạn phải đưa tiêu đề vào 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 đưa <my/package/IFoo.h>
vào phần phụ trợ CPP (hoặc <aidl/my/package/IFoo.h>
cho phần phụ trợ NDK).
Triển khai dịch vụ
Để triển khai một dịch vụ, bạn phải kế thừa từ lớp mô phỏng gốc. Lớp này đọ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. Tưởng tượng rằ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 này:
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 (chú ý 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à sử dụng 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 bên dưới, một số API sẽ kiểm tra dịch vụ (nghĩa là các API này sẽ trả về ngay lập tức nếu không có dịch vụ).
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 theo nền tảng Android.
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 (chú ý 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ợ của 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 will run on this thread.
std::future::pending().await
}
Một điểm khác biệt quan trọng so với các tuỳ chọn khác là chúng ta không gọi join_thread_pool
khi sử dụng Rust không đồng bộ và thời gian chạy đơn luồng. Lý do là 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ụ này, luồng chính sẽ phục vụ mục đích đó. Mọi tác vụ được tạo bằng tokio::spawn
sẽ 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 nhiều 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();
});
}
Trong môi trường thời gian chạy đa luồng Tokio, các tác vụ được tạo sẽ không thực thi trên
chuỗi. Do đó, bạn nên gọi join_thread_pool
trên luồng chính để luồng chính không chỉ ở trạng thái rảnh. Bạn phải kết thúc cuộc gọi
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ữ liên kết bị lỗi. Điều này có thể giúp tránh làm rò rỉ proxy 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 đố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, bạn phải duy trì đối tượng đó tồn tại trong thời gian như bạn muốn nhận thông báo.
Thông tin về phương thức gọi
Khi nhận được lệnh gọi liên kết nhân, thông tin người gọi sẽ có trong một số API. PID (hay mã định danh tiến trình) là mã nhận dạng tiến trình Linux của đang gửi một giao dịch. UID (hay User ID) đề 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. Thời gian bên 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 nội dung sau (thay vì cho phép giao diện 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 lỗi sẽ thu thập
từ khắp nơi trong hệ thống để hỗ trợ gỡ lỗi nhiều sự cố.
Đối với các dịch vụ AIDL, báo cáo lỗi sử dụng dumpsys
nhị phân trên tất cả các dịch vụ
đã đăng ký với người quản lý dịch vụ để kết xuất thông tin của họ vào
báo cáo lỗi. Bạn cũng có thể dùng dumpsys
trên dòng lệnh để nhận thông tin
từ một dịch vụ có dumpsys SERVICE [ARGS]
. Trong phần phụ trợ C++ và Java, bạn có thể kiểm soát thứ tự 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ể sử 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ụ của mình, bạn có thể ghi đè dump
trong đối tượng máy chủ của bạn giống như bạn đang triển khai bất kỳ phương thức IPC nào khác
xác định trong tệp AIDL. Khi thực hiện việc này, bạn nên hạn chế kết xuất trong ứng dụng
quyền android.permission.DUMP
hoặc hạn chế kết xuất đối với 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 nội dung sau (thay vì cho phép giao diện 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 lại không hỗ trợ các tệp tham chiếu liên kết yếu
tại lớp gốc.
Trong phần phụ trợ CPP, loại 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, bạn sử dụng WpIBinder
hoặc Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Tự động lấy chỉ số mô tả giao diện
Chỉ số 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 chỉ số mô tả giao diện bằng mã như sau:
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();
Các phần phụ trợ của NDK và Rust không hỗ trợ tính năng này.
Lấy chỉ số mô tả giao diện không đổi
Đôi khi (chẳng hạn như khi đăng ký dịch vụ @VintfStability
), bạn cần phải
biết bộ mô tả giao diện là gì theo phương thức tĩnh. Trong Java, bạn có thể nhận được
bằng cách thêm mã, chẳng hạn 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 (chú ý 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()
Dải ô enum
Trong phần phụ trợ gốc, bạn có thể lặp lại các giá trị mà một enum có thể lấy . Do cân nhắc về kích thước mã, tính năng này không được hỗ trợ trong Java.
Đối với enum MyEnum
được xác định trong AIDL, vòng lặp đượ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 thực thể của libbinder
trong một quy trình duy trì một nhóm luồng. Đối với hầu hết
các trường hợp sử dụng, thì đây phải là chính xác một nhóm luồng, dùng chung trên tất cả các phần phụ trợ.
Ngoại lệ duy nhất là khi mã của nhà cung cấp có thể tải một bản sao khác của libbinder
để giao tiếp với /dev/vndbinder
. Vì nằm trên một nút liên kết riêng biệt, nên nhóm luồng không được chia sẻ.
Đối với phần phụ trợ Java, threadpool chỉ có thể tăng kích thước (vì đã 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ự như vậy, 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ó hai nhóm luồng: liên kết và Tokio.
Tức là các ứng dụng sử dụng Rust không đồng bộ cần phải đặc biệt 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 theo ngôn ngữ cụ thể. Mặc dù AIDL không thực thi các quy định hạn chế dựa trên quy tắc về ngôn ngữ, nhưng việc sử dụng
tên trường hoặc loại tên khớp với một tên dành riêng có thể dẫn đến quá trình biên dịch
đối với 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 tên được đặt trước trong định nghĩa AIDL nếu có thể để tránh các liên kết không hợp lý hoặc lỗi biên dịch hoàn toàn.
Nếu đã đặt trước tên trong các định nghĩa AIDL, bạn có thể yên tâm đổi tên các trường trong khi vẫn tương thích với giao thức; bạn có thể cần phải cập nhật mã để tiếp tục tạo bản dựng, nhưng mọi chương trình đã tạo sẽ tiếp tục tương tác.
Tên cần tránh: