AIDL 風格指南

這裡概述的最佳實踐可以作為有效開發 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_VALUECONSTANT_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 方法都有隱式狀態回傳代碼。請參閱ServiceSpecificExceptionEX_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 用 Ja​​va 實作時,如果不小心處理,可能會導致檔案描述符洩漏。基本上,如果您接受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;