版本管理

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 共享相同的版本。包版本遵循語義版本控制,沒有補丁級別和構建元資料組件。在給定的包中,次要版本提升意味著該包的新版本與舊包向後相容,而主要版本提升意味著該包的新版本不與舊包向後相容。

從概念上講,一個包可以透過以下幾種方式之一與另一個包相關:

  • 一點也不
  • 包級向後相容的可擴展性。對於軟體包的新次要版本升級(下一個增量修訂版),會發生這種情況;新軟體包與舊軟體包具有相同的名稱和主要版本,但次要版本更高。從功能上來說,新包是舊包的超集,這意味著:
    • 父包的頂級介面出現在新包中,儘管這些接口可能具有新方法、新接口本地 UDT(下面描述的接口級擴展)以及types.hal中的新 UDT。
    • 新的介面也可能被加入到新的套件中。
    • 父包的所有資料類型都存在於新包中,並且可以透過舊包中的(可能重新實現的)方法進行處理。
    • 也可以新增新的資料類型以供升級現有介面的新方法或新介面使用。
  • 介面級向後相容的可擴充性。新套件還可以透過包含邏輯上獨立的介面來擴充原始套件,這些介面僅提供附加功能,而不是核心功能。為此,可能需要採取以下措施:
    • 新包中的介面需要依賴舊包的資料類型。
    • 新包中的介面可以擴充一個或多個舊包的介面。
  • 擴展原來的向後不相容性。這是該軟體包的主要版本升級版,兩者之間不需要有任何關聯。在某種程度上,它可以用舊版本包中的類型組合以及舊包介面子集的繼承來表示。

建置介面

對於結構良好的接口,新增不屬於原始設計一部分的新功能類型應該需要修改 HIDL 介面。相反,如果您可以或期望在介面兩側進行更改以引入新功能而不更改介面本身,則該介面不是結構化的。

Treble支援單獨編譯的供應商和系統元件,其中設備上的vendor.imgsystem.img可以單獨編譯。 vendor.imgsystem.img之間的所有互動都必須明確且徹底地定義,以便它們可以繼續工作多年。這包括許多 API 表面,但主要表面是 HIDL 用於system.img / vendor.img邊界上的進程間通訊的 IPC 機制。

要求

透過 HIDL 傳遞的所有資料都必須明確定義。為了確保實作和客戶端即使在單獨編譯或獨立開發時也能繼續協同工作,資料必須遵守以下要求:

  • 可以直接在 HIDL 中使用語義名稱和含義進行描述(使用結構枚舉等)。
  • 可以透過 ISO/IEC 7816 等公共標準進行描述。
  • 可以透過硬體標準或硬體實體佈局來描述。
  • 如有必要,可以是不透明資料(例如公鑰、id 等)。

如果使用不透明數據,則必須只能由 HIDL 介面的一側讀取。例如,如果vendor.img程式碼向system.img上的元件提供字串訊息或vec<uint8_t>數據,則system.img本身無法解析該數據;它只能傳回vendor.img來解釋。將值從vendor.img傳遞到system.img上的供應商代碼或另一個設備時,必須準確描述資料的格式及其解釋方式,並且仍然是介面的一部分

指南

您應該能夠僅使用 .hal 檔案編寫 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.camera版本3.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是包的以點分隔的主版本號.次版本號格式(例如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只能在Foo聲明的範圍內稱為Bar 。在套件或介面級別,您必須透過Foo : Foo.Bar引用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);
};

在本機上尋找NfcErrorMessage ,找到其上面的typedefNfcData也在本地查找,但由於它未在本地定義,因此使用規則 2 和 3。 @1.0::NfcStatus提供了一個版本,因此規則 1 不適用。

規則2

如果規則 1 失敗且缺少完全限定名稱的元件(套件、版本或套件和版本),則系統會使用目前套件中的資訊自動填入該元件。然後,HIDL 編譯器會在目前檔案(以及所有導入)中尋找自動填入的完全限定名稱。使用上面的範例,假設ExtendedNfcData的宣告是在與NfcData相同的套件 ( android.hardware.nfc ) 的相同版本 ( 1.0 ) 中進行的,如下所示:

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

HIDL 編譯器會填入目前套件中的套件名稱和版本名稱,以產生完全限定的 UDT 名稱android.hardware.nfc@1.0::NfcData 。由於該名稱存在於目前套件中(假設已正確導入),因此將其用於聲明。

只有在滿足下列條件之一時,才會匯入目前套件中的名稱:

  • 它是透過import語句明確導入的。
  • 它在當前包的types.hal中定義

如果NfcData僅由版本號限定,則遵循相同的過程:

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

規則3

