介面版本管理

HIDL 要求所有以 HIDL 編寫的介面都需要版本化。HAL 後 介面發布後,將進入凍結狀態,如需進一步變更, 新版介面。雖然指定的介面無法 則可由其他介面擴充

HIDL 程式碼結構

HIDL 程式碼是依使用者定義分類 類型、介面和套件:

  • 使用者定義的類型 (UDT):HIDL 是用來存取一組 可用來組合較複雜類型的原始資料類型 結構、聯集和列舉項目UDT 會傳遞給 介面,而且可在套件層級定義 (所有 或本機介面傳送至介面
  • 介面。是 HIDL 的基本構成要素,也就是介面 包含 UDT 和方法宣告。介面也可以繼承 另一個介面
  • 套件。組織相關 HIDL 介面與資料 以及它們的運作類型套件是以名稱和版本做為識別 包含下列項目:
    • 名為 types.hal 的資料類型定義檔案。
    • 零或多個介面,每個介面分別位於各自的 .hal 檔案中。

資料類型定義檔案 types.hal 僅包含 UDT (全部) 套件層級 UDT 會保存在單一檔案中)。目標中的表示法 套件中的所有介面皆可使用。

版本管理理念

之後的 HIDL 套件 (例如 android.hardware.nfc) 發布的版本 (例如 1.0) 無法變更。該資料來源 無法變更。修改套件中的介面或 對其 UDT 的變更只能在「其他」套件中進行。

在 HIDL 中,版本管理是套用於套件層級,而非介面層級。 且套件中的所有介面和 UDT 都會共用相同的版本。整批交易廣告 版本遵循語意 沒有修補程式等級和 build-metadata 元件的版本管理。在 特定套件的次要版本是指新版本 該套件可回溯相容於舊套件和主要套件 version 通則代表新版本的套件沒有 回溯相容於舊套件

從概念上來說,套件可透過以下其中一種方式與其他套件建立關聯:

  • 完全不會
  • 套件層級回溯相容可擴充。這個 套件的新次要版本更新 (下一個遞增的修訂版本) 會發生; 新套件的名稱和主要版本與舊套件相同,但 小調版本從功能上來說,新套件是舊版 Pod 的超集合 包裝,也就是:
    • 新套件中存在父項套件的頂層介面 儘管介面可能有新方法,但會使用新的介面本機 UDT ( 介面層級擴充功能,以及 types.hal
    • 新介面也可以新增至新套件。
    • 父項套件的所有資料類型都存在於新套件中 可以透過舊套件的 (可能重新實作) 方法處理。
    • 新的資料類型也可以透過更新過的新方法使用 或透過新介面進行預測
  • 介面層級回溯相容的擴充能力。而 也可以將原始套件 只提供其他功能的介面,而非核心功能。 您可以針對這個目標執行下列動作:
    • 新套件中的介面需要回溯至舊資料類型的資料類型 套件。
    • 新套件中的介面可擴充一或多個舊版的介面 套件
  • 擴充原始的回溯相容性。這是 套件的主要版本升級,因此 不過這兩者之間有一些主要差異在該數量上,這個數字可以結合 以及子集的子集 舊版套件介面

建構介面

為了打造結構完善的介面,建議您加入 不屬於原始設計的一部分,因此得修改 HIDL 存取 API相反地,如果預期或預期會在 介面將提供新功能,且在不變更介面的情況下 表示介面未結構化

Treble 支援另外編譯的供應商和系統元件 裝置上的 vendor.imgsystem.img 可 個別編譯而成vendor.img 到 之間的所有互動 system.img 必須明確且明確定義,才能 該機構會繼續努力 多年這包含許多 API 介面 表面是 HIDL 採用的 IPC 機制 在 system.img/vendor.img 個界線。

需求條件

透過 HIDL 傳送的所有資料都必須明確定義。為了確保 即使已編譯完成,用戶端仍可繼續合作 單獨或獨立開發的資料,必須遵守下列規定: 規定:

  • 可直接在 HIDL 中說明 (使用結構體列舉等) 語意名稱和意義
  • 可透過 ISO/IEC 7816 等公開標準提供。
  • 可根據硬體標準或硬體實體配置來描述。
  • 若有需要,可以是不透明資料 (例如公開金鑰、ID 等)。

如果使用不透明資料,則只能由 HIDL 的其中一側讀取該資料 存取 API舉例來說,如果 vendor.img 程式碼提供 system.img 字串訊息或 vec<uint8_t> 資料,system.img 本身無法剖析該資料;它可以 才會傳回 vendor.img 進行解讀時間 將值從 vendor.img 傳遞到 system.img或其他裝置:資料格式及其方式 的解讀方式必須明確描述,且仍屬於 介面

規範

您應該能夠僅使用 .hal 檔案 (也就是說,您不應該查看 Android 原始碼或公開檔案) 標準)。建議您指明必要的行為。這類陳述 「實作可能需 A 或 B」鼓勵採行的 與開發客戶的相對安全

