這裡概述的最佳實踐可以作為有效開發 AIDL 介面的指南,並注意介面的靈活性,特別是當 AIDL 用於定義 API 或與 API 表面互動時。
當應用程式需要在背景進程中相互互動或需要與系統互動時,AIDL 可用於定義 API。有關使用 AIDL 在應用程式中開發程式介面的更多信息,請參閱Android 介面定義語言 (AIDL) 。有關實踐中的 AIDL 範例,請參閱HAL 的 AIDL和穩定的 AIDL 。
版本控制
AIDL API 的每個向後相容快照都對應一個版本。要拍攝快照,請執行m <module-name>-freeze-api
。每當API的用戶端或伺服器發佈時(例如在主線列車中),您需要拍攝快照並製作新版本。對於系統到供應商的 API,這應該在每年的平台修訂中發生。
有關允許的變更類型的更多詳細資訊和信息,請參閱版本控制介面。
API設計指南
一般的
1.記錄一切
- 記錄每個方法的語義、參數、內建異常的使用、特定於服務的異常和傳回值。
- 記錄每個介面的語意。
- 記錄枚舉和常量的語意。
- 記錄實施者可能不清楚的任何內容。
- 提供相關範例。
2. 外殼
對類型使用大駝峰式大小寫,對方法、欄位和參數使用小駝峰式大小寫。例如, MyParcelable
表示可包裹類型, anArgument
表示參數。對於首字母縮略詞,請將首字母縮寫視為單字( NFC
-> Nfc
)。
[-Wconst-name] 枚舉值和常數應為ENUM_VALUE
和CONSTANT_NAME
介面
1. 命名
[-Winterface-name] 介面名稱應以I
like 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
參數設為 null(在 C++ 中,將其設為std::nullopt
),但 Java 用戶端無法將其讀取為 null。
結構化可分包
1. 何時使用
當您有多種資料類型要傳送時,請使用結構化 Parcelable。
或者,當您目前只有單一資料類型但您預計將來需要擴展它時。例如,不要使用String username
。使用可擴充的 Parcelable,如下所示:
parcelable User {
String username;
}
這樣,將來您就可以擴展它,如下所示:
parcelable User {
String username;
int id;
}
2. 明確提供預設值
[-Wexplicit-default, -Wenum-explicit-default] 為欄位提供明確預設值。
非結構化可包裹
1. 何時使用
目前,非結構化 Parcelable 在 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
}
檔案描述符
[-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;