穩定的 AIDL

Android 10 添加了對穩定的 Android 接口定義語言 (AIDL) 的支持,這是一種跟踪 AIDL 接口提供的應用程序接口 (API)/應用程序二進制接口 (ABI) 的新方法。穩定的 AIDL 與 AIDL 有以下主要區別:

  • 接口均符合構建系統定義aidl_interfaces
  • 接口只能包含結構化數據。表示所需類型的 Parcelable 根據其 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源文件的列表。用於AIDL類型的路徑Foo在包中定義com.acme應在<base_path>/com/acme/Foo.aidl ,其中<base_path>可能是相關的,其中該目錄的任何目錄Android.bp是。在上文中,例如<base_path> IS srcs/aidl
  • local_include_dir :從包名稱從哪裡開始的路徑。它對應於<base_path>如上所述。
  • imports :列表aidl_interface模塊,這種用途。如果你的AIDL接口,一個使用一個接口或從另一個parcelable aidl_interface ,這裡把它的名字。這可以通過本身是名,是指到最新版本,或與版本後綴(如姓名-V1 )是指一個特定版本。從 Android 12 開始支持指定版本
  • versions :被下冷凍界面的以前版本api_dir ,在Android的11起, versions正在冷凍aidl_api/ name 。如果沒有接口的凍結版本,則不應指定此項,並且不會進行兼容性檢查。
  • stability :此接口的穩定性承諾可選的標誌。目前僅支持"vintf"如果未設置,則這對應於在此編譯上下文中具有穩定性的接口(因此此處加載的接口只能與編譯在一起的東西一起使用,例如在 system.img 上)。如果設置為"vintf"這相當於一個穩定的承諾:接口必須保持穩定,只要它被使用。
  • gen_trace :可選的標誌打開跟踪打開或關閉。默認是false
  • host_supported :可選的標誌,當設置為true ,使生成的庫提供給主機環境。
  • unstable :用來標記該接口並不需要是穩定的可選標誌。當它被設置為true ,構建系統既不接口創建API轉儲,也不需要它來進行更新。
  • backend.<type>.enabled這些標誌切換各後端該AIDL編譯器將生成的代碼的。目前,3後端的支持: javacppndk 。默認情況下,後端全部啟用。當不需要特定後端時,需要明確禁用它。
  • backend.<type>.apex_available :APEX名稱的列表,生成的存根庫可用於。
  • backend.[cpp|java].gen_log可選的標誌控制是否產生用於收集關於所述交易信息附加代碼。
  • backend.[cpp|java].vndk.enabled :可選的標誌,使這個接口VNDK的一部分。默認是false
  • backend.java.platform_apis :可選的標誌控制是否在Java存根庫是針對從平台私有API構建的。這應被設置為"true"stability被設置為"vintf"
  • backend.java.sdk_version :用於指定SDK的Java存根庫對內置的版本可選標誌。默認值是"system_current"這不應該是集時backend.java.platform_apis是真實的。
  • backend.java.platform_apis :應設置為可選的標誌true時產生的庫需要建立對平台的API,而不是SDK。

對於每個組合versions ,並啟用後端,將創建一個存根庫。見模塊命名規則為如何引用存根庫的具體版本為一個特定的後端。

編寫 AIDL 文件

穩定 AIDL 中的接口類似於傳統接口,不同之處在於它們不允許使用非結構化的 Parcelable(因為它們不穩定!)。穩定 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的目標。如果建成,富冰凍-API下增加了一個新的API定義api_diraidl_api/ name ,取決於Android版本,並增加了一個.hash文件,均代表著當前界面的新的凍結版本。建立這也將更新versions屬性,以反映額外的版本。一旦versions指定屬性,構建系統運行冷凍版本之間也頂樹(TOT)和最新的凍結版本之間的兼容性檢查。

此外,您還需要管理 ToT 版本的 API 定義。每當API更新,運行FOO更新-API更新aidl_api/ name /current其中包含版本在ToT的API定義。

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

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

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

使用版本化接口

接口方法

在運行時,試圖調用一個舊服務器上的新方法時,新的客戶端自動獲得UNKNOWN_TRANSACTION 。對於策略來處理這個看到查詢的版本,並使用默認值

包裹

當新字段被添加到 Parcelables 時,舊的客戶端和服務器會刪除它們。當新客戶端和服務器收到舊的 Parcelable 時,新字段的默認值會自動填充。這意味著需要為 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後端,
    • cpp為C ++後端,
    • 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)

與 Android 11 相比,

  • foo- backend ,其中提到最新的穩定版本變得foo- V2 - backend
  • foo-unstable- backend ,其中提到在ToT版本變成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編譯器不建立任何一個unstable版本模塊,或穩定的AIDL接口非版本模塊。從 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 接口。僅當遠程端未實現該方法時才會調用默認實現中的方法(因為它是使用舊版本的接口構建的)。由於默認值是全局設置的,因此不應在潛在共享上下文中使用它們。

C++ 中的示例:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(std::unique_ptr<IFoo>(MyDefault));

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

  3. 創建aidl_interface包含您的模塊,它的依賴,你需要的任何其他信息的名稱,包裝(如上所述)。為了使其穩定(不僅僅是結構化),它還需要進行版本控制。欲了解更多信息,請參閱版本接口