穩定版 AIDL

Android 10 新增對穩定版 Android 介面定義語言 (AIDL) 的支援,可透過 AIDL 介面追蹤應用程式設計介面 (API) 和應用程式二進位介面 (ABI)。穩定版 AIDL 的運作方式與 AIDL 完全相同,但建構系統會追蹤介面相容性,且您可執行的操作會受到限制:

  • 介面是在建構系統中以 aidl_interfaces 定義。
  • 介面只能包含結構化資料。系統會根據偏好型別的 AIDL 定義,自動建立代表偏好型別的 Parcelable,並自動封送及取消封送。
  • 介面可以宣告為穩定 (回溯相容)。發生這種情況時,系統會追蹤其 API,並在 AIDL 介面旁的檔案中進行版本控管。

結構化與穩定版 AIDL

結構化 AIDL 是指純粹以 AIDL 定義的型別。舉例來說,可打包宣告 (自訂可打包) 並非結構化 AIDL。在 AIDL 中定義欄位的 Parcelable 稱為結構化 Parcelable

穩定版 AIDL 需要結構化 AIDL,建構系統和編譯器才能瞭解對可打包物件所做的變更是否向後相容。不過,並非所有結構化介面都穩定。如要確保介面穩定,介面只能使用結構化型別,且必須使用下列版本控管功能。反之,如果介面是使用核心建構系統建構,或已設定 unstable:true,則介面不穩定。

定義 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 介面的專屬 ID。
  • 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/nameversions如果介面沒有凍結版本,則不應指定此項目,也不會進行相容性檢查。這個欄位已由 Android 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"。如果未設定 stability,建構系統會檢查介面是否向後相容,除非指定 unstable。如果未設定,則對應於這個編譯環境中的穩定性介面 (因此是所有系統項目,例如 system.img 和相關分割區中的項目,或是所有供應商項目,例如 vendor.img 和相關分割區中的項目)。如果 stability 設為 "vintf",這就代表穩定性保證:只要使用介面,就必須保持穩定。
  • gen_trace:選用旗標,可開啟或關閉追蹤功能。自 Android 14 起,cppjava 後端的預設值為 true
  • host_supported:選用旗標,設為 true 時,產生的程式庫可供主機環境使用。
  • unstable:選用標記,用於標示這個介面不需要穩定。如果設為 true,建構系統就不會為介面建立 API 傾印,也不會要求更新。
  • frozen:選用標記,設為 true 時表示介面與上一個版本相同。這可啟用更多建構時間檢查。如果設為 false,表示介面正在開發中,且有新的變更,因此執行 foo-freeze-api 會產生新版本,並自動將值變更為 true。這項功能已在 Android 14 推出。
  • backend.<type>.enabled:這些標記會切換 AIDL 編譯器產生程式碼的每個後端。支援四個後端:Java、C++、NDK 和 Rust。Java、C++ 和 NDK 後端預設為啟用。如果不需要這三種後端中的任何一種,就必須明確停用。在 Android 15 推出前,Rust 預設為停用。
  • backend.<type>.apex_available:可使用產生的 Stub 程式庫的 APEX 名稱清單。
  • backend.[cpp|java].gen_log:選用旗標,可控制是否產生額外程式碼,以收集交易相關資訊。
  • backend.[cpp|java].vndk.enabled:這個選用標記可將這個介面設為 VNDK 的一部分。預設值為 false
  • backend.[cpp|ndk].additional_shared_libraries:Android 14 中導入的這項標記會將依附元件新增至原生程式庫。這個標記適用於 ndk_headercpp_header
  • backend.java.sdk_version:選用標記,用於指定建構 Java 存根程式庫時所用的 SDK 版本。預設值為 "system_current"。如果 backend.java.platform_apistrue,則不應設定此屬性。
  • backend.java.platform_apis:選用旗標,如果產生的程式庫需要根據平台 API (而非 SDK) 建構,則應設為 true

