Android 11 giới thiệu khả năng sử dụng AIDL cho HAL (Lớp trừu tượng phần cứng) trong Android. Điều này giúp có thể triển khai các phần của Android mà không cần HIDL. Chuyển đổi HAL để sử dụng AIDL chỉ khi có thể (khi HAL ngược dòng sử dụng HIDL, HIDL phải được sử dụng).
HAL (Lớp trừu tượng phần cứng) sử dụng AIDL để giao tiếp giữa các thành phần khung, chẳng hạn như thành phần trong
system.img
và các thành phần phần cứng (chẳng hạn như các thành phần trong vendor.img
) phải sử dụng
AIDL ổn định. Tuy nhiên, để giao tiếp trong một phân vùng, ví dụ: từ một
HAL sang một giao thức khác, không có hạn chế nào về cơ chế IPC có thể sử dụng.
Động lực
AIDL đã tồn tại lâu hơn HIDL và được sử dụng ở nhiều nơi khác, chẳng hạn như như giữa các thành phần khung Android hoặc trong ứng dụng. Giờ đây, AIDL đã có tính ổn định , bạn có thể triển khai toàn bộ ngăn xếp bằng một thời gian chạy IPC. AIDL cũng có hệ thống tạo phiên bản tốt hơn HIDL.
- Sử dụng một ngôn ngữ IPC duy nhất nghĩa là bạn chỉ cần tìm hiểu, gỡ lỗi tối ưu hoá và bảo mật.
- AIDL hỗ trợ tạo phiên bản tại chỗ cho chủ sở hữu giao diện:
- Chủ sở hữu có thể thêm các phương thức vào cuối giao diện hoặc trường vào Parceable. Điều này có nghĩa là sẽ dễ dàng tạo mã phiên bản hơn trong những năm và cả năm chi phí so với cùng kỳ năm trước nhỏ hơn (có thể sửa đổi loại tại chỗ và không có thư viện bổ sung cho mỗi phiên bản giao diện).
- Giao diện tiện ích có thể được đính kèm trong thời gian chạy thay vì theo loại hệ thống của mình, vì vậy, bạn không cần đặt lại tiện ích hạ nguồn cho các tiện ích mới hơn các phiên bản giao diện.
- Giao diện AIDL hiện có có thể được sử dụng trực tiếp khi chủ sở hữu giao diện đó chọn ổn định thiết bị đó. Trước đây, toàn bộ bản sao giao diện sẽ phải được tạo trong HIDL.
Tạo dựa trên thời gian chạy AIDL
AIDL có 3 phần phụ trợ: Java, NDK, CPP. Để sử dụng AIDL chính thức, bạn phải
luôn sử dụng bản sao hệ thống của libbinder tại system/lib*/libbinder.so
và trò chuyện
vào ngày /dev/binder
. Đối với mã trên ảnh nhà cung cấp, điều này có nghĩa là libbinder
Không thể sử dụng (từ VNDK): thư viện này có API C++ không ổn định và
bên trong không ổn định. Thay vào đó, mã của nhà cung cấp gốc phải sử dụng phần phụ trợ NDK của
AIDL, liên kết với libbinder_ndk
(được hệ thống libbinder.so
hỗ trợ),
và liên kết dựa trên các thư viện NDK do các mục nhập aidl_interface
tạo. Cho
tên mô-đun chính xác, xem
quy tắc đặt tên mô-đun.
Viết giao diện AIDL HAL
Để sử dụng giao diện AIDL giữa hệ thống và nhà cung cấp, giao diện này cần hai thay đổi:
- Mọi định nghĩa kiểu đều phải được chú thích bằng
@VintfStability
. - Nội dung khai báo
aidl_interface
cần bao gồmstability: "vintf",
.
Chỉ chủ sở hữu của giao diện mới có thể thực hiện những thay đổi này.
Khi bạn thực hiện những thay đổi này, giao diện phải ở trong
Tệp kê khai VINTF để có thể hoạt động. Kiểm tra điều này (và các liên quan
theo yêu cầu, chẳng hạn như xác minh rằng giao diện đã phát hành bị treo) bằng cách sử dụng
Xét nghiệm VTS vts_treble_vintf_vendor_test
. Bạn có thể dùng @VintfStability
không có các yêu cầu này bằng cách gọi
AIBinder_forceDowngradeToLocalStability
trong phần phụ trợ NDK,
android::Stability::forceDowngradeToLocalStability
trong phần phụ trợ C++,
hoặc android.os.Binder#forceDowngradeToSystemStability
trong phần phụ trợ Java
trên một đối tượng liên kết trước khi đối tượng đó được gửi đến một quy trình khác. Hạ cấp dịch vụ
đối với độ ổn định của nhà cung cấp không được hỗ trợ trong Java vì tất cả các ứng dụng đều chạy trong một hệ thống
ngữ cảnh.
Ngoài ra, để tối đa hoá khả năng di chuyển mã và tránh những vấn đề tiềm ẩn như như các thư viện bổ sung không cần thiết, hãy vô hiệu hoá phần phụ trợ CPP.
Xin lưu ý rằng việc sử dụng backends
trong mã ví dụ bên dưới là chính xác, vì
là 3 phần phụ trợ (Java, NDK và CPP). Mã dưới đây cho biết cách chọn
phần phụ trợ CPP cụ thể, để vô hiệu hoá nó.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Tìm giao diện AIDL HAL
Giao diện AIDL ổn định của AOSP (Dự án nguồn mở Android) cho HAL (Lớp trừu tượng phần cứng) nằm trong cùng thư mục cơ sở như
Giao diện HIDL, trong thư mục aidl
.
- phần cứng/giao diện
- khung/phần cứng/giao diện
- hệ thống/phần cứng/giao diện
Bạn nên đặt giao diện tiện ích vào hardware/interfaces
khác
các thư mục con trong vendor
hoặc hardware
.
Giao diện tiện ích
Android có một bộ giao diện AOSP chính thức với mỗi bản phát hành. Khi Android các đối tác muốn thêm chức năng vào các giao diện này thì họ không nên thay đổi trực tiếp vì điều này có nghĩa là thời gian chạy Android của họ không tương thích với môi trường thời gian chạy Android của AOSP (Dự án nguồn mở Android). Đối với thiết bị GMS, tránh thay đổi những giao diện này cũng là yếu tố đảm bảo hình ảnh GSI có thể tiếp tục hoạt động.
Các tiện ích có thể đăng ký theo hai cách:
- trong thời gian chạy, hãy xem các tiện ích được đính kèm.
- độc lập, được đăng ký trên toàn cầu và trong VINTF.
Tuy nhiên, tiện ích được đăng ký, khi tiện ích dành riêng cho nhà cung cấp (có nghĩa là không phải là một phần của các thành phần AOSP ngược dòng (upstream) sử dụng giao diện, không thể hợp nhất xung đột. Tuy nhiên, khi sửa đổi hạ nguồn đối với các thành phần AOSP ngược dòng (upstream) có thể xảy ra, xung đột hợp nhất có thể xảy ra và các chiến lược sau đây được khuyến nghị:
- các phần bổ sung giao diện có thể được tải lên AOSP trong bản phát hành tiếp theo
- bổ sung giao diện cho phép linh hoạt hơn mà không có xung đột hợp nhất, có thể được cải tiến trong bản phát hành tiếp theo
ParcelableHolder của phần mở rộng
ParcelableHolder
là một Parcelable
có thể chứa một Parcelable
khác.
Trường hợp sử dụng chính của ParcelableHolder
là giúp Parcelable
có thể mở rộng.
Ví dụ: hình ảnh mà người triển khai thiết bị muốn có thể mở rộng
Parcelable
, AospDefinedParcelable
do AOSP xác định, để bao gồm giá trị gia tăng của chúng
các tính năng AI mới.
Trước đây nếu không có ParcelableHolder
, trình triển khai thiết bị không thể sửa đổi
giao diện AIDL ổn định do AOSP xác định vì sẽ có lỗi nếu thêm giao diện khác
trường:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Như đã thấy trong mã trước, phương pháp này bị lỗi vì các trường do trình triển khai thiết bị thêm vào có thể có xung đột khi Parcelable được được sửa đổi trong bản phát hành Android tiếp theo.
Bằng cách sử dụng ParcelableHolder
, chủ sở hữu của một gói có thể xác định một tiện ích
điểm trong Parcelable
.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Sau đó, trình triển khai thiết bị có thể xác định Parcelable
riêng cho
tiện ích.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
Cuối cùng, bạn có thể đính kèm Parcelable
mới vào Parcelable
gốc bằng
trường ParcelableHolder
.
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
Tên thực thể máy chủ AIDL HAL
Theo quy ước, các dịch vụ AIDL HAL có tên thực thể của định dạng
$package.$type/$instance
. Ví dụ: một ví dụ của HAL bộ rung là
đã đăng ký bằng android.hardware.vibrator.IVibrator/default
.
Viết máy chủ HAL AIDL
@VintfStability
máy chủ AIDL phải được khai báo trong tệp kê khai VINTF, đối với
ví dụ như sau:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
Nếu không, họ nên đăng ký dịch vụ AIDL như bình thường. Khi chạy VTS thường thì tất cả các lớp trừu tượng phần cứng (HAL) AIDL đã khai báo đều có sẵn.
Viết một ứng dụng AIDL
Ứng dụng AIDL phải tự khai báo trong ma trận tương thích, ví dụ: như sau:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Chuyển đổi HAL hiện có từ HIDL sang AIDL
Dùng công cụ hidl2aidl
để chuyển đổi giao diện HIDL thành AIDL.
Tính năng của hidl2aidl
:
- Tạo tệp
.aidl
dựa trên các tệp.hal
của gói đã cho - Tạo quy tắc bản dựng cho gói AIDL mới tạo cùng với tất cả phần phụ trợ đã bật
- Tạo các phương thức dịch trong phần phụ trợ Java, CPP và NDK để dịch từ loại HIDL sang loại AIDL
- Tạo quy tắc bản dựng để dịch thư viện có các phần phụ thuộc bắt buộc
- Tạo các xác nhận tĩnh để đảm bảo rằng bộ liệt kê HIDL và AIDL có các giá trị giống nhau trong phần phụ trợ CPP và NDK
Hãy làm theo các bước sau để chuyển đổi gói tệp .hal thành tệp .aidl:
Tạo công cụ nằm trong
system/tools/hidl/hidl2aidl
.Việc tạo công cụ này từ nguồn mới nhất sẽ cung cấp của bạn. Bạn có thể dùng phiên bản mới nhất để chuyển đổi giao diện trên nhánh từ bản phát hành trước.
m hidl2aidl
Thực thi công cụ bằng một thư mục đầu ra, theo sau là gói sẽ là đã chuyển đổi.
Bạn có thể dùng đối số
-l
để thêm nội dung của tệp giấy phép mới (không bắt buộc) lên đầu tất cả các tệp đã tạo. Hãy nhớ sử dụng đúng giấy phép và ngày tháng.hidl2aidl -o <output directory> -l <file with license> <package>
Ví dụ:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
Đọc các tệp đã tạo và khắc phục mọi vấn đề về lượt chuyển đổi.
conversion.log
chứa mọi vấn đề chưa xử lý được trước tiên.- Các tệp
.aidl
đã tạo có thể chứa cảnh báo và đề xuất có thể cần hành động. Các bình luận này bắt đầu bằng//
. - Hãy tận dụng cơ hội này để dọn dẹp và cải thiện gói.
- Xem
@JavaDerive
chú thích về các tính năng có thể cần đến, chẳng hạn nhưtoString
hoặcequals
.
Chỉ xây dựng những mục tiêu bạn cần.
- Tắt những phần phụ trợ sẽ không dùng đến. Ưu tiên phần phụ trợ của NDK hơn CPP phần phụ trợ, hãy xem phần chọn thời gian chạy.
- Xoá các thư viện dịch hoặc bất kỳ mã nào đã tạo trong đó mà sẽ không dùng đến.
Xem Những điểm khác biệt chính về AIDL/HIDL.
- Việc sử dụng
Status
được tích hợp sẵn của AIDL và các ngoại lệ thường cải thiện giao diện và không cần phải có một loại trạng thái giao diện cụ thể khác. - Đối số giao diện AIDL trong các phương thức không phải là
@nullable
theo mặc định giống như ở HIDL.
- Việc sử dụng
SEPolicy cho HAL AIDL
Loại dịch vụ AIDL hiển thị với mã nhà cung cấp phải có
Thuộc tính hal_service_type
. Nếu không, cấu hình sepolicy sẽ giống nhau
như bất kỳ dịch vụ AIDL nào khác (mặc dù có các thuộc tính đặc biệt dành cho HAL). Ở đây
là định nghĩa mẫu về ngữ cảnh dịch vụ HAL:
type hal_foo_service, service_manager_type, hal_service_type;
Đối với hầu hết các dịch vụ do nền tảng xác định, ngữ cảnh dịch vụ có thông tin chính xác
loại đã được thêm vào (ví dụ: android.hardware.foo.IFoo/default
sẽ
đã được đánh dấu là hal_foo_service
). Tuy nhiên, nếu ứng dụng khung hỗ trợ
nhiều tên thực thể, bạn phải thêm tên thực thể bổ sung vào
service_contexts
dành riêng cho thiết bị.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Bạn phải thêm các thuộc tính HAL khi tạo một loại HAL mới. Một HAL cụ thể
có thể được liên kết với nhiều loại dịch vụ (mỗi loại dịch vụ có thể
có nhiều trường hợp như chúng ta vừa thảo luận). Đối với HAL, foo
, chúng tôi có
hal_attribute(foo)
Macro này xác định các thuộc tính hal_foo_client
và
hal_foo_server
Đối với một miền cụ thể, hal_client_domain
và
Macro hal_server_domain
liên kết miền với một thuộc tính HAL nhất định. Cho
ví dụ: máy chủ hệ thống là ứng dụng khách của HAL này tương ứng với chính sách
hal_client_domain(system_server, hal_foo)
. Máy chủ HAL còn có chức năng tương tự
hal_server_domain(my_hal_domain, hal_foo)
. Thông thường, đối với một HAL (Lớp trừu tượng phần cứng) nhất định
, chúng ta cũng sẽ tạo một miền như hal_foo_default
để tham chiếu hoặc
ví dụ về HAL (Lớp trừu tượng phần cứng). Tuy nhiên, một số thiết bị sử dụng các miền này cho máy chủ của riêng chúng.
Việc phân biệt giữa các tên miền cho nhiều máy chủ chỉ quan trọng nếu chúng ta có
nhiều máy chủ phân phát cùng một giao diện và cần quyền khác
đặt trong quá trình triển khai. Trong tất cả các macro này, hal_foo
không thực sự
đối tượng sepolicy. Thay vào đó, mã thông báo này được các macro này sử dụng để tham chiếu đến
nhóm thuộc tính được liên kết với một cặp máy chủ ứng dụng.
Tuy nhiên, cho đến nay, chúng tôi chưa liên kết hal_foo_service
và hal_foo
(cặp thuộc tính từ hal_attribute(foo)
). Có liên kết với thuộc tính HAL
với các dịch vụ HAL AIDL sử dụng macro hal_attribute_service
(sử dụng HAL HIDL
macro hal_attribute_hwservice
). Ví dụ:
hal_attribute_service(hal_foo, hal_foo_service)
. Điều này có nghĩa là
Các quy trình hal_foo_client
có thể tuân thủ HAL và hal_foo_server
có thể đăng ký HAL. Việc thực thi các quy tắc đăng ký này
do trình quản lý ngữ cảnh (servicemanager
) thực hiện. Lưu ý rằng tên dịch vụ có thể
không phải lúc nào cũng tương ứng với thuộc tính HAL. Ví dụ: chúng ta có thể thấy
hal_attribute_service(hal_foo, hal_foo2_service)
. Mặc dù vậy, nhìn chung, vì
điều này ngụ ý rằng các dịch vụ luôn được sử dụng cùng nhau, chúng ta có thể xem xét việc xoá
hal_foo2_service
và sử dụng hal_foo_service
cho tất cả các dịch vụ của chúng tôi
ngữ cảnh. Hầu hết các HAL (Lớp trừu tượng phần cứng) đặt nhiều hal_attribute_service
là vì
tên thuộc tính HAL (Lớp trừu tượng phần cứng) ban đầu không đủ khái quát và không thể thay đổi được.
Kết hợp tất cả lại với nhau, HAL (Lớp trừu tượng phần cứng) ví dụ sẽ có dạng như sau:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
Giao diện tiện ích đi kèm
Có thể đính kèm một tiện ích vào bất kỳ giao diện liên kết nào, cho dù đó là giao diện cấp cao nhất được đăng ký trực tiếp với trình quản lý dịch vụ hoặc đó là một giao diện phụ. Khi tải tiện ích, bạn phải xác nhận loại tiện ích là như dự kiến. Bạn chỉ có thể thiết lập tiện ích trong quá trình phân phát một liên kết.
Bạn nên sử dụng các tiện ích đính kèm mỗi khi một tiện ích sửa đổi chức năng của HAL hiện có. Khi cần chức năng hoàn toàn mới, không cần sử dụng cơ chế này, đồng thời bạn có thể sử dụng giao diện mở rộng đã đăng ký trực tiếp với nhà quản lý dịch vụ. Giao diện tiện ích đi kèm hợp lý nhất khi chúng được đính kèm vào các giao diện phụ, vì hệ thống phân cấp có thể sâu hoặc nhiều phiên bản. Sử dụng tiện ích chung để phản chiếu hệ thống phân cấp giao diện liên kết của một dịch vụ khác sẽ đòi hỏi rất nhiều công việc kế toán để cung cấp chức năng tương đương cho các tiện ích được đính kèm trực tiếp.
Để thiết lập một tiện ích trên liên kết, hãy sử dụng các API sau:
- Trong phần phụ trợ của NDK:
AIBinder_setExtension
- Trong phần phụ trợ Java:
android.os.Binder.setExtension
- Trong phần phụ trợ CPP:
android::Binder::setExtension
- Trong phần phụ trợ Rust:
binder::Binder::set_extension
Để nhận tiện ích trên một liên kết, hãy sử dụng các API sau:
- Trong phần phụ trợ của NDK:
AIBinder_getExtension
- Trong phần phụ trợ Java:
android.os.IBinder.getExtension
- Trong phần phụ trợ CPP:
android::IBinder::getExtension
- Trong phần phụ trợ Rust:
binder::Binder::get_extension
Bạn có thể tìm thêm thông tin về các API này trong tài liệu về
Hàm getExtension
trong phần phụ trợ tương ứng. Ví dụ về cách sử dụng
có thể tìm thấy tiện ích trong
phần cứng/giao diện/kiểm thử/tiện ích/bộ rung.
Những điểm khác biệt chính giữa AIDL và HIDL
Khi sử dụng lớp trừu tượng phần cứng AIDL hoặc sử dụng giao diện AIDL HAL, hãy lưu ý đến sự khác biệt so với việc viết HAL HIDL.
- Cú pháp của ngôn ngữ AIDL gần giống với Java hơn. Cú pháp HIDL tương tự như C++.
- Tất cả giao diện AIDL đều tích hợp sẵn trạng thái lỗi. Thay vì tạo nhóm quảng cáo tuỳ chỉnh
loại trạng thái, tạo số nguyên trạng thái không đổi trong tệp giao diện và sử dụng
EX_SERVICE_SPECIFIC
trong phần phụ trợ CPP/NDK vàServiceSpecificException
trong phần phụ trợ Java. Xem Lỗi Xử lý. - AIDL không tự động khởi động nhóm luồng khi các đối tượng liên kết được gửi. Bạn phải bắt đầu các quy trình này theo cách thủ công (xem chuỗi quản lý chiến dịch).
- AIDL không huỷ khi lỗi truyền tải không được kiểm tra (HIDL
Return
huỷ bật lỗi chưa đánh dấu). - AIDL chỉ có thể khai báo một loại cho mỗi tệp.
- Các đối số AIDL có thể được chỉ định là vào/ra/ra ngoài cùng với dữ liệu đầu ra (không có "lệnh gọi lại đồng bộ").
- AIDL sử dụng fd làm loại nguyên gốc thay vì tên người dùng.
- HIDL dùng phiên bản lớn cho những thay đổi không tương thích và phiên bản nhỏ cho
các thay đổi tương thích. Trong AIDL, những thay đổi có khả năng tương thích ngược sẽ được thực hiện.
AIDL không có khái niệm rõ ràng về các phiên bản lớn; thay vào đó, đây là
được tích hợp vào tên gói. Ví dụ: AIDL có thể sử dụng tên gói
bluetooth2
. - Theo mặc định, AIDL không kế thừa mức độ ưu tiên theo thời gian thực.
setInheritRt
hàm phải được sử dụng trên mỗi liên kết để bật tính năng kế thừa ưu tiên theo thời gian thực.