HIDL 程式碼版面配置

HIDL 包含核心和供應商套件。

核心 HIDL 介面皆是由 Google 指定的介面。他們所屬的套件 開頭是 android.hardware.,並以子系統命名 可能會使用巢狀結構命名舉例來說,NFC 套件的名稱為 android.hardware.nfc,而相機套件為 android.hardware.camera。一般來說,核心套件的名稱 android.hardware.[name1].[name2].... 除了名稱之外,HIDL 套件還具有版本。舉例來說 android.hardware.camera 可能為 3.4 版本;這是 重要的是,因為套件版本會影響其在來源樹狀結構中的位置。

所有核心套件都位於以下項目的 hardware/interfaces/ 中: 建構系統包裹 android.hardware.[name1]。[name2]... 版本 $m.$n 低於 hardware/interfaces/name1/name2/.../$m.$n/;包裹 android.hardware.camera3.4 位於目錄中 hardware/interfaces/camera/3.4/. 已存在硬式編碼對應 介於套件前置字串 android.hardware. 和路徑之間 hardware/interfaces/

非核心 (供應商) 套件是由 SoC 供應商或 ODM 產生的套件。 非核心套件的前置字串為 vendor.$(VENDOR).hardware.,其中 $(VENDOR) 是指 SoC 供應商或原始設備製造商 (OEM)/ODM。這會對應至 樹狀結構中的 vendor/$(VENDOR)/interfaces (此對應也能 硬式編碼)。

完整的使用者定義類型名稱

在 HIDL 中,每個 UDT 都有一個完整名稱,由 UDT 名稱、 定義 UDT 的套件名稱以及套件版本。 只有在執行個體已宣告且 未定義類型本身。例如,假設 android.hardware.nfc, 版本 1.0 定義了結構 名為 NfcData。在宣告網站中 (無論 types.hal 或介面宣告中), 單純表示:

struct NfcData {
    vec<uint8_t> data;
};

宣告此類型的例項時 (無論是在資料結構內 作為方法參數),請使用完整的類型名稱:

android.hardware.nfc@1.0::NfcData

一般語法為 PACKAGE@VERSION::UDT,其中:

  • PACKAGE 是 HIDL 套件以點分隔的名稱 (例如:android.hardware.nfc).
  • VERSION 是以點分隔的 main.minor-version 格式 (例如1.0).
  • UDT 是 HIDL UDT 的以點分隔名稱。 由於 HIDL 支援巢狀 UDT 和 HIDL 介面,因此可以包含 UDT ( 巢狀宣告)),則使用半形句號來存取名稱。

舉例來說,如果下列巢狀宣告是由 套件 android.hardware.example 版本的類型檔案 1.0

// types.hal
package android.hardware.example@1.0;
struct Foo {
    struct Bar {
        // …
    };
    Bar cheers;
};

Bar 的完整名稱為 android.hardware.example@1.0::Foo.Bar。如果除了 上述套件,巢狀宣告位於名為 IQuux:

// IQuux.hal
package android.hardware.example@1.0;
interface IQuux {
    struct Foo {
        struct Bar {
            // …
        };
        Bar cheers;
    };
    doSomething(Foo f) generates (Foo.Bar fb);
};

Bar 的完整名稱為 android.hardware.example@1.0::IQuux.Foo.Bar

在這兩種情況下,Bar 只能稱為 BarFoo 宣告範圍內。在套件中, 介面層級,您必須透過 Foo 參照 BarFoo.Bar (如 doSomething 方法的宣告中所示) 。或者,您可以更詳盡地宣告方法,如下所示:

