Android 10 增加了對穩定的 Android 接口定義語言 (AIDL) 的支持,這是一種跟踪 AIDL 接口提供的應用程序接口 (API)/應用程序二進制接口 (ABI) 的新方法。穩定的 AIDL 與 AIDL 有以下主要區別:
- 接口在構建系統中使用
aidl_interfaces
定義。 - 接口只能包含結構化數據。代表所需類型的 Parcelables 是根據它們的 AIDL 定義自動創建的,並且會自動編組和解組。
- 接口可以聲明為穩定的(向後兼容)。發生這種情況時,他們的 API 會在 AIDL 接口旁邊的文件中進行跟踪和版本控制。
定義 AIDL 接口
aidl_interface
的定義如下所示:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions_with_info: [
{
version: "1",
imports: ["other-aidl-V1"],
},
{
version: "2",
imports: ["other-aidl-V3"],
}
],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
-
name
:AIDL接口模塊的名稱,唯一標識一個AIDL接口。 -
srcs
:構成接口的 AIDL 源文件列表。包com.acme
中定義的 AIDL 類型Foo
的路徑應位於<base_path>/com/acme/Foo.aidl
,其中<base_path>
可以是與Android.bp
所在目錄相關的任何目錄。在上面的示例中,<base_path>
是srcs/aidl
。 -
local_include_dir
:包名稱開始的路徑。它對應於上面解釋的<base_path>
。 -
imports
:它使用的aidl_interface
模塊列表。如果您的一個 AIDL 接口使用了另一個aidl_interface
接口或 parcelable,請將其名稱放在這裡。這可以是名稱本身,以引用最新版本,或帶有版本後綴的名稱(例如-V1
)以引用特定版本。從Android 12開始支持指定版本 versions
:凍結在api_dir
下的接口的先前版本,從 Android 11 開始,versions
凍結在aidl_api/ name
下。如果接口沒有凍結版本,則不應指定,並且不會進行兼容性檢查。對於 13 及更高版本,此字段已替換為versions_with_info
。-
versions_with_info
:元組列表,每個元組包含一個凍結版本的名稱和一個列表,其中包含此版本的 aidl_interface 導入的其他 aidl_interface 模塊的版本導入。 AIDL 接口 IFACE 的版本 V 的定義位於aidl_api/ IFACE / V
。該字段是Android 13引入的,不能直接在Android.bp中修改。通過調用*-update-api
或*-freeze-api
添加或更新該字段。此外,當用戶調用*-update-api
或*-freeze-api
時,versions
字段會自動遷移到versions_with_info
。 -
stability
:此接口的穩定性承諾的可選標誌。目前僅支持"vintf"
。如果未設置,則對應於在此編譯上下文中具有穩定性的接口(因此此處加載的接口只能與一起編譯的東西一起使用,例如在 system.img 上)。如果將其設置為"vintf"
,則這對應於穩定性承諾:接口必須在使用期間保持穩定。 -
gen_trace
:用於打開或關閉跟踪的可選標誌。默認為false
。 -
host_supported
:可選標誌,當設置為true
時,生成的庫可用於主機環境。 -
unstable
:用於標記此接口不需要穩定的可選標誌。設置為true
時,構建系統既不會為接口創建 API 轉儲,也不會要求對其進行更新。 -
frozen
:可選標誌,當設置為true
時表示該接口自上一版本接口以來沒有任何變化。這會啟用更多構建時檢查。當設置為false
時,這意味著接口正在開發中並且有新的更改,因此運行foo-freeze-api
將生成一個新版本並自動將值更改為true
。在 Android 14 中引入(AOSP 實驗性)。 -
backend.<type>.enabled
:這些標誌切換 AIDL 編譯器為其生成代碼的每個後端。目前支持四種後端:Java、C++、NDK 和 Rust。默認啟用 Java、C++ 和 NDK 後端。如果不需要這三個後端中的任何一個,則需要明確禁用它。默認情況下禁用 Rust。 -
backend.<type>.apex_available
:生成的存根庫可用於的 APEX 名稱列表。 -
backend.[cpp|java].gen_log
:可選標誌,用於控制是否生成附加代碼以收集有關事務的信息。 -
backend.[cpp|java].vndk.enabled
:使該接口成為 VNDK 一部分的可選標誌。默認為false
。 -
backend.java.sdk_version
:用於指定構建 Java 存根庫所針對的 SDK 版本的可選標誌。默認值為"system_current"
。當backend.java.platform_apis
為真時不應設置此項。 -
backend.java.platform_apis
:當生成的庫需要針對平台 API 而不是 SDK 構建時,應設置為true
的可選標誌。
對於版本和啟用的後端的每個組合,都會創建一個存根庫。特定後端如何引用特定版本的存根庫,參見模塊命名規則。
編寫 AIDL 文件
穩定 AIDL 中的接口類似於傳統接口,不同之處在於它們不允許使用非結構化的 parcelables(因為它們不穩定!)。穩定版 AIDL 的主要區別在於 parcelable 的定義方式。以前,parcelables 是預先聲明的;在穩定的 AIDL 中,parcelables 字段和變量是明確定義的。
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
boolean
、 char
、 float
、 double
、 byte
、 int
、 long
和String
當前支持(但不是必需的)默認值。在 Android 12 中,還支持用戶定義枚舉的默認值。如果未指定默認值,則使用類似 0 或空值。沒有默認值的枚舉被初始化為 0,即使沒有零枚舉器也是如此。
使用存根庫
將存根庫作為依賴項添加到模塊後,您可以將它們包含到文件中。以下是構建系統中存根庫的示例( Android.mk
也可用於遺留模塊定義):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if desire is to load a library and share
// it among multiple users or if you only need access to constants
static_libs: ["my-module-name-java"],
...
}
# or
rust_... {
name: ...,
rust_libs: ["my-module-name-rust"],
...
}
C++ 中的示例:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
Java 中的示例:
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
Rust 中的示例:
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
版本控制接口
聲明一個名為foo的模塊還會在構建系統中創建一個目標,您可以使用它來管理模塊的 API。構建時, foo-freeze-api在api_dir
或aidl_api/ name
下添加一個新的 API 定義,具體取決於 Android 版本,並添加一個.hash
文件,兩者都代表接口的新凍結版本。 foo-freeze-api還更新了versions_with_info
屬性以反映該版本的附加版本和imports
。基本上, versions_with_info
中的imports
是從imports
字段複製的。但是最新的穩定版本是在versions_with_info
imports
中為沒有明確版本的導入指定的。一旦指定了versions_with_info
屬性,構建系統就會在凍結版本之間以及樹頂 (ToT) 和最新凍結版本之間運行兼容性檢查。
此外,您需要管理 ToT 版本的 API 定義。每當 API 更新時,運行foo-update-api來更新aidl_api/ name /current
,其中包含 ToT 版本的 API 定義。
為了保持接口的穩定性,所有者可以添加新的:
- 接口結束的方法(或具有明確定義的新序列號的方法)
- 元素到 parcelable 的末尾(需要為每個元素添加默認值)
- 常數值
- 在 Android 11 中,枚舉器
- 在 Android 12 中,聯合體末尾的字段
不允許其他操作,也沒有其他人可以修改接口(否則他們可能會與所有者所做的更改發生衝突)。
要測試所有接口是否已凍結以供發布,您可以使用以下環境變量集進行構建:
-
AIDL_FROZEN_REL=true m ...
- 構建需要凍結所有穩定的 AIDL 接口,這些接口沒有指定owner:
字段。 -
AIDL_FROZEN_OWNERS="aosp test"
- 構建要求所有穩定的 AIDL 接口與owner:
指定為“aosp”或“test”的字段。
進口穩定性
為接口的凍結版本更新導入版本在穩定的 AIDL 層是向後兼容的。然而,更新這些需要更新所有使用舊版本接口的服務器和客戶端,並且一些應用程序在混合不同版本的類型時可能會混淆。通常,對於僅類型或通用包,這是安全的,因為需要已經編寫代碼來處理來自 IPC 事務的未知類型。
Android平台代碼android.hardware.graphics.common
就是這種版本升級的最大例子。
使用版本化接口
接口方法
在運行時,當嘗試在舊服務器上調用新方法時,新客戶端會收到錯誤或異常,具體取決於後端。
-
cpp
後端獲取::android::UNKNOWN_TRANSACTION
。 -
ndk
後端獲取STATUS_UNKNOWN_TRANSACTION
。 -
java
後端獲取android.os.RemoteException
並顯示一條消息,說明 API 未實現。
包裹
當新字段添加到 parcelables 時,舊的客戶端和服務器會丟棄它們。當新客戶端和服務器收到舊的 parcelables 時,新字段的默認值會自動填寫。這意味著需要為 parcelable 中的所有新字段指定默認值。
客戶端不應該期望服務器使用新字段,除非他們知道服務器正在實現定義了字段的版本(請參閱查詢版本)。
枚舉和常量
同樣,客戶端和服務器應酌情拒絕或忽略無法識別的常量值和枚舉器,因為將來可能會添加更多。例如,服務器在收到它不知道的枚舉器時不應中止。它應該忽略它,或者返回一些東西,以便客戶端知道它在此實現中不受支持。
工會
如果接收方是舊的並且不知道該字段,則嘗試發送具有新字段的聯合會失敗。實現永遠不會看到與新字段的聯合。如果是單向事務,則忽略失敗;否則錯誤是BAD_VALUE
(對於 C++ 或 NDK 後端)或IllegalArgumentException
(對於 Java 後端)。如果客戶端將新字段的聯合集發送到舊服務器,或者舊客戶端從新服務器接收聯合,則會收到錯誤。
模塊命名規則
在 Android 11 中,對於啟用的版本和後端的每個組合,都會自動創建一個存根庫模塊。要引用特定的存根庫模塊進行鏈接,不要使用aidl_interface
模塊的名稱,而是使用存根庫模塊的名稱,即ifacename - version - backend ,其中
ifacename
:aidl_interface
模塊的名稱version
是V version-number
-
V latest-frozen-version-number + 1
樹尖(尚未凍結)版本
backend
是java
用於 Java 後端,- 用於 C++ 後端的
cpp
, - NDK 後端的
ndk
或ndk_platform
。前者是針對應用,後者是針對平台使用, - Rust 後端的
rust
。
假設有一個名為foo的模塊,其最新版本為2 ,並且它同時支持 NDK 和 C++。在這種情況下,AIDL 生成這些模塊:
- 基於版本 1
-
foo-V1-(java|cpp|ndk|ndk_platform|rust)
-
- 基於版本 2(最新穩定版)
-
foo-V2-(java|cpp|ndk|ndk_platform|rust)
-
- 基於 ToT 版本
foo-V3-(java|cpp|ndk|ndk_platform|rust)
與安卓11相比,
-
foo- backend
,指的是最新的穩定版本變成foo- V2 - backend
-
foo-unstable- backend
,指的是 ToT 版本變成foo- V3 - backend
輸出文件名始終與模塊名相同。
- 基於版本 1:
foo-V1-(cpp|ndk|ndk_platform|rust).so
- 基於版本 2:
foo-V2-(cpp|ndk|ndk_platform|rust).so
- 基於 ToT 版本:
foo-V3-(cpp|ndk|ndk_platform|rust).so
請注意,AIDL 編譯器不會為穩定的 AIDL 接口創建unstable
版本模塊或非版本模塊。從 Android 12 開始,從穩定的 AIDL 接口生成的模塊名稱始終包含其版本。
新的元接口方法
Android 10 為穩定的 AIDL 添加了幾個元接口方法。
查詢遠程對象的接口版本
客戶端可以查詢遠程對象正在實現的接口的版本和散列,並將返回值與客戶端正在使用的接口的值進行比較。
cpp
後端示例:
sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();
ndk
(和ndk_platform
)後端示例:
IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);
java
後端示例:
IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
// the remote side is using an older interface
}
String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();
對於 Java 語言,遠程端必須實現getInterfaceVersion()
和getInterfaceHash()
如下(使用super
而不是IFoo
以避免複製/粘貼錯誤。可能需要註釋@SuppressWarnings("static")
來禁用警告,具體取決於javac
配置):
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return super.VERSION; }
@Override
public final String getInterfaceHash() { return super.HASH; }
}
這是因為生成的類( IFoo
、 IFoo.Stub
等)在客戶端和服務器之間共享(例如,這些類可以在引導類路徑中)。共享類時,服務器也會鏈接到最新版本的類,即使它可能是使用舊版本的接口構建的。如果這個元接口在共享類中實現,它總是返回最新版本。然而,通過實現上述方法,接口的版本號被嵌入到服務器的代碼中(因為IFoo.VERSION
是一個static final int
,在引用時被內聯)因此該方法可以返回服務器構建的確切版本和。
處理舊界面
有可能客戶端使用較新版本的 AIDL 接口進行了更新,但服務器使用的是舊的 AIDL 接口。在這種情況下,在舊接口上調用方法會返回UNKNOWN_TRANSACTION
。
有了穩定的 AIDL,客戶就有了更多的控制權。在客戶端,您可以為 AIDL 接口設置默認實現。只有在遠程端未實現該方法時才會調用默認實現中的方法(因為它是使用較舊版本的接口構建的)。由於默認值是全局設置的,因此不應從潛在的共享上下文中使用它們。
Android 13 及更高版本中的 C++ 示例:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
Java 中的示例:
IFoo.Stub.setDefaultImpl(new IFoo.Default() {
@Override
public xxx anAddedMethod(...) throws RemoteException {
// do something default
}
}); // once per an interface in a process
foo.anAddedMethod(...);
您不需要在 AIDL 接口中提供所有方法的默認實現。保證在遠程端實現的方法(因為您確定遠程是在方法在 AIDL 接口描述中時構建的)不需要在默認的impl
類中被覆蓋。
將現有的 AIDL 轉換為結構化/穩定的 AIDL
如果您有現有的 AIDL 接口和使用它的代碼,請使用以下步驟將該接口轉換為穩定的 AIDL 接口。
確定接口的所有依賴項。對於接口所依賴的每個包,確定包是否在穩定的 AIDL 中定義。如果未定義,則必須轉換包。
將界面中的所有 parcelables 轉換為穩定的 parcelables(界面文件本身可以保持不變)。通過直接在 AIDL 文件中表達它們的結構來做到這一點。必須重寫管理類才能使用這些新類型。這可以在您創建
aidl_interface
包之前完成(如下)。創建一個
aidl_interface
包(如上所述),其中包含您的模塊名稱、其依賴項以及您需要的任何其他信息。為了使其穩定(不僅僅是結構化),它還需要進行版本控制。有關詳細信息,請參閱版本控制接口。