穩定的 AIDL

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

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: ["1", "2"],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            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的可分包,請將其名稱放在這裡。這可以是名稱本身,以引用最新版本,也可以是帶有版本後綴的名稱(例如-V1 )以引用特定版本。自 Android 12 起支持指定版本
  • versions : 之前凍結在api_dir下的接口版本,從 Android 11 開始,凍結在aidl_api/ name下的versions 。如果接口沒有凍結版本,則不應指定,並且不會進行兼容性檢查。
  • stability :此接口的穩定性承諾的可選標誌。目前只支持"vintf" 。如果未設置,則這對應於在此編譯上下文中具有穩定性的接口(因此此處加載的接口只能與一起編譯的東西一起使用,例如在 system.img 上)。如果將其設置為"vintf" ,則對應於穩定性承諾:接口必須在使用時保持穩定。
  • gen_trace :打開或關閉跟踪的可選標誌。默認為false
  • host_supported :可選標誌,當設置為true時,生成的庫可用於主機環境。
  • unstable :用於標記此接口不需要穩定的可選標誌。當它設置為true時,構建系統既不會為接口創建 API 轉儲,也不需要更新它。
  • backend.<type>.enabled :這些標誌切換 AIDL 編譯器將為其生成代碼的每個後端。目前,支持三個後端: javacppndk 。默認情況下,後端都啟用。當不需要特定後端時,需要明確禁用它。
  • backend.<type>.apex_available :生成的存根庫可用的 APEX 名稱列表。
  • backend.[cpp|java].gen_log :可選標誌,控制是否生成額外代碼以收集有關事務的信息。
  • backend.[cpp|java].vndk.enabled :使此接口成為 VNDK 一部分的可選標誌。默認為false
  • backend.java.platform_apis :控制 Java 存根庫是否針對來自平台的私有 API 構建的可選標誌。當stability設置為"vintf"時,應將其設置為"true" "。
  • backend.java.sdk_version :用於指定構建 Java 存根庫的 SDK 版本的可選標誌。默認值為"system_current" 。當backend.java.platform_apis為 true 時,不應設置此項。
  • backend.java.platform_apis :當生成的庫需要針對平台 API 而不是 SDK 構建時,應該設置為true的可選標誌。

對於versions和啟用的後端的每個組合,都會創建一個存根庫。請參閱模塊命名規則,了解如何為特定後端引用特定版本的存根庫。

編寫 AIDL 文件

穩定 AIDL 中的接口類似於傳統接口,不同之處在於它們不允許使用非結構化 Parcelables(因為它們不穩定!)。穩定 AIDL 的主要區別在於如何定義 parcelables。以前,parcelables 是提前申報的;在穩定的 AIDL 中,parcelables 字段和變量是明確定義的。

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

booleancharfloatdoublebyteintlongString目前支持(但不是必需的)默認值。在 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"],
    ...
}

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

版本控制接口

聲明一個名為foo的模塊還會在構建系統中創建一個目標,您可以使用它來管理模塊的 API。構建時, foo-freeze-apiapi_diraidl_api/ name下添加一個新的 API 定義,具體取決於 Android 版本,並添加一個.hash文件,兩者都代表新凍結的接口版本。構建它還會更新versions屬性以反映附加版本。一旦指定了versions屬性,構建系統就會在凍結版本之間以及樹頂 (ToT) 和最新的凍結版本之間運行兼容性檢查。

此外,您還需要管理 ToT 版本的 API 定義。每當更新 API 時,運行foo-update-api以更新包含 ToT 版本的 API 定義的aidl_api/ name /current

為了保持界面的穩定性,所有者可以添加新的:

  • 接口末尾的方法(或具有明確定義的新序列的方法)
  • Parcelable 末尾的元素(需要為每個元素添加默認值)
  • 常數值
  • 在 Android 11 中,枚舉器
  • 在 Android 12 中,聯合結束的字段

不允許其他操作,其他任何人都不能修改界面(否則它們可能會與所有者所做的更改發生衝突)。

要測試所有接口是否已凍結以供發布,您可以使用以下環境變量集進行構建:

  • AIDL_FROZEN_REL=true m ... - 構建需要凍結所有沒有owner:指定字段。
  • AIDL_FROZEN_OWNERS="aosp test" - 構建要求所有穩定的 AIDL 接口與owner:字段指定為“aosp”或“test”。

使用版本化接口

接口方法

在運行時,當嘗試在舊服務器上調用新方法時,新客戶端會收到錯誤或異常,具體取決於後端。

  • cpp後端獲取::android::UNKNOWN_TRANSACTION
  • ndk後端獲取STATUS_UNKNOWN_TRANSACTION
  • java後端獲取android.os.RemoteException並顯示 API 未實現的消息。

有關處理此問題的策略,請參閱查詢版本使用默認值