// IQuux.hal
doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);

完整列舉值

如果 UDT 是列舉類型,則列舉類型的每個值都有 完整名稱,開頭為列舉類型的完整名稱, 後面加上冒號,然後是列舉值的名稱。例如: 假設套件 android.hardware.nfc,1.0 版 定義了列舉類型 NfcStatus

enum NfcStatus {
    STATUS_OK,
    STATUS_FAILED
};

參照 STATUS_OK 時,完整名稱為:

android.hardware.nfc@1.0::NfcStatus:STATUS_OK

一般語法為 PACKAGE@VERSION::UDT:VALUE, 其中:

  • PACKAGE@VERSION::UDT是 與列舉類型的完整名稱完全相同。
  • VALUE 是值的名稱。

自動推論規則

您不需要指定完整的 UDT 名稱。UDT 名稱可以 安全地省略下列程式碼:

  • 包裹,例如@1.0::IFoo.Type
  • 套件和版本,例如:IFoo.Type

HIDL 會嘗試使用自動幹擾規則完成名稱 (較低規則) 數字表示優先順序較高)。

規則 1

如未提供套件和版本,系統會嘗試查詢本機名稱。 例子:

interface Nfc {
    typedef string NfcErrorMessage;
    send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m);
};

已在本機查詢NfcErrorMessagetypedef 或顯示在其他元件上方也在當地查詢了NfcData,但內容不變 未在本機定義,使用的是規則 2 和 3。@1.0::NfcStatus 因此不會套用規則 1

規則 2

如果規則 1 失敗,且缺少完整名稱的組成部分 (套件、版本,或套件和版本),系統會透過 最新資訊接著,HIDL 編譯器會檢查 目前的檔案 (及所有匯入內容),找出自動填入的完整完整名稱。 使用上述範例假設 ExtendedNfcData 的宣告 已在同一套件 (android.hardware.nfc) 中製作 版本 (1.0) 取代為 NfcData,如下所示:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

HIDL 編譯器會填寫 ,產生完整的 UDT 名稱 android.hardware.nfc@1.0::NfcData。這個 ID 存在於 目前的套件 (假設已正確匯入),則會用於 宣告內容

只有在下列其中一個項目出現時,系統才會匯入目前套件中的名稱 是:

  • 會以 import 陳述式明確匯入。
  • 這是在目前套件中的 types.hal 定義

如果 NfcData 僅由 版本號碼:

struct ExtendedNfcData {
    // autofill the current package name (android.hardware.nfc)
    @1.0::NfcData base;
    // … additional members
};

規則 3

如果規則 2 無法產生相符項目 (目前資源中並未定義 UDT) HIDL 編譯器),會掃描所有匯入的套件中是否有相符項目。 使用上述範例假設 ExtendedNfcData android.hardware.nfc 套件的 1.1 版本, 1.1 會正確匯入 1.0 (請參閱 套件層級的擴充功能) 和定義 只會指定 UDT 名稱:

struct ExtendedNfcData {
    NfcData base;
    // … additional members
};

編譯器會尋找任何名為 NfcData 的 UDT,並會在 於 1.0 版本為 android.hardware.nfc,導致 完全合格的 UDT,共 android.hardware.nfc@1.0::NfcData。如果顯示 在指定的部分 UDT (HIDL 編譯器) 找到一個以上的相符項目 擲回錯誤。

範例

使用規則 2 時,目前套件中定義的匯入類型會取代 從其他套件匯入的類型:

// hardware/interfaces/foo/1.0/types.hal
package android.hardware.foo@1.0;
struct S {};

// hardware/interfaces/foo/1.0/IFooCallback.hal
package android.hardware.foo@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/types.hal
package android.hardware.bar@1.0;
typedef string S;

// hardware/interfaces/bar/1.0/IFooCallback.hal
package android.hardware.bar@1.0;
interface IFooCallback {};