針對各個版本和已啟用後端的組合,系統會建立存根程式庫。如要瞭解如何參照特定後端的虛設常式程式庫特定版本,請參閱「模組命名規則」。

編寫 AIDL 檔案

穩定版 AIDL 中的介面與傳統介面類似,但不得使用非結構化可 Parcel 化物件 (因為這些物件不穩定!請參閱「結構化與穩定版 AIDL」)。穩定版 AIDL 的主要差異在於如何定義可打包物件。先前,可打包物件是轉送宣告;在穩定 (因此結構化) 的 AIDL 中,可打包物件欄位和變數會明確定義。

// 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: ...,
    // use `shared_libs:` to load your library and its transitive dependencies
    // dynamically
    shared_libs: ["my-module-name-cpp"],
    // use `static_libs:` to include the library in this binary and drop
    // transitive dependencies
    static_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // use `static_libs:` to add all jars and classes to this jar
    static_libs: ["my-module-name-java"],
    // use `libs:` to make these classes available during build time, but
    // not add them to the jar, in case the classes are already present on the
    // boot classpath (such as if it's in framework.jar) or another jar.
    libs: ["my-module-name-java"],
    // use `srcs:` with `-java-sources` if you want to add classes in this
    // library jar directly, but you get transitive dependencies from
    // somewhere else, such as the boot classpath or another jar.
    srcs: ["my-module-name-java-source", ...],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["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_diraidl_api/name 下新增 API 定義 (視 Android 版本而定),並新增 .hash 檔案,兩者都代表介面的新凍結版本。foo-freeze-api 也會更新 versions_with_info 屬性,以反映額外版本和該版本的 imports。基本上,imports 中的 versions_with_info 是從 imports 欄位複製而來。但匯入的 imports 中指定了 versions_with_info 的最新穩定版,且沒有明確版本。指定 versions_with_info 屬性後,建構系統會執行凍結版本之間的相容性檢查,以及樹狀結構頂端 (ToT) 與最新凍結版本之間的相容性檢查。

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

為確保介面穩定性,擁有者可以新增下列項目:

  • 介面結尾的方法 (或明確定義新序列的方法)
  • 元素 (須為每個元素新增預設值)
  • 常數值
  • 在 Android 11 中,列舉值
  • 在 Android 12 中,聯集結尾的欄位

不得執行其他動作,且其他人無法修改介面 (否則可能會與擁有者所做的變更發生衝突)。

如要測試所有介面是否已凍結以供發布,您可以建構並設定下列環境變數:

  • AIDL_FROZEN_REL=true m ... - build 需要凍結所有穩定的 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 未實作的訊息。

如需處理這類問題的策略,請參閱查詢版本使用預設值

Parcelable

如果可封送物件新增欄位,舊版用戶端和伺服器會捨棄這些欄位。 當新用戶端和伺服器收到舊的 Parcelable 時,系統會自動填入新欄位的預設值。也就是說,您必須為 Parcelable 中的所有新欄位指定預設值。

除非用戶端知道伺服器實作了定義欄位的版本 (請參閱查詢版本),否則不應預期伺服器會使用新欄位。

列舉和常數

同樣地,用戶端和伺服器應視情況拒絕或忽略無法辨識的常數值和列舉值,因為日後可能會新增更多。舉例來說,伺服器收到不明列舉值時,不應中止作業。伺服器應忽略列舉值,或傳回某些內容,讓用戶端知道這個實作方式不支援列舉值。

聯集

如果接收者是舊版,且不瞭解該欄位,則嘗試傳送含有新欄位的聯集會失敗。實作方式永遠不會與新欄位聯集。如果是單向交易,系統會忽略失敗情形;否則錯誤為 BAD_VALUE(適用於 C++ 或 NDK 後端) 或 IllegalArgumentException(適用於 Java 後端)。如果用戶端將聯集傳送至舊伺服器的新欄位,或是舊用戶端從新伺服器接收聯集,就會收到這項錯誤。

管理多個版本

為避免產生的 aidl 型別有多個定義,Android 中的連結器命名空間只能有特定 aidl 介面的 1 個版本。C++ 具有單一定義規則,規定每個符號只能有一個定義。

如果模組依附於相同 aidl_interface 程式庫的不同版本,Android 建構作業就會提供錯誤。模組可能直接或間接依附於這些程式庫,方法是透過依附元件的依附元件。這些錯誤會顯示從失敗模組到衝突版本 aidl_interface 程式庫的依附元件圖。所有依附元件都必須更新,才能包含這些程式庫的相同 (通常是最新) 版本。

如果介面程式庫由許多不同模組使用,為需要使用相同版本的任何程式庫和程序群組建立 cc_defaultsjava_defaultsrust_defaults,會很有幫助。導入新版介面時,可以更新這些預設值,所有使用這些預設值的模組也會一併更新,確保模組不會使用不同版本的介面。

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

aidl_interface 模組匯入其他 aidl_interface 模組時,會建立額外的依附元件,這些依附元件必須搭配特定版本使用。如果有多個 aidl_interface 模組匯入通用 aidl_interface 模組,且這些模組在相同程序中一起使用,管理起來就會很困難。

aidl_interfaces_defaults 可用來保留 aidl_interface 依附元件最新版本的定義,這些定義可在單一位置更新,並供所有想匯入該通用介面的 aidl_interface 模組使用。

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

以旗標為主的開發

開發中的 (未凍結) 介面無法在發布裝置上使用,因為不保證具有回溯相容性。

AIDL 支援這些未凍結介面程式庫的執行階段備用方案,以便針對最新未凍結版本編寫程式碼,並在發布裝置上使用。用戶端的回溯相容行為與現有行為類似,且實作項目也需要遵循這些行為。請參閱「使用版本化介面」。

AIDL 建構旗標

控制這項行為的標記是在 build/release/build_flags.bzl 中定義的 RELEASE_AIDL_USE_UNFROZENtrue 表示在執行階段使用介面的未凍結版本,false 則表示未凍結版本的程式庫行為都與最後凍結的版本相同。您可以覆寫標記,以便進行本機開發,但發布前必須還原為 falsetrue通常開發作業會使用已將旗標設為 true 的設定完成。

相容性矩陣和資訊清單

供應商介面物件 (VINTF 物件) 會定義預期的版本,以及供應商介面兩端提供的版本。

大多數非 Cuttlefish 裝置只會在介面凍結後,以最新的相容性矩陣為目標,因此根據 RELEASE_AIDL_USE_UNFROZEN 建立的 AIDL 程式庫沒有差異。

矩陣

合作夥伴擁有的介面會新增至裝置專屬或產品專屬的相容性矩陣,裝置會在開發期間以這些矩陣為目標。因此,當介面的新版本 (未凍結) 新增至相容性矩陣時,先前的凍結版本必須保留 RELEASE_AIDL_USE_UNFROZEN=false。如要處理這個問題,請為不同的 RELEASE_AIDL_USE_UNFROZEN 設定使用不同的相容性矩陣檔案,或在所有設定使用的單一相容性矩陣檔案中,同時允許這兩個版本。

舉例來說,新增未凍結的第 4 版時,請使用 <version>3-4</version>

凍結版本 4 後,您可以從相容性矩陣中移除版本 3,因為系統會在 RELEASE_AIDL_USE_UNFROZENfalse 時使用凍結的版本 4。

資訊清單

Android 15 導入了 libvintf 的變更,可根據 RELEASE_AIDL_USE_UNFROZEN 的值,在建構期間修改資訊清單檔案。

資訊清單和資訊清單片段會宣告服務實作的介面版本。使用最新未凍結的介面版本時,必須更新資訊清單,反映這個新版本。當 libvintf 調整資訊清單項目以反映產生的 AIDL 程式庫變更時,RELEASE_AIDL_USE_UNFROZEN=false版本會從未凍結版本 N 修改為最後凍結版本 N - 1。因此,使用者不必為每項服務管理多個資訊清單或資訊清單片段。

HAL 用戶端變更

HAL 用戶端程式碼必須與每個先前支援的凍結版本回溯相容。當 RELEASE_AIDL_USE_UNFROZENfalse 時,服務一律會與最後凍結的版本或更早的版本相同 (例如,呼叫新的未凍結方法會傳回 UNKNOWN_TRANSACTION,或新的 parcelable 欄位會採用預設值)。Android 架構用戶端必須與其他舊版向後相容,但這是供應商用戶端和合作夥伴擁有介面的用戶端的新詳細資料。

HAL 實作異動

使用旗標開發 HAL 時,與 HAL 實作項目必須向後相容於最後凍結版本,才能在 RELEASE_AIDL_USE_UNFROZENfalse 時運作,這是與旗標式開發最大的差異。在實作項目和裝置程式碼中考慮回溯相容性,是新的練習。請參閱「使用已加入版本的介面」。

一般而言,用戶端和伺服器,以及架構程式碼和供應商程式碼的向後相容性考量因素相同,但您現在有效實作兩個使用相同原始碼 (目前未凍結的版本) 的版本,因此需要注意細微差異。

示例:介面有三個凍結版本。介面會更新為新方法。用戶端和服務都會更新,改用新的第 4 版程式庫。由於 V4 程式庫是以未凍結的介面版本為基礎,因此在 RELEASE_AIDL_USE_UNFROZENfalse 時,其行為與最後凍結的版本 (版本 3) 相同,並會禁止使用新方法。

介面凍結後,RELEASE_AIDL_USE_UNFROZEN 的所有值都會使用該凍結版本,且處理回溯相容性的程式碼可以移除。

在回呼中呼叫方法時,您必須妥善處理傳回 UNKNOWN_TRANSACTION 的情況。用戶端可能會根據發布設定實作兩個不同版本的回呼,因此您無法假設用戶端會傳送最新版本,而新方法可能會傳回這個值。這與穩定版 AIDL 用戶端如何維持與伺服器的回溯相容性類似,詳情請參閱「使用版本化介面」。

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

現有類型 (parcelableenumunion) 中的新欄位可能不存在,或在 RELEASE_AIDL_USE_UNFROZENfalse 時包含預設值,且服務嘗試傳送的新欄位值會在程序結束時遭到捨棄。

在這個解除凍結的版本中新增的類型,無法透過介面傳送或接收。

RELEASE_AIDL_USE_UNFROZENfalse 時,實作項目永遠不會收到任何用戶端的新方法呼叫。

請注意,新列舉值只能搭配導入該列舉值的版本使用,不能搭配舊版使用。

通常您會使用 foo->getInterfaceVersion() 查看遠端介面使用的版本。不過,由於您是透過旗標式版本控管支援實作兩個不同版本,因此可能需要取得目前介面的版本。如要執行這項操作,請取得目前物件的介面版本,例如 this->getInterfaceVersion()my_ver 的其他方法。詳情請參閱「查詢遠端物件的介面版本」。

新的 VINTF 穩定介面

新增 AIDL 介面套件時,沒有最後凍結的版本,因此 RELEASE_AIDL_USE_UNFROZENfalse 時,沒有可回溯的行為。請勿使用這些介面。如果 RELEASE_AIDL_USE_UNFROZENfalse,服務管理員就不會允許服務註冊介面,用戶也找不到該介面。

您可以根據裝置 makefile 中 RELEASE_AIDL_USE_UNFROZEN 旗標的值,有條件地新增服務:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

如果服務是較大程序的一部分,因此無法有條件地新增至裝置,您可以檢查服務是否已使用 IServiceManager::isDeclared() 宣告。如果已宣告但無法註冊,請中止程序。如果未宣告,預期會無法註冊。

新的 VINTF 穩定擴充介面

新的擴充介面沒有可回溯的舊版,且未向 ServiceManager 註冊或在 VINTF 資訊清單中宣告,因此 IServiceManager::isDeclared() 無法用於判斷何時將擴充介面附加至其他介面。

RELEASE_AIDL_USE_UNFROZEN 變數可用於判斷是否要將新的未凍結擴充功能介面附加至現有介面,避免在已發布的裝置上使用。介面必須凍結,才能在已發布的裝置上使用。

vts_treble_vintf_vendor_testvts_treble_vintf_framework_test VTS 測試會偵測發布的裝置是否使用未凍結的擴充功能介面,並擲回錯誤。

如果擴充功能介面不是新版,且先前已凍結版本,系統會還原為先前凍結的版本,不需要額外步驟。

將 Cuttlefish 做為開發工具

每年 VINTF 凍結後,我們會調整架構相容性矩陣 (FCM) target-level 和 Cuttlefish 的 PRODUCT_SHIPPING_API_LEVEL,以反映搭載明年發布版本的裝置。我們會調整 target-levelPRODUCT_SHIPPING_API_LEVEL,確保有經過測試的啟動裝置符合明年的新發布需求。

RELEASE_AIDL_USE_UNFROZENtrue 時,Cuttlefish 會用於開發日後推出的 Android 版本。這項功能以明年發布的 Android 版本為目標,FCM 層級為 PRODUCT_SHIPPING_API_LEVEL,因此必須符合下個版本的供應商軟體需求 (VSR)。

RELEASE_AIDL_USE_UNFROZENfalse 時,Cuttlefish 會使用先前的 target-levelPRODUCT_SHIPPING_API_LEVEL 來反映發布裝置。在 Android 14 以下版本,這項差異化作業會透過不同的 Git 分支完成,這些分支不會擷取 FCM target-level、出貨 API 級別或任何其他以下一版為目標的程式碼變更。

模組命名規則

在 Android 11 中,系統會針對每個版本和已啟用後端的組合,自動建立虛設常式程式庫模組。如要參照特定存根程式庫模組進行連結,請勿使用 aidl_interface 模組的名稱,而是使用存根程式庫模組的名稱,也就是 ifacename-version-backend,其中

  • ifacenameaidl_interface 模組的名稱
  • version 為以下任一項:
    • Vversion-number,瞭解不再更新的版本
    • Vlatest-frozen-version-number + 1,適用於樹狀結構頂端 (尚未凍結) 版本
  • backend 為以下任一項:
    • java 適用於 Java 後端,
    • cpp C++ 後端,
    • NDK 後端為 ndkndk_platform。前者適用於應用程式,後者則適用於 Android 13 之前的平台使用情況。在 Android 13 以上版本中,只能使用 ndk
    • 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)

相較於 Android 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,是為了避免複製及貼上時發生錯誤。視 javac 設定而定,可能需要 @SuppressWarnings("static") 註解才能停用警告:

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

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

這是因為產生的類別 (IFooIFoo.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 介面。

  1. 找出介面的所有依附元件。針對介面依附的每個套件,判斷套件是否定義於穩定版 AIDL。如未定義,則必須轉換套件。

  2. 將介面中的所有可打包物件轉換為穩定可打包物件 (介面檔案本身可以保持不變)。方法是直接在 AIDL 檔案中表示結構。管理類別必須改寫,才能使用這些新類型。您可以在建立aidl_interface套件 (如下) 前完成這項作業。

  3. 建立 aidl_interface 套件 (如上所述),其中包含模組名稱、依附元件和任何其他所需資訊。如要穩定 (而不只是結構化),也需要版本化。詳情請參閱「介面版本管理」。