如果規則 2 無法產生符合項目(目前套件中未定義 UDT),HI​​DL 編譯器將在所有匯入的套件中掃描符合項目。使用上面的範例,假設ExtendedNfcData在包android.hardware.nfc的版本1.1中聲明, 1.1按其應有的方式導入1.0 (請參閱包級擴展),並且定義僅指定 UDT 名稱:

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

編譯器會尋找任何名為NfcData的 UDT,並在android.hardware.nfc版本1.0中找到一個,從而產生android.hardware.nfc@1.0::NfcData的完全限定 UDT。如果給定的部分限定 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使用規則 2 插值為android.hardware.bar@1.0::IFooCallback ,但無法找到它,因為bar/1.0/IFooCallback.hal不會自動導入(與types.hal一樣)。因此,規則 3 將其解析為android.hardware.foo@1.0::IFooCallback ,它是透過import android.hardware.foo@1.0;導入的。 )。

類型.hal

每個 HIDL 包都包含一個types.hal文件,其中包含在參與該包的所有介面之間共享的 UDT。 HIDL 類型始終是公共的;無論 UDT 是在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中的介面)
  • 來自android.hardware.qux@1.0IQux.haltypes.hal
  • 來自android.hardware.quuz@1.0Quuz (假設Quuz是在types.hal中定義的,則解析整個types.hal文件,但不會導入Quuz以外的類型)。

介面級版本控制

包中的每個介面都駐留在自己的檔案中。介面所屬的套件是使用package語句在介面頂部聲明的。在套件聲明之後,可以列出零個或多個介面級導入(部分或整個包)。例如:

package android.hardware.nfc@1.0;

在 HIDL 中,介面可以使用extends關鍵字從其他介面繼承。對於要擴展另一個接口的接口,它必須能夠透過import語句存取該接口。正在擴充的介面的名稱(基本介面)遵循上面解釋的類型名稱限定規則。一個介面只能繼承自一個介面; HIDL 不支援多重繼承。

下面的 uprev 版本控制範例使用以下套件:

// 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( package@major.0package@major.(minor-2)均未定義)或規則 B (如果它是@major.(minor-2)的升級版本);



  2. 「繼承至少一個同名介面」:存在一個介面package@major.minor::IFoo擴展了package@major.(minor-1)::IFoo (如果前一個套件有一個介面);



  3. 「沒有不同名稱的繼承介面」:不能存在擴展package@major.(minor-1)::IBazpackage@major.minor::IBar ,其中IBarIBaz是兩個不同的名稱。如果存在同名的接口, package@major.minor::IBar必須擴展package@major.(minor-k)::IBar ,這樣不存在具有較小 k 的 IBar。

因為規則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::IBaseIBase本身除外)。

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 ,這仍然不是有效的 uprev。

升級介面

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);
}

這是types.halandroid.hardware.example 1.0版本的包級import 。雖然包的1.1版中沒有添加新的 UDT,但仍需要對1.0版中的 UDT 的引用,因此需要在types.hal中進行包級導入。 (透過IQuux.hal中的介面級導入可以實現相同的效果。)

IQuux宣告中的extends @1.0::IQuux中,我們指定了要繼承的IQuux版本(需要消除歧義,因為IQuux用於宣告介面並從介面繼承)。由於宣告只是繼承宣告處的所有套件和版本屬性的名稱,因此必須在基底介面的名稱中消除歧義;我們也可以使用完全限定的 UDT,但那是多餘的。

新介面IQuux不會重新宣告它繼承自@1.0::IQuux方法fromFooToBar() ;它只是列出了它添加的新方法fromBarToFoo() 。在 HIDL 中,繼承的方法可能無法在子介面中再次聲明,因此IQuux介面無法明確宣告fromFooToBar()方法。

Uprev 約定

有時介面名稱必須重新命名擴充介面。我們建議枚舉擴展、結構和聯合與其擴展的名稱具有相同的名稱,除非它們足夠不同以保證使用新名稱。例子:

// 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 的組合來表達。

然而,一種類型的關係是嚴格定義的並且必須強制執行:包級向後相容繼承。在這種情況下,包是繼承自的包,子包是擴充父包的包。包級向後相容的繼承規則如下:

  1. 父包的所有頂層介面都由子包中的介面繼承。
  2. 新的介面也可以加入到新的套件中(與其他套件中的其他介面的關係沒有限制)。
  3. 也可以新增新的資料類型以供升級現有介面的新方法或新介面使用。

這些規則可以使用 HIDL 介面級繼承和 UDT 組合來實現,但需要元級知識來了解這些關係構成向後相容的包擴展。該知識推論如下:

如果軟體包符合此要求, hidl-gen將強制執行向後相容性規則。