// hardware/interfaces/bar/1.0/IBar.hal
package android.hardware.bar@1.0;
import android.hardware.foo@1.0;
interface IBar {
    baz1(S s); // android.hardware.bar@1.0::S
    baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback
};
  • S 是內插類型, android.hardware.bar@1.0::S,位置: bar/1.0/types.hal (因為 types.hal 會自動 已匯入)。
  • IFooCallback 是內插類型, android.hardware.bar@1.0::IFooCallback使用規則 2,但規則 2 未匯入 bar/1.0/IFooCallback.hal,因此找不到。 自動 (如 types.hal 所示)。因此,規則 3 會解析為 改為 android.hardware.foo@1.0::IFooCallback,這是已匯入的資料 透過 import android.hardware.foo@1.0;)。

types.hal

每個 HIDL 套件都包含一個 types.hal 檔案,其中包含 UDT 該套件參與的所有介面共用HIDL 類型 一律為公開狀態;無論您是否在 types.hal 或介面宣告中,這些類型為 位於定義範圍外存取。types.hal 並非用來說明套件的公用 API,而是代管 UDT 套件中的所有介面使用基於 HIDL 的本質,所有 UDT 是介面的一部分

types.hal 包含 UDT 和 import 陳述式。 由於 types.hal 提供給 套件 (為隱式匯入),這些 import 陳述式會是 套件層級定義types.hal 中的 UDT 也可以納入 進而匯入 UDT 和介面。

IFoo.hal 為例:

package android.hardware.foo@1.0;
// whole package import
import android.hardware.bar@1.0;
// types only import
import android.hardware.baz@1.0::types;
// partial imports
import android.hardware.qux@1.0::IQux.Quux;
// partial imports
import android.hardware.quuz@1.0::Quuz;

系統會匯入以下項目:

  • android.hidl.base@1.0::IBase (隱含)
  • android.hardware.foo@1.0::types (隱含)
  • android.hardware.bar@1.0」中的所有項目 (包括全部 介面及其 types.hal)
  • 距離android.hardware.baz@1.0::types types.hal (android.hardware.baz@1.0 中的介面不會匯入)
  • IQux.haltypes.halandroid.hardware.qux@1.0
  • 來自android.hardware.quuz@1.0Quuz (假設 Quuz 是在 types.hal 中定義,也就是 已剖析 types.hal 檔案,但 Quuz 以外的類型 不會匯入)。

介面層級版本管理

套件中的每個介面都位於專屬的檔案中。套件 是透過 package 陳述式。在套件宣告之後 (零或多個) 系統可能會列出介面層級的匯入作業 (部分或完整套件)。例如:

package android.hardware.nfc@1.0;

在 HIDL 中,介面可以透過 extends關鍵字。至於要擴充其他介面的介面 必須透過 import 陳述式存取。清單的名稱 擴充的介面 (基礎介面) 會遵循類型名稱的規則 評估。一個介面只能繼承一個介面; HIDL 不支援多重繼承。

以下較新版本的版本管理範例使用下列套件:

// types.hal
package android.hardware.example@1.0
struct Foo {
    struct Bar {
        vec<uint32_t> val;
    };
};

// IQuux.hal
package android.hardware.example@1.0
interface IQuux {
    fromFooToBar(Foo f) generates (Foo.Bar b);
}

更新規則

如要定義套件 package@major.minor,可以採用 A 或整個 B 須為 true:

規則 A 「是起始子版本」:所有先前的子版本, package@major.0package@major.1、...、 無法定義 package@major.(minor-1)
規則 B

符合下列所有條件:

  1. 「先前的子版本有效」:package@major.(minor-1) 就必須定義相同的規則 A,並遵循相同的規則 A (無 package@major.0package@major.(minor-2) ) 或規則 B (如果更新自 @major.(minor-2));



  2. 「沿用至少一個名稱相同的介面」:存在 可擴充至 package@major.minor::IFoo 的介面 package@major.(minor-1)::IFoo (如果先前的套件有介面);



  3. 「未沿用的介面也沒有不同名稱」:不存在 package@major.minor::IBar,可擴充 package@major.(minor-1)::IBaz,其中 IBarIBaz 是兩個不同的名稱。如果系統顯示 相同名稱,package@major.minor::IBar必須延伸 package@major.(minor-k)::IBar,這樣就不會有 IBar k。

根據規則 A 的做法:

  • 套件的開頭可以是任何次要版本號碼 (例如 android.hardware.biometrics.fingerprint開始時間 @2.1)。
  • 未定義要求「android.hardware.foo@1.0」意思是 目錄 hardware/interfaces/foo/1.0 甚至不應存在。

