AIDL 樣式指南

本文概述的最佳做法是協助您有效開發 AIDL 介面,同時留意介面的靈活性,特別是在 AIDL 用於定義 API 或與 API 介面互動時。

如果應用程式需要在背景程序中互相介面,或需要與系統介面互動,可以使用 AIDL 定義 API。如要進一步瞭解如何使用 AIDL 在應用程式中開發程式設計介面,請參閱「Android 介面定義語言 (AIDL)」。如需 AIDL 實務範例,請參閱 HAL 適用的 AIDLStable AIDL

版本管理

每個 AIDL API 回溯相容的快照都會對應一個版本,如要拍攝快照,請執行 m <module-name>-freeze-api。每當 API 的用戶端或伺服器發布時 (例如在 Mainline Train 中),您必須拍攝快照並建立新版本。至於系統對供應商的 API,這應同時包含每年平台的修訂版本。

如要進一步瞭解允許的變更類型,請參閱版本管理介面

API 設計指南

一般

1. 記錄所有內容

  • 記錄每種方法的語意、引數、內建例外狀況、服務特定例外狀況和傳回值。
  • 記錄每個介面的語意。
  • 記錄列舉和常數的語意含義。
  • 記錄實作者可能不清楚的部分。
  • 請視情況舉例說明。

2. 套管

針對類型使用大寫駝峰式大小寫,並針對方法、欄位和引數使用較低的駝峰式大小寫。例如,MyParcelable 代表 parcel 類型,而 anArgument 代表引數。如果是縮寫字詞,請考慮使用單字縮寫 (NFC -> Nfc)。

[-Wconst-name] 列舉值和常數應為 ENUM_VALUECONSTANT_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 方法都含有隱含狀態碼。請參閱 ServiceSpecificExceptionEX_SERVICE_SPECIFIC。按照慣例,這些值在 AIDL 介面中定義為常數。詳情請參閱 AIDL 後端的錯誤處理一節

5. 視為輸出參數的陣列視為有害

[-Wout-array] 具有陣列輸出參數 (例如 void foo(out String[] ret)) 的方法通常不佳,因為用戶端必須在 Java 中宣告及分配輸出陣列大小,因此伺服器無法選擇陣列輸出內容的大小。陣列在 Java 中的運作方式 (無法重新分配),因此會發生這類不理想的行為。而是優先使用 String[] foo() 等 API。

6. 避免使用中的參數

[-Winout-parameter] 這可能會混淆用戶端,因為即使是 in 參數看起來像 out 參數。

7. 避免產生和 inout @nullable 非陣列參數

[-Wout-nullable] 由於 Java 後端不會在其他後端處理 @nullable 註解,因此 out/inout @nullable T 可能會導致後端的行為不一致。舉例來說,非 Java 後端可以將 @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,其在 NDK 後端中則可透過 @NdkOnlyStableParcelable 使用。通常這些是無法結構化的舊和現有的 parcelable。

常數和列舉

1. Bitfields 應使用常數欄位

Bitfields 應使用常數欄位 (例如介面中的 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. 避免多餘的前置字串和後置字串

[-Wmultiple-name] 避免常數和列舉工具中的多餘或重複的前置字串和後置字串。

不建議的填寫方式:使用多餘的前置字串

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

建議:直接為列舉命名

enum MyStatus {
    GOOD,
    BAD
}

檔案描述元

[-Wfile-描述元] 強烈建議不要使用 FileDescriptor 做為引數或 AIDL 介面方法的傳回值。特別是在 Java 中實作 AIDL 時,除非妥善處理,否則可能會導致檔案描述元外洩。基本上,如果您接受 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;