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/name
。versions
如果介面沒有凍結版本,則不應指定此項目,也不會進行相容性檢查。這個欄位已由 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 起,cpp
和java
後端的預設值為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_header
和cpp_header
。backend.java.sdk_version
:選用標記,用於指定建構 Java 存根程式庫時所用的 SDK 版本。預設值為"system_current"
。如果backend.java.platform_apis
為true
,則不應設定此屬性。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;
}
boolean
、char
、float
、double
、byte
、int
、long
和 String
支援預設值 (但非必要)。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_dir
或 aidl_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_defaults
、java_defaults
和 rust_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_UNFROZEN
。true
表示在執行階段使用介面的未凍結版本,false
則表示未凍結版本的程式庫行為都與最後凍結的版本相同。您可以覆寫標記,以便進行本機開發,但發布前必須還原為 false
。true
通常開發作業會使用已將旗標設為 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_UNFROZEN
為 false
時使用凍結的版本 4。
資訊清單
Android 15 導入了 libvintf
的變更,可根據 RELEASE_AIDL_USE_UNFROZEN
的值,在建構期間修改資訊清單檔案。
資訊清單和資訊清單片段會宣告服務實作的介面版本。使用最新未凍結的介面版本時,必須更新資訊清單,反映這個新版本。當 libvintf
調整資訊清單項目以反映產生的 AIDL 程式庫變更時,RELEASE_AIDL_USE_UNFROZEN=false
版本會從未凍結版本 N
修改為最後凍結版本 N - 1
。因此,使用者不必為每項服務管理多個資訊清單或資訊清單片段。
HAL 用戶端變更
HAL 用戶端程式碼必須與每個先前支援的凍結版本回溯相容。當 RELEASE_AIDL_USE_UNFROZEN
為 false
時,服務一律會與最後凍結的版本或更早的版本相同 (例如,呼叫新的未凍結方法會傳回 UNKNOWN_TRANSACTION
,或新的 parcelable
欄位會採用預設值)。Android 架構用戶端必須與其他舊版向後相容,但這是供應商用戶端和合作夥伴擁有介面的用戶端的新詳細資料。
HAL 實作異動
使用旗標開發 HAL 時,與 HAL 實作項目必須向後相容於最後凍結版本,才能在 RELEASE_AIDL_USE_UNFROZEN
為 false
時運作,這是與旗標式開發最大的差異。在實作項目和裝置程式碼中考慮回溯相容性,是新的練習。請參閱「使用已加入版本的介面」。
一般而言,用戶端和伺服器,以及架構程式碼和供應商程式碼的向後相容性考量因素相同,但您現在有效實作兩個使用相同原始碼 (目前未凍結的版本) 的版本,因此需要注意細微差異。
示例:介面有三個凍結版本。介面會更新為新方法。用戶端和服務都會更新,改用新的第 4 版程式庫。由於 V4 程式庫是以未凍結的介面版本為基礎,因此在 RELEASE_AIDL_USE_UNFROZEN
為 false
時,其行為與最後凍結的版本 (版本 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();
}
}
現有類型 (parcelable
、enum
、union
) 中的新欄位可能不存在,或在 RELEASE_AIDL_USE_UNFROZEN
為 false
時包含預設值,且服務嘗試傳送的新欄位值會在程序結束時遭到捨棄。
在這個解除凍結的版本中新增的類型,無法透過介面傳送或接收。
當 RELEASE_AIDL_USE_UNFROZEN
為 false
時,實作項目永遠不會收到任何用戶端的新方法呼叫。
請注意,新列舉值只能搭配導入該列舉值的版本使用,不能搭配舊版使用。
通常您會使用 foo->getInterfaceVersion()
查看遠端介面使用的版本。不過,由於您是透過旗標式版本控管支援實作兩個不同版本,因此可能需要取得目前介面的版本。如要執行這項操作,請取得目前物件的介面版本,例如 this->getInterfaceVersion()
或 my_ver
的其他方法。詳情請參閱「查詢遠端物件的介面版本」。
新的 VINTF 穩定介面
新增 AIDL 介面套件時,沒有最後凍結的版本,因此 RELEASE_AIDL_USE_UNFROZEN
為 false
時,沒有可回溯的行為。請勿使用這些介面。如果 RELEASE_AIDL_USE_UNFROZEN
為
false
,服務管理員就不會允許服務註冊介面,用戶也找不到該介面。
您可以根據裝置 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_test
和 vts_treble_vintf_framework_test
VTS 測試會偵測發布的裝置是否使用未凍結的擴充功能介面,並擲回錯誤。
如果擴充功能介面不是新版,且先前已凍結版本,系統會還原為先前凍結的版本,不需要額外步驟。
將 Cuttlefish 做為開發工具
每年 VINTF 凍結後,我們會調整架構相容性矩陣 (FCM) target-level
和 Cuttlefish 的 PRODUCT_SHIPPING_API_LEVEL
,以反映搭載明年發布版本的裝置。我們會調整 target-level
和 PRODUCT_SHIPPING_API_LEVEL
,確保有經過測試的啟動裝置符合明年的新發布需求。
當 RELEASE_AIDL_USE_UNFROZEN
為 true
時,Cuttlefish 會用於開發日後推出的 Android 版本。這項功能以明年發布的 Android 版本為目標,FCM 層級為 PRODUCT_SHIPPING_API_LEVEL
,因此必須符合下個版本的供應商軟體需求 (VSR)。
當 RELEASE_AIDL_USE_UNFROZEN
為 false
時,Cuttlefish 會使用先前的 target-level
和 PRODUCT_SHIPPING_API_LEVEL
來反映發布裝置。在 Android 14 以下版本,這項差異化作業會透過不同的 Git 分支完成,這些分支不會擷取 FCM target-level
、出貨 API 級別或任何其他以下一版為目標的程式碼變更。
模組命名規則
在 Android 11 中,系統會針對每個版本和已啟用後端的組合,自動建立虛設常式程式庫模組。如要參照特定存根程式庫模組進行連結,請勿使用 aidl_interface
模組的名稱,而是使用存根程式庫模組的名稱,也就是 ifacename-version-backend,其中
ifacename
:aidl_interface
模組的名稱version
為以下任一項:Vversion-number
,瞭解不再更新的版本Vlatest-frozen-version-number + 1
,適用於樹狀結構頂端 (尚未凍結) 版本
backend
為以下任一項:java
適用於 Java 後端,cpp
C++ 後端,- NDK 後端為
ndk
或ndk_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; }
}
這是因為產生的類別 (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。如未定義,則必須轉換套件。
將介面中的所有可打包物件轉換為穩定可打包物件 (介面檔案本身可以保持不變)。方法是直接在 AIDL 檔案中表示結構。管理類別必須改寫,才能使用這些新類型。您可以在建立
aidl_interface
套件 (如下) 前完成這項作業。建立
aidl_interface
套件 (如上所述),其中包含模組名稱、依附元件和任何其他所需資訊。如要穩定 (而不只是結構化),也需要版本化。詳情請參閱「介面版本管理」。