不過,規則 A 不會對套件名稱相同的套件造成影響, 不同的主要版本 (例如 android.hardware.camera.device 同時包含@1.0@3.2 已定義;@3.2 不需要互動 @1.0)。因此,@3.2::IExtFoo 可以延長 @1.0::IFoo

如果套件名稱不同, package@major.minor::IBar 可從具有 不同的名稱 (例如,android.hardware.bar@1.0::IBar 可以 擴充 android.hardware.baz@2.2::IBaz)。如果介面沒有 使用 extend 關鍵字明確宣告超級類型 擴充 android.hidl.base@1.0::IBase (IBase 除外) 其本身)。

請務必同時遵循 B.2 和 B.3。舉例來說 android.hardware.foo@1.1::IFoo 延伸 android.hardware.foo@1.0::IFoo 傳遞規則 B.2 android.hardware.foo@1.1::IExtBar 延長 android.hardware.foo@1.0::IBar,這仍然無效。

升級介面

提升 android.hardware.example@1.0 (如上定義) 至 @1.1

// types.hal
package android.hardware.example@1.1;
import android.hardware.example@1.0;

// IQuux.hal
package android.hardware.example@1.1
interface IQuux extends @1.0::IQuux {
    fromBarToFoo(Foo.Bar b) generates (Foo f);
}

這是套件層級 import (第 1.0 版) types.hal中有 android.hardware.example。但沒有新觀眾 系統會在套件的 1.1 版本中新增 UDT,在 仍需要 1.0 版,因此套件層級匯入作業 位置:types.hal。(如果使用 IQuux.hal 中的介面層級匯入)。

在以下宣告的 extends @1.0::IQuux 中: IQuux,我們指定的 IQuux 版本 繼承 (由於 IQuux 是用於 宣告介面並繼承自某個介面)。如宣告內容所示 也就是沿用網站網站上所有套件和版本屬性的名稱 宣告時,釐清歧義必須位於基礎介面的名稱;我們 可能也使用了完整的 UDT 多餘的功能。

新介面 IQuux 不會重新宣告方法 fromFooToBar() 繼承自 @1.0::IQuux;它只是 列出其新增 fromBarToFoo() 的新方法。在 HIDL 中沿用 方法「無法」在子項介面中再次宣告,因此 IQuux 介面無法宣告 fromFooToBar() 方法。

更新慣例

有時介面名稱必須重新命名擴充介面。建議做法 列舉擴充套件、結構體和聯集的名稱,與擴充項目的名稱相同 除非它們與新名稱之間有極大差異例如:

// in parent hal file
enum Brightness : uint32_t { NONE, WHITE };

// in child hal file extending the existing set with additional similar values
enum Brightness : @1.0::Brightness { AUTOMATIC };

// extending the existing set with values that require a new, more descriptive name:
enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };

如果方法可擁有新的語意名稱 (例如 fooWithLocation),然後設定為偏好選項。否則, 類似其延伸名稱舉例來說 @1.1::IFoo 中的 foo_1_1 可能會取代功能 就會使用 @1.0::IFoo 中的 foo 方法 (如果沒有更好的話) 替代名稱。

套件層級版本管理

HIDL 版本管理是在套件層級進行。套件發布後 是不可變動的 (介面集和 UDT 無法變更)。套件可以 都以多種方式傳達給彼此 是介面層級的繼承和建構 UDT 的組合。

不過,我們嚴格定義了其中一種關係,因此必須強制執行: 套件層級回溯相容繼承。在這種情況下 parent 套件是沿用自 child 套件是擴充父項的套件。套件層級 回溯相容的繼承規則如下:

  1. 父項套件的所有頂層介面,都是由 子套件。
  2. 您也可以增加新介面,以新增介面 (沒有 與其他套件中其他介面的關係)。
  3. 新的資料類型也可以透過更新過的新方法使用 或透過新介面進行預測

如要導入這些規則,可以使用 HIDL 介面層級繼承和 UDT 構成,但需要元級知識才能瞭解這些關係 則會視為回溯相容的套件擴充功能。這項知識是系統推論出來的 如下所示:

如果套件符合這項規定,hidl-gen 會強制執行 回溯相容規則