本文概述的最佳做法可做為開發 AIDL 介面的指南,並注意介面的彈性,特別是在使用 AIDL 定義 API 或與 API 途徑互動時。
如果應用程式需要在背景程序中互相介面,或需要與系統介面互動,可以使用 AIDL 定義 API。如要進一步瞭解如何使用 AIDL 在應用程式中開發程式介面,請參閱「Android 介面定義語言 (AIDL)」。如需 AIDL 實際應用範例,請參閱「HAL 專用 AIDL」和「穩定 AIDL」。
版本管理
AIDL API 的每個向後相容快照都會對應至版本。如要拍攝快照,請執行 m <module-name>-freeze-api
。每當 API 的用戶端或伺服器發布 (例如在 Mainline 訓練資料中),您就需要擷取快照並建立新版本。對於系統至供應商 API,這項作業應在每年的平台修訂版中進行。
如要進一步瞭解允許的變更類型,請參閱「版本化介面」。
API 設計指南
一般
1. 記錄所有內容
- 為每個方法記錄其語義、引數、內建例外狀況的使用方式、服務專屬例外狀況和回傳值。
- 為每個介面記錄其語意。
- 記錄列舉和常數的語意意義。
- 記錄實作人員可能不清楚的內容。
- 請視情況舉例說明。
2. 套管
請使用大寫駝峰式命名法為類型命名,使用小寫駝峰式命名法為方法、欄位和引數命名。例如,MyParcelable
代表 parcel 類型,而 anArgument
代表引數。對於縮寫詞,請將縮寫詞視為單字 (NFC
-> Nfc
)。
[-Wconst-name] 列舉值和常數應為 ENUM_VALUE
和 CONSTANT_NAME
介面
1. 命名
[-Winterface-name] 介面名稱應以 I
開頭,例如 IFoo
。
2. 避免使用 ID 型「物件」建立大型介面
如果有許多與特定 API 相關的呼叫,請改用子介面。這項功能有以下優點:
- 讓用戶端或伺服器程式碼更容易理解
- 簡化物件的生命週期
- 利用繫結器無法追溯的優勢。
不建議:單一大型介面,其中包含 ID 型物件
interface IManager {
int getFooId();
void beginFoo(int id); // clients in other processes can guess an ID
void opFoo(int id);
void recycleFoo(int id); // ownership not handled by type
}
建議做法:個別介面
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. 請勿混用單向和雙向方法
[-Wmixed-oneway] 請勿將單向方法與非單向方法混用,因為這會讓用戶端和伺服器難以瞭解執行緒模型。具體來說,讀取特定介面的用戶端程式碼時,您需要查詢每個方法是否會阻斷。
4. 避免傳回狀態碼
方法應避免使用狀態碼做為回傳值,因為所有 AIDL 方法都有隱含的狀態回傳碼。請參閱 ServiceSpecificException
或 EX_SERVICE_SPECIFIC
。依照慣例,這些值會在 AIDL 介面中定義為常數。詳情請參閱 AIDL 後端的錯誤處理一節。
5. 視為輸出參數的陣列視為有害
[-Wout-array] 具有陣列輸出參數 (例如 void foo(out String[] ret)
) 的方法通常不佳,因為用戶端必須在 Java 中宣告及分配輸出陣列大小,因此伺服器無法選擇陣列輸出內容的大小。這種不理想的行為是因為陣列在 Java 中的運作方式 (無法重新分配)。而是優先使用 String[] foo()
等 API。
6. 避免使用 inout 參數
[-Winout-parameter] 這可能會讓用戶端感到困惑,因為即使 in
參數看起來像 out
參數。
7. 避免使用 out 和 inout @nullable 非陣列參數
[-Wout-nullable] 由於 Java 後端不會處理 @nullable
註解,而其他後端會處理,因此 out/inout @nullable T
可能會導致後端之間的行為不一致。舉例來說,非 Java 後端可以將 out @nullable
參數設為空值 (在 C++ 中將其設為 std::nullopt
),但 Java 用戶端無法將其讀取為空值。
結構化 parcelable
1. 使用時機
在需要傳送多種資料類型時,請使用結構化 parcelable。
或者,如果您只有單一資料類型,但預期日後需要擴充該類型,例如,請勿使用 String username
。使用可擴充的 parcelable,例如以下內容:
parcelable User {
String username;
}
這樣日後就能擴充,如下所示:
parcelable User {
String username;
int id;
}
2. 明確提供預設值
[-Wexplicit-default, -Wenum-explicit-default] 為欄位提供明確的預設值。
非結構化 parcelable
1. 使用時機
在 Java 中,@JavaOnlyStableParcelable
可用於非結構化 parcelable,而 @NdkOnlyStableParcelable
可用於 NDK 後端。這些通常是無法結構化的舊版和現有 parcelable。
常數和列舉
1. 位元欄位應使用常數欄位
位元欄位應使用常數欄位 (例如介面中的 const int FOO = 3;
)。
2. 列舉應為封閉式集合。
列舉應為封閉式集合。注意:只有介面擁有者才能新增列舉元素。如果供應商或原始設備製造商需要擴充這些欄位,就必須使用其他機制。請盡可能使用上游供應商功能。不過,在某些情況下,系統可能會允許自訂供應商值 (不過,供應商應設有機制來為此版本,或許是 AIDL 本身,這些機制不應彼此衝突,且這些值不應公開給第三方應用程式)。
3. 避免使用「NUM_ELEMENTS」等值
由於列舉有版本,因此應避免使用表示值數量的值。在 C++ 中,可以使用 enum_range<>
解決這個問題。如果是 Rust,請使用 enum_values()
。在 Java 中,目前還沒有解決方案。
不建議使用:使用編號值
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. 避免使用多餘的前置字串和後置字串
[-Wredundant-name] 避免在常數和枚舉器中使用多餘或重複的前置字串和後置字串。
不建議的填寫方式:使用多餘的前置字串
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
建議做法:直接命名列舉
enum MyStatus {
GOOD,
BAD
}
檔案描述元
[-Wfile-descriptor] 強烈建議您不要將 FileDescriptor
用於 AIDL 介面方法的引數或傳回值。特別是當 AIDL 以 Java 實作時,除非謹慎處理,否則可能會導致檔案描述元外洩。基本上,如果您接受 FileDescriptor
,就必須在不再使用時手動關閉。
針對原生後端,您可以放心,因為 FileDescriptor
對應至可自動關閉的 unique_fd
。不過,無論您使用的後端語言為何,請務必「完全不使用 FileDescriptor
」,因為這樣會限制日後您自由變更後端語言。
請改用可自動關閉的 ParcelFileDescriptor
。
變數單位
請務必在名稱中加入變數單位,以便清楚定義並瞭解單位,無須參照說明文件
範例
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
時間戳記必須標示參照
時間戳記 (其實是所有單位) 都必須清楚標示單位和參考點。
範例
/**
* Time since device boot in milliseconds
*/
long timestampMs;
/**
* UTC time received from the NTP server in units of milliseconds
* since January 1, 1970
*/
long utcTimeMs;