AIDL 後端是存根代碼生成的目標。使用 AIDL 文件時,您始終以具有特定運行時的特定語言使用它們。根據上下文,您應該使用不同的 AIDL 後端。
AIDL 有以下後端:
後端 | 語言 | API 表面 | 構建系統 |
---|---|---|---|
爪哇 | 爪哇 | SDK/SystemApi (穩定*) | 全部 |
NDK | C++ | libbinder_ndk(穩定*) | aidl_interface |
CPP | C++ | libbinder(不穩定) | 全部 |
銹 | 銹 | libbinder_rs(不穩定) | aidl_interface |
- 這些 API 表面是穩定的,但許多 API(例如用於服務管理的 API)是為內部平台使用而保留的,對應用程序不可用。有關如何在應用程序中使用 AIDL 的更多信息,請參閱開發者文檔。
- Rust 後端是在 Android 12 中引入的; NDK 後端從 Android 10 開始可用。
- Rust crate 建立在
libbinder_ndk
。 APEX 使用 binder crate 的方式與系統端的其他任何人相同。 Rust 部分被捆綁到 APEX 中並在其中運送。它取決於系統分區上的libbinder_ndk.so
。
構建系統
根據後端的不同,有兩種方法可以將 AIDL 編譯為存根代碼。有關構建系統的更多詳細信息,請參閱Soong 模塊參考。
核心構建系統
在任何cc_
或java_
Android.bp 模塊(或它們的Android.mk
等效模塊)中,可以將.aidl
文件指定為源文件。在這種情況下,使用 AIDL 的 Java/CPP 後端(不是 NDK 後端),並自動將使用相應 AIDL 文件的類添加到模塊中。諸如local_include_dirs
之類的選項告訴構建系統該模塊中 AIDL 文件的根路徑可以在這些模塊中的aidl:
組中指定。請注意,Rust 後端僅用於 Rust。 rust_
模塊的處理方式不同,因為 AIDL 文件未指定為源文件。相反, aidl_interface
模塊會生成一個名為<aidl_interface name>-rust
rustlib
rustlib,它可以被鏈接。有關更多詳細信息,請參閱Rust AIDL 示例。
aidl_interface
請參閱穩定的 AIDL 。與此構建系統一起使用的類型必須是結構化的;即直接用 AIDL 表示。這意味著不能使用自定義 Parcelables。
類型
您可以將aidl
編譯器視為類型的參考實現。創建接口時,調用aidl --lang=<backend> ...
查看生成的接口文件。當您使用aidl_interface
模塊時,您可以在out/soong/.intermediates/<path to module>/
中查看輸出。
Java/AIDL 類型 | C++ 類型 | NDK 類型 | 銹型 |
---|---|---|---|
布爾值 | 布爾 | 布爾 | 布爾 |
字節 | int8_t | int8_t | i8 |
字符 | char16_t | char16_t | u16 |
整數 | int32_t | int32_t | i32 |
長 | int64_t | int64_t | i64 |
漂浮 | 漂浮 | 漂浮 | f32 |
雙倍的 | 雙倍的 | 雙倍的 | f64 |
細繩 | 安卓::String16 | 標準::字符串 | 細繩 |
android.os.Parcelable | android::Parcelable | 不適用 | 不適用 |
綁定器 | 安卓::IBinder | ndk::SpAIBinder | 活頁夾::SpIBinder |
T[] | 標準::向量<T> | 標準::向量<T> | 在:&T 輸出: Vec<T> |
字節[] | std::vector<uint8_t> | std::vector<int8_t> 1 | 在:&[u8] 輸出:Vec<u8> |
列表<T> | 標準::向量<T> 2 | 標準::向量<T> 3 | 在:&[T] 4 輸出: Vec<T> |
文件描述符 | android::base::unique_fd | 不適用 | binder::parcel::ParcelFileDescriptor |
包裹文件描述符 | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
接口類型 (T) | 安卓::sp<T> | std::shared_ptr<T> | 活頁夾::強 |
可包裹類型 (T) | 噸 | 噸 | 噸 |
聯合類型 (T) 5 | 噸 | 噸 | 噸 |
T[N] 6 | 標準::數組<T, N> | 標準::數組<T, N> | [T; N] |
1. 在 Android 12 或更高版本中,出於兼容性原因,字節數組使用 uint8_t 而不是 int8_t。
2. C++ 後端支持List<T>
其中T
是String
、 IBinder
、 ParcelFileDescriptor
或 parcelable 之一。在 Android T(AOSP 實驗性)或更高版本中, T
可以是除數組之外的任何非原始類型(包括接口類型)。 AOSP 建議您使用像T[]
這樣的數組類型,因為它們適用於所有後端。
3. NDK 後端支持List<T>
,其中T
是String
、 ParcelFileDescriptor
或 parcelable 之一。在 Android T(AOSP 實驗性)或更高版本中, T
可以是除數組之外的任何非原始類型。
4. Rust 代碼的類型傳遞不同,具體取決於它們是輸入(參數)還是輸出(返回值)。
5. Android 12 及更高版本支持聯合類型。
6. 在Android T(AOSP 實驗版)或更高版本中,支持固定大小的數組。固定大小的數組可以有多個維度(例如int[3][4]
)。在 Java 後端,固定大小的數組表示為數組類型。
方向性(輸入/輸出/輸入)
指定函數參數的類型時,可以將它們指定為in
、 out
或inout
。這控制了為 IPC 調用傳遞方向信息的方向。 in
是默認方向,表示數據從調用者傳遞到被調用者。 out
表示數據從被調用者傳遞給調用者。 inout
是這兩者的結合。但是,Android 團隊建議您避免使用參數說明符inout
。如果您將inout
與版本化接口和較舊的被調用者一起使用,則僅存在於調用者中的附加字段將重置為其默認值。對於 Rust,普通的inout
類型接收&mut Vec<T>
,而 list inout
類型接收&mut Vec<T>
。
UTF8/UTF16
使用 CPP 後端,您可以選擇字符串是 utf-8 還是 utf-16。在 AIDL 中將字符串聲明為@utf8InCpp String
以自動將它們轉換為 utf-8。 NDK 和 Rust 後端總是使用 utf-8 字符串。有關utf8InCpp
註釋的更多信息,請參閱AIDL 中的註釋。
可空性
您可以使用@nullable
註釋 Java 後端中可以為空的類型,以將空值公開給 CPP 和 NDK 後端。在 Rust 後端,這些@nullable
類型公開為Option<T>
。默認情況下,本機服務器拒絕空值。唯一的例外是interface
和IBinder
類型,對於 NDK 讀取和 CPP/NDK 寫入,它們始終可以為空。有關nullable
註釋的更多信息,請參閱AIDL 中的註釋。
自定義 Parcelables
在核心構建系統的 C++ 和 Java 後端,您可以聲明在目標後端(C++ 或 Java)中手動實現的 Parcelable。
package my.package;
parcelable Foo;
或使用 C++ 標頭聲明:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
然後你可以在 AIDL 文件中使用這個 parcelable 作為類型,但它不會由 AIDL 生成。
Rust 不支持自定義 parcelables。
默認值
結構化 Parcelable 可以為這些類型的基元、 String
和數組聲明每個字段的默認值。
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
在 Java 後端中,當缺少默認值時,字段被初始化為原始類型的零值和非原始類型的null
。
在其他後端,當未定義默認值時,使用默認初始化值初始化字段。例如,在 C++ 後端, String
字段初始化為空字符串, List<T>
字段初始化為空vector<T>
。 @nullable
字段被初始化為空值字段。
錯誤處理
Android 操作系統提供了內置的錯誤類型供服務在報告錯誤時使用。這些由 binder 使用,並且可以由實現 binder 接口的任何服務使用。它們的使用在 AIDL 定義中有詳細記錄,並且不需要任何用戶定義的狀態或返回類型。
當 AIDL 函數報告錯誤時,該函數可能不會初始化或修改輸出參數。具體來說,如果錯誤發生在拆包期間,而不是發生在交易本身的處理期間,則可以修改輸出參數。一般來說,當從 AIDL 函數中得到錯誤時,所有的inout
和out
參數以及返回值(在某些後端就像一個out
參數)都應該被認為處於不確定狀態。
如果 AIDL 接口需要內置錯誤類型未涵蓋的其他錯誤值,則它們可以使用特殊的特定於服務的內置錯誤,該錯誤允許包含由用戶定義的特定於服務的錯誤值.這些特定於服務的錯誤通常在 AIDL 接口中定義為const int
或int
支持的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 |
CPP | binder/Status.h |
NDK | android/binder_status.h |
銹 | android/binder_status.h |
使用各種後端
這些說明特定於 Android 平台代碼。這些示例使用定義的類型my.package.IFoo
。有關如何使用 Rust 後端的說明,請參閱Android Rust 模式頁面上的Rust AIDL 示例。
導入類型
無論定義的類型是接口、parcelable 還是聯合,都可以在 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 後端中,您必須包含其根類型的標頭。例如,當導入在my/package/IFoo.aidl
中定義的嵌套類型Bar
( IFoo
是文件的根類型)時,您必須為 CPP 後端包含<my/package/IFoo.h>
(或<aidl/my/package/IFoo.h>
用於 NDK 後端)。
實施服務
要實現服務,您必須從本機存根類繼承。此類從活頁夾驅動程序讀取命令並執行您實現的方法。想像一下,你有一個這樣的 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(())
}
}
註冊和獲取服務
Android 平台中的服務通常註冊到servicemanager
進程中。除了下面的 API 之外,一些 API 還會檢查服務(這意味著如果服務不可用,它們會立即返回)。檢查相應的servicemanager
接口以獲取確切的詳細信息。這些操作只能在針對 Android 平台編譯時進行。
在 Java 中:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// getting
myService = IFoo.Stub.asInterface(ServiceManager.getService("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);
// getting
status_t err = getService<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
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// getting
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("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(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()
}
與死亡相連
您可以請求獲取有關託管活頁夾的服務何時終止的通知。這可以幫助避免洩漏回調代理或幫助錯誤恢復。在活頁夾代理對像上進行這些調用。
- 在 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
當錯誤報告運行時(例如,使用adb bugreport
),它們會從系統周圍收集信息以幫助調試各種問題。對於 AIDL 服務,錯誤報告使用服務管理器註冊的所有服務上的二進制dumpsys
將其信息轉儲到錯誤報告中。您還可以在命令行上使用dumpsys
從帶有dumpsys SERVICE [ARGS]
的服務中獲取信息。在 C++ 和 Java 後端,您可以通過使用addService
的附加參數來控制服務轉儲的順序。您還可以在調試時使用dumpsys --pid SERVICE
來獲取服務的 PID。
要將自定義輸出添加到您的服務,您可以覆蓋服務器對像中的dump
方法,就像您實現 AIDL 文件中定義的任何其他 IPC 方法一樣。執行此操作時,您應該限制轉儲到應用程序權限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 中,您可以通過添加以下代碼來獲取描述符:
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()
枚舉範圍
在本機後端,您可以迭代枚舉可以採用的可能值。由於代碼大小的考慮,Java 目前不支持此功能。
對於 AIDL 中定義的枚舉MyEnum
,迭代提供如下。
在 CPP 後端:
::android::enum_range<MyEnum>()
在 NDK 後端:
::ndk::enum_range<MyEnum>()
在 Rust 後端:
MyEnum::enum_range()
線程管理
進程中的每個libbinder
實例都維護一個線程池。對於大多數用例,這應該是一個線程池,在所有後端共享。唯一的例外是供應商代碼可能會加載另一個libbinder
副本以與/dev/vndbinder
。由於這是在單獨的綁定器節點上,因此線程池不共享。
對於 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();