HIDL要求對用HIDL編寫的每個接口進行版本控制。發布HAL接口後,將凍結該接口,並且必須對該接口的新版本進行任何進一步的更改。雖然給定的發布接口可能無法修改,但可以由另一個接口擴展。
HIDL代碼結構
HIDL代碼按用戶定義的類型,接口和包進行組織:
- 用戶定義類型(UDT) 。 HIDL提供對一組原始數據類型的訪問,這些原始數據類型可通過結構,聯合和枚舉用於組成更複雜的類型。 UDT傳遞給接口方法,並且可以在程序包級別(對於所有接口通用)或在接口本地定義。
- 接口。作為HIDL的基本構建塊,接口由UDT和方法聲明組成。接口也可以從另一個接口繼承。
- 包。組織相關的HIDL接口及其操作的數據類型。軟件包由名稱和版本標識,並包括以下內容:
- 名為
types.hal
數據類型定義文件。 - 零個或多個接口,每個接口都在自己的
.hal
文件中。
- 名為
數據類型定義文件類型types.hal
僅包含UDT(所有程序包級UDT都保存在一個文件中)。目標語言的表示形式可用於程序包中的所有接口。
版本哲學
在針對指定版本(例如1.0
)發布後,HIDL軟件包(例如android.hardware.nfc
)是不可變的;它無法更改。只能在另一個程序包中對程序包中的接口進行修改或對其UDT進行任何更改。
在HIDL中,版本控制適用於程序包級別,而不適用於接口級別,並且程序包中的所有接口和UDT共享同一版本。軟件包版本遵循語義版本控制,而沒有補丁程序級別和構建元數據組件。在給定的程序包中,次要版本變更意味著該程序包的新版本與舊程序包向後兼容,而主版本變更意味著該程序包的新版本與舊程序包不向後兼容。
從概念上講,一個包可以通過以下幾種方式之一與另一個包相關:
- 一點也不。
- 包級向後兼容的可擴展性。對於軟件包的新次要版本uprevs(下一個遞增修訂版),會發生這種情況;新軟件包的名稱和主要版本與舊軟件包相同,但較高的次要版本。從功能上講,新軟件包是舊軟件包的超集,意味著:
- 父程序包的頂級接口位於新程序包中,儘管這些接口可能具有新的方法,新的接口本地UDT(如下所述的接口級擴展)和
types.hal
新UDT。 - 新接口也可以添加到新程序包中。
- 父包的所有數據類型都存在於新包中,並且可以通過舊包中的(可能重新實現的)方法進行處理。
- 也可以添加新的數據類型,以供更新現有接口的新方法或由新接口使用。
- 父程序包的頂級接口位於新程序包中,儘管這些接口可能具有新的方法,新的接口本地UDT(如下所述的接口級擴展)和
- 接口級向後兼容的可擴展性。新軟件包還可以通過包含邏輯上獨立的接口(僅提供其他功能而不是核心功能)來擴展原始軟件包。為此,可能需要執行以下操作:
- 新軟件包中的接口需要求助於舊軟件包的數據類型。
- 新軟件包中的接口可以擴展一個或多個舊軟件包的接口。
- 擴展原始的向後不兼容。這是軟件包的主要版本uprev,兩者之間沒有任何關聯。在某種程度上,它可以用舊版本軟件包的類型和舊軟件包接口子集的繼承的組合來表示。
結構化界面
對於結構良好的接口,添加不屬於原始設計的新型功能應要求對HIDL接口進行修改。相反,如果您可以或希望在不增加界面本身的情況下對引入新功能的界面的兩側進行更改,則該界面不是結構化的。
Treble支持單獨編譯的供應商和系統組件,其中設備上的vendor.img
和system.img
可以分別編譯。 vendor.img
和system.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.
開頭android.hardware.
並由子系統命名,可能具有嵌套的命名級別。例如,NFC包名為android.hardware.nfc
,而camera包名為android.hardware.camera
。通常,核心軟件包的名稱為android.hardware.
[ name1
]。[ name2
]…。 HIDL軟件包除名稱外還具有版本。例如,包android.hardware.camera
版本可能為3.4
;這很重要,因為包的版本會影響其在源樹中的位置。
所有核心軟件包都放置在構建系統中的hardware/interfaces/
下。包android.hardware.
$m.$n
版本中的[ name1
]。[ name2
]…在hardware/interfaces/name1/name2/
/$m.$n/
;包android.hardware.camera
版本3.4
位於目錄hardware/interfaces/camera/3.4/.
包前綴android.hardware.
之間存在硬編碼映射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
是包的點分隔的major.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
只能在Foo
聲明的範圍內稱為Bar
。在包或接口級別,您必須通過Foo
引用Bar
: Foo.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
,並找到其上方的typedef
。 NfcData
也在本地查找,但由於未在本地定義,因此使用規則2和3。 @1.0::NfcStatus
提供了一個版本,因此規則1不適用。
規則二
如果規則1失敗,並且缺少標準名稱的組件(程序包,版本或程序包和版本),則會使用當前程序包中的信息自動填充該組件。然後,HIDL編譯器在當前文件(和所有導入文件)中查找自動填充的完全限定名稱。使用上面的示例,假設ExtendedNfcData
的聲明是在與NfcData
相同版本( 1.0
)的同一包( android.hardware.nfc
)中進行的,如下所示:
struct ExtendedNfcData { NfcData base; // … additional members };
HIDL編譯器會從當前軟件包中填寫軟件包名稱和版本名稱,以生成標準的UDT名稱android.hardware.nfc@1.0::NfcData
。由於該名稱存在於當前包中(假設已正確導入),因此該名稱用於聲明。
僅當滿足以下條件之一時,才導入當前包中的名稱:
- 它是使用
import
語句顯式import
。 - 它在當前包中的
types.hal
中定義
如果NfcData
僅由版本號限定,則遵循相同的過程:
struct ExtendedNfcData { // autofill the current package name (android.hardware.nfc) @1.0::NfcData base; // … additional members };
規則三
如果規則2無法產生匹配項(當前包中未定義UDT),則HIDL編譯器會在所有導入的包中掃描匹配項。使用上面的例子,假設ExtendedNfcData
在版本聲明1.1
包android.hardware.nfc
, 1.1
進口1.0
,因為它應該(見封裝級擴展),並定義只指定UDT名稱:
struct ExtendedNfcData { NfcData base; // … additional members };
編譯器會查找所有名為NfcData
UDT,並在1.0
版的android.hardware.nfc
中找到一個,從而得到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,這些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.0
IQux.hal
和types.hal
- 來自
android.hardware.quuz@1.0
Quuz
(假設Quuz
是在types.hal
定義的,則解析了整個types.hal
文件,但未導入Quuz
以外的其他類型)。
接口級版本控制
程序包中的每個接口都駐留在其自己的文件中。接口所屬的package
使用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); }
Uprev規則
要定義包package package@major.minor
,A或B的全部必須為true:
規則A | “是一個開端次要版本”:以前所有次要版本, package@major.0 , package@major.1 ,..., package@major.(minor-1) 不能確定。 |
---|
規則B | 以下所有條件都是正確的:
|
---|
由於規則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
,這仍然不是有效的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.hal
中android.hardware.example
1.0
版的程序包級import
。儘管在軟件包的1.1
版中未添加新的UDT,但仍需要在1.0
版中引用UDT,因此在types.hal
了軟件包級別的導入。 (使用IQuux.hal
的接口級導入可以達到相同的效果。)
在extends @1.0::IQuux
聲明的IQuux
extends @1.0::IQuux
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構建的組合來表示。
但是,一種類型的關係是嚴格定義的,必須強制執行:包級別的向後兼容繼承。在這種情況下,父包是從其繼承的包,子包是擴展父包的包。程序包級向後兼容的繼承規則如下:
- 父包的所有頂級接口均由子包中的接口繼承。
- 新接口也可以添加到新程序包中(對與其他程序包中其他接口的關係沒有限制)。
- 也可以添加新的數據類型,以供更新現有接口的新方法或由新接口使用。
這些規則可以使用HIDL接口級繼承和UDT組成實現,但是需要元級知識才能知道這些關係構成了向後兼容的程序包擴展。該知識推斷如下:
如果軟件包滿足此要求, hidl-gen
強制執行向後兼容規則。