本文列出的最佳做法可做為指南,協助您有效開發 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,引數則為 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] Provide explicit defaults for fields.
非結構化可攜式物件
1. 使用時機
Java 中的非結構化可序列化物件可透過 @JavaOnlyStableParcelable 取得,NDK 後端則可透過 @NdkOnlyStableParcelable 取得。通常這些是無法建構的舊有可封送物件。
常數和列舉
1. 位元欄位應使用常數欄位
位元欄位應使用常數欄位 (例如介面中的 const int FOO = 3;)。
2. 列舉應為封閉集合。
列舉應為封閉集合。注意:只有介面擁有者可以新增列舉元素。如果供應商或原始設備製造商 (OEM) 需要擴充這些欄位,則必須使用替代機制。盡可能優先上傳供應商功能。不過,在某些情況下,系統可能會允許自訂供應商值通過 (但供應商應設有版本控制機制,或許是 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
}
FileDescriptor
[-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;