Parcelables

當新字段被添加到 parcelables 時,舊的客戶端和服務器會刪除它們。當新的客戶端和服務器收到舊的 Parcelable 時,會自動填寫新字段的默認值。這意味著需要為 Parcelable 中的所有新字段指定默認值。

客戶端不應期望服務器使用新字段,除非他們知道服務器正在實現已定義字段的版本(請參閱查詢版本)。

枚舉和常量

同樣,客戶端和服務器應該酌情拒絕或忽略無法識別的常量值和枚舉器,因為將來可能會添加更多。例如,當服務器收到一個它不知道的枚舉器時,它不應該中止。它應該要么忽略它,要么返回一些東西,以便客戶端知道它在此實現中不受支持。

工會

如果接收者是舊的並且不知道該字段,則嘗試發送帶有新字段的聯合會失敗。實現將永遠不會看到與新字段的聯合。如果是單向事務,則忽略失敗;否則錯誤是BAD_VALUE (對於 C++ 或 NDK 後端)或IllegalArgumentException (對於 Java 後端)。如果客戶端正在將新字段的聯合集發送到舊服務器,或者當它是舊客戶端從新服務器接收聯合時,則會收到錯誤。

模塊命名規則

在 Android 11 中,對於啟用的版本和後端的每種組合,都會自動創建一個存根庫模塊。要引用特定的存根庫模塊進行鏈接,不要使用aidl_interface模塊的名稱,而是存根庫模塊的名稱,即ifacename - version - backend ,其中

  • ifacenameaidl_interface模塊的名稱
  • version
    • 凍結版本的V version-number
    • V latest-frozen-version-number + 1表示樹尖(尚未凍結)版本
  • backend
    • 用於 Java 後端的java
    • C++ 後端的cpp
    • ndkndk_platform用於 NDK 後端。前者用於應用程序,後者用於平台使用。

假設有一個名為foo的模塊,它的最新版本是2 ,它同時支持 NDK 和 C++。在這種情況下,AIDL 會生成以下模塊:

  • 基於版本 1
    • foo-V1-(java|cpp|ndk|ndk_platform)
  • 基於版本 2(最新穩定版)
    • foo-V2-(java|cpp|ndk|ndk_platform)
  • 基於 ToT 版本
    • foo-V3-(java|cpp|ndk|ndk_platform)

與安卓 11 相比,

  • foo- backend ,指的是最新的穩定版本,變成foo- V2 - backend
  • foo-unstable- backend ,指 ToT 版本變成 foo- foo- V3 - backend

輸出文件名始終與模塊名相同。

  • 基於版本 1: foo-V1-(cpp|ndk|ndk_platform).so
  • 基於版本 2: foo-V2-(cpp|ndk|ndk_platform).so
  • 基於 ToT 版本: foo-V3-(cpp|ndk|ndk_platform).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() ,如下所示:

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return IFoo.VERSION; }

    @Override
    public final String getInterfaceHash() { return IFoo.HASH; }
}

這是因為生成的類( IFooIFoo.Stub等)在客戶端和服務器之間共享(例如,這些類可以在引導類路徑中)。共享類時,服務器也會鏈接到最新版本的類,即使它可能是使用舊版本的接口構建的。如果這個元接口在共享類中實現,它總是返回最新版本。但是,通過實現上述方法,接口的版本號嵌入在服務器的代碼中(因為IFoo.VERSION是一個static final int ,在引用時是內聯的),因此該方法可以返回構建服務器的確切版本和。

處理舊接口

客戶端可能使用較新版本的 AIDL 接口進行更新,但服務器使用的是舊的 AIDL 接口。在這種情況下,在舊接口上調用方法會返回UNKNOWN_TRANSACTION

有了穩定的 AIDL,客戶有更多的控制權。在客戶端,您可以將默認實現設置為 AIDL 接口。只有在遠程端沒有實現該方法時才會調用默認實現中的方法(因為它是使用舊版本的接口構建的)。由於默認值是全局設置的,因此不應在可能共享的上下文中使用它們。

Android T(AOSP 實驗性)及更高版本中的 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 接口。

  1. 識別接口的所有依賴項。對於接口依賴的每個包,確定包是否在穩定的 AIDL 中定義。如果未定義,則必須轉換包。

  2. 將界面中的所有 Parcelable 轉換為穩定的 Parcelable(界面文件本身可以保持不變)。通過直接在 AIDL 文件中表達它們的結構來做到這一點。必須重寫管理類以使用這些新類型。這可以在創建aidl_interface包之前完成(如下)。

  3. 創建一個aidl_interface包(如上所述),其中包含您的模塊名稱、它的依賴項以及您需要的任何其他信息。為了使其穩定(不僅僅是結構化),還需要對其進行版本控制。有關詳細信息,請參閱版本控制接口