穩定的 AIDL

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

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

結構化與穩定版 AIDL

結構化 AIDL 是指純粹在 AIDL 中定義的類型。舉例來說,可分割的宣告 (自訂可分割) 並非結構化 AIDL。Parcelable 及其在 AIDL 中定義的欄位稱為「結構化 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 介面。
  • 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 開始,versions 會在 aidl_api/name 下凍結。如果介面沒有已凍結的版本,則不應指定此版本,且不會進行相容性檢查。這個欄位已由 Android 13 以上版本的 versions_with_info 取代。
  • versions_with_info:元組清單,每個元組都包含凍結版本名稱,以及此版本匯入的其他 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:產生的虛設常式程式庫可用的 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

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

寫入 AIDL 檔案

穩定版 AIDL 中的介面與傳統介面類似,但不允許使用非結構化 parcelable (因為這些不是穩定的!請參閱「結構化與穩定版 AIDL」)。穩定版 AIDL 的主要差異在於如何定義可分割的項目。先前,Parcelable 會預先宣告;在穩定 (因此有結構) AIDL 中,Parcelable 欄位和變數會明確定義。

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

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

booleancharfloatdoublebyteintlongString 支援預設值 (但不強制規定)。在 Android 12 中,系統也支援使用者定義列舉的預設值。如未指定預設值,系統會使用 0 或空值。即使沒有零枚舉器,沒有預設值的枚舉也會初始化為 0。

使用 Stub 程式庫

將虛設常式程式庫新增為模組的依附元件後,就可以將這些程式庫納入檔案。以下是建構系統中的 Stub 程式庫範例 (Android.mk 也可用於舊版模組定義):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference 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: ...,
    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-凍結-api 會根據 Android 版本在 api_diraidl_api/name 下新增 API 定義,並新增 .hash 檔案,兩者皆可代表新的凍結介面版本。foo-凍結-api 也會更新 versions_with_info 屬性,以反映其他版本和 imports 版本。基本上,versions_with_info 中的 imports 是從 imports 欄位複製而來。不過,匯入作業的最新穩定版是在 versions_with_infoimports 中指定,而該版本並未明確指定版本。指定 versions_with_info 屬性後,建構系統會在已凍結版本之間,以及樹狀結構頂端 (ToT) 和最新的已凍結版本之間執行相容性檢查。

此外,您必須管理 ToT 版本的 API 定義。每次更新 API 時,請執行 foo-update-api 來更新包含 ToT 版本 API 定義的 aidl_api/name/current

為維持介面的穩定性,擁有者可以新增下列項目:

  • 介面結尾處的方法 (或明確定義新序列的方法)
  • 元素位於可傳送物件結尾 (需要為每個元素新增預設值)
  • 常數值
  • 在 Android 11 中,枚舉器
  • 在 Android 12 中,聯集結尾的欄位

系統不允許其他動作,也沒有其他人可以修改介面 (否則可能會與擁有者所做的變更衝突)。

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

  • AIDL_FROZEN_REL=true m ... - 建構作業要求所有穩定的 AIDL 介面皆已凍結,且未指定 owner: 欄位。
  • AIDL_FROZEN_OWNERS="aosp test" - 建構作業要求所有穩定版 AIDL 介面在指定為「aosp」或「test」的 owner: 欄位時凍結。

匯入作業的穩定性

針對介面的已凍結版本更新匯入版本,可在 Stable 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 後端)。如果用戶端將聯集集合傳送至舊伺服器的新欄位,或是舊用戶端從新伺服器接收聯集,就會收到這項錯誤。

管理多個版本

Android 中的連結器命名空間只能有 1 個特定 aidl 介面版本,以免產生的 aidl 類型有多個定義。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 表示未凍結版本的程式庫都會像最後凍結的版本一樣運作。您可以將標記覆寫為 true,用於本機開發作業,但必須在發布前將其還原為 false。通常,開發作業會使用旗標設為 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 的值修改資訊清單檔案。

資訊清單和資訊清單片段會宣告服務實作的介面版本。使用最新的未凍結介面版本時,資訊清單必須更新,以反映新版本。當 RELEASE_AIDL_USE_UNFROZEN=false 時,libvintf 會調整資訊清單項目,以反映產生的 AIDL 程式庫變更。版本會從未凍結的版本 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();
  }
}

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

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

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() 宣告。如果已宣告且註冊失敗,請中斷程序。如果未宣告,則註冊作業應會失敗。

將 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 中,每個版本和已啟用的後端組合都會自動建立 Stub 程式庫模組。如要參照特定的輔助程式程式庫模組進行連結,請勿使用 aidl_interface 模組的名稱,而是使用輔助程式程式庫模組的名稱,也就是 ifacename-version-backend,其中

  • ifacenameaidl_interface 模組的名稱
  • version 為以下任一項:
    • Vversion-number (適用於已凍結的版本)
    • Vlatest-frozen-version-number + 1:樹狀結構頂端 (尚未凍結) 的版本
  • backend 為以下任一項:
    • java (Java 後端)、
    • cpp (C++ 後端)
    • ndkndk_platform 用於 NDK 後端。前者適用於應用程式,後者則適用於 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 等) 會在用戶端和伺服器之間共用 (例如,類別可位於啟動 classpath 中)。共用類別時,即使伺服器可能已使用舊版介面建構,也會連結至最新版本的類別。如果這個元介面是在共用類別中實作,就會一律傳回最新版本。不過,透過實作上述方法,介面的版本號碼會嵌入伺服器的程式碼中 (因為 IFoo.VERSIONstatic 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 套件 (如上所述),其中包含模組名稱、依附元件和您需要的任何其他資訊。為了讓資料穩定 (而非只是結構化),還需要為其建立版本。詳情請參閱版本管理介面