1.概述
在Android 8.0开始,Android引入了Treble的机制,为了方便Android系统的快速移植、升级,提升系统稳定性,Android引入了HAL Binder的机制,把framework和HAL进行隔离,减少了framework和HAL的耦合性,使得framework部分可以直接被覆盖、更新,而不需要重新对HAL进行编译。
在HAL Binder中,采用的接口定义语言为HIDL。
下图展示了Android 有Treble和没有Treble的升级方式:
2.HIDL说明
HIDL 全称为HAL interface definition language(发音为“hide-l”),是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。
同AIDL类似,我们只需要为hal定义相关接口,然后通过hidl-gen工具即可自动编译生成对应的 C++或者java源代码,定义hal接口的文档命名为xxx.hal,为了编译这些.hal代码,需要编写相应的Android.bp或者Android.mk。
Android.bp文档用于编译C++
Android.mk文档用于编译Java
从Android10.0 开始,hidl编译去除了Android.mk,统一使用Android.bp 来生成C++和JAVA的中间代码。
2.1 绑定式(Binderized)、直通式(Passthrough)
在Android8.0之前,HAL的调用方式为Legacy HAL,HAL 都是编译成 so,然后动态链接到各个 frameworks service 中去。
到Android8.0 时,Android为了给厂商一些修改的时间,同时保持Android的向前兼容性,Android另外设计了一套直通式(Passthrough)的HAL,保留动态库的HAL调用方式,binderservice 链接动态库HAL的实现。
到Android8.0之后,Android规定,厂商新增的HAL都需要切换到绑定式HAL。
HAL的发展历程:
Legacy Hal:Android 8.0 之前版本的 HAL 都是编译成 so,然后动态链接到各个 frameworks service 中去。
Passthrough Hal:该模式是为了兼容旧版的 HAL,旧版 HAL 实现仍以动态库的方式提供,只是 binder service 链接了动态库 HAL 实现,即 binder service 通过 hw_get_module 链接了旧版的 hal 实现,而用户端通过与 binder service IPC 通信,间接实现了与旧版 HAL 的交互。
Binderized HAL:HAL 与 用户调用在不同的进程中,HAL 被写成 binder service,而用户接口如 frameworks 作为 binder client 通过 IPC 机制实现跨进程接口调用。
直通式(Passthrough Hal):
这个是由hidl 封装的hal,具体是在hidl封装处dlopen传统的hal(目前8.0上Camera是这种形式)大概形如如下面这个。下面右侧三个子部分都是在同一个进程中。封装层dlopen传统的hal,这样就不用更改太多代码。
绑定式(Binderized HAL):
这个完全由hidl语言描述,由hidl-gen工具自动生成的hal。此模式下framwework和hal通过进程间binder通信。(camera相关的是/dev/hwbinder节点)。
Android 8.0 或后续版本的设备都必须只支持绑定式 HAL。
2.1 HIDL 规范
2.1.1 软件包
软件包名称可以具有子级,如 package.subpackage。已发布的 HIDL 软件包的根目录为 hardware/interfaces 或 vendor/vendorName(例如,对于 Pixel 设备,根目录为 vendor/google)。软件包名称在根目录下形成一个或多个子目录;定义同一个软件包的所有文件都位于同一目录下。例如,您可以在
hardware/interfaces/example/extension/light/2.0 下找到
package android.hardware.example.extension.light@2.0。
软件包名称必须采用完全限定名称(FQN)格式(称为:PACKAGE-NAME)。格式如下:
PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION
下表列出了软件包前缀和位置:
软件包前缀
位置
接口类型
android.hardware.*
hardware/interfaces/*
HAL
android.frameworks.*
frameworks/hardware/interfaces/*
frameworks/ 相关
android.system.*
system/hardware/interfaces/*
system/ 相关
android.hidl.*
system/libhidl/transport/*
核心
2.1.2 目录结构
HIDL接口定义目录如下:
Root-dir
├─Module
│ └─Version
│ ├─Android.bp
│ ├─IInterface_1.hal
│ ├─IInterface_2.hal
│ ├─…
│ ├─IInterface_n.hal
│ └─types.hal (可选)
└
Root-dir:
hardware/interfaces 核心HIDL软件包
vendor/VENDOR/interfaces 供应商软件包
Module:
描述系统的小写字词。如果多个字词,需使用嵌套式SubModule
Version:
版本号,格式:major.minor
IInterface_x:
接口名称
例如:
vendor/ingres/interfaces //Root-dir
├─demo //Module
│ └─1.0 //Version
│ └─ITestHal.hal //IInterface_x, hal接口
│ └─types.hal
├─update-all.sh //执行hal-gen的shell脚本
├─Android.bp //hidl编译文件
2.1.3 接口定义
除了 types.hal 之外,其他每个 .hal 文件均定义一个接口。接口通常定义如下:
interface IBar extends IFoo { // IFoo is another interface
// embedded types
struct MyStruct {/*...*/};
// interface methods
create(int32_t id) generates (MyStruct s);
close();
};
不含显式 extends 声明的接口会从 android.hidl.base@1.0::IBase(类似于 Java 中的 java.lang.Object)隐式扩展。隐式导入的 IBase 接口声明了多种不应也不能在用户定义的接口中重新声明或以其他方式使用的预留方法。这些方法包括:
ping
interfaceChain
interfaceDescriptor
notifySyspropsChanged
linkToDeath
unlinkToDeath
setHALInstrumentation
getDebugInfo
debug
getHashChain
2.1.4 导入
import 语句是用于访问其他软件包中的软件包接口和类型的 HIDL 机制。
格式如下:
完整软件包导入
import PACKAGE-NAME
部分导入
import PACKAGE-NAME::UDT;(或者,如果导入的类型是在同一个软件包中,则为 import UDT;)。
仅类型导入
import PACKAGE-NAME::types;
如果当前软件包types.hal存在,则自动导入。
在软件包声明之后(在导入之前),添加一个空行。每个导入都应占用一行,且不能缩进。按以下顺序对导入进行分组:
android.harder 软件包(使用完全限定名称)
vendor.VENDOR软件包(使用完全限定名称)
每个供应商应为一组
按字母顺序对供应商进行排序
源自同一个软件包的其他接口导入(使用简单名称)
在组与组之间添加一行空行。每个组内,按照字母顺序对导入进行排序。
2.2 HIDL语法
2.2.1 语法
根据设计,HIDL 语言与 C 语言类似(但前者不使用 C 预处理器)。下面未描述的所有标点符号(用途明显的 = 和 | 除外)都是语法的一部分。
/** */ 表示文档注释。此样式只能应用于类型、方法、字段和枚举值声明。
/* */ 表示多行注释。
// 表示注释一直持续到行尾。除 // 之外,换行符与任何其他空格一样。
在以下示例语法中,从 // 到行尾的文本不是语法的一部分,而是对语法的注释。
[empty] 表示该字词可能为空。
? 跟在文本或字词后,表示它是可选的。
... 表示包含零个或多个项、使用指定的分隔符号分隔的序列。HIDL 中不含可变参数。
逗号用于分隔序列元素。
分号用于终止各个元素,包括最后的元素。
大写字母是非终止符。
italics 是一个令牌系列,如 integer 或 identifier(标准 C 解析规则)。
constexpr 是 C 样式的常量表达式(例如 1 + 1 和 1L << 3)。
import_name 是软件包或接口名称,按 HIDL 版本编号中所述的方式加以限定。
小写 words 是文本令牌。
2.2.2 软件包声明
软件包声明应位于文档顶部,在许可通知之后,应占一整行,并且不应缩进。声明软件包时需采用以下格式(有关名称格式,请参阅软件包名称):
package PACKAGE-NAME;
例如:
package android.hardware.fingerprint@2.1;
package vendor.ingres.demo@1.0;
2.2.3 函数声明
函数名称、参数、generates 和返回值应在同一行中(如果放得下)。例如:
interface IDemo {
/** ... */
getHelloString(string name) generates (string result);
};
如果一行中放不下,则尝试按相同的缩进量放置参数和返回值,并突出 generate,以便读取器快速看到参数和返回值。
例如:
interface IDemo {
anEvenLongerMethodThatCannotFitInOneLine(int32_t theFirstLongParameter,
int32_t anotherVeryLongParameter)
generates (int32_t theFirstReturnValue,
int32_t anotherReturnValue);
}
2.2.4 结构体声明
对于结构体声明,遵循如下规则:
如果与其他软件包共用结构体声明,则将声明放在 types.hal 中,而不是嵌入到接口内。
在结构体类型名称后和左大括号前加空格。
对齐字段名称(可选)。例如:
struct MyStruct {
vec<uint8_t> data;
int32_t someInt;
}
2.2.5 矢量声明
对于矢量声明,遵循如下规则:
vec 和左尖括号。
左尖括号和元素类型(例外情况:元素类型也是 vec)。
元素类型和右尖括号(例外情况:元素类型也是 vec)。
例如:
// Good
vec<int32_t> array;
// Good
vec<vec<int32_t>> array;
// Good
vec< vec<int32_t> > array;
// Bad
vec < int32_t > array;
// Bad
vec < vec < int32_t > > array;
2.3 接口HASH
接口HASH是一种旨在防止意外更改接口并确保接口更改经过全面审查的机制。这种机制是必需的,因为 HIDL 接口带有版本编号,也就是说,接口一经发布便不得再更改,但不会影响应用二进制接口 (ABI) 的情况(例如更正备注)除外。
2.3.1 布局
每个软件包根目录(即映射到 hardware/interfaces 的 android.hardware 或映射到 vendor/ingres/demo/interfaces 的 vendor.demo)都必须包含一个列出所有已发布 HIDL 接口文件的 current.txt 文件。我们可以在编译时,通过shell脚本调用hidl-gen来生成。
命令如下:
hidl-gen -L hash -r android.hardware:hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.demo@1.0 >> ./current.txt
3. hidl-gen的使用
hidl-gen 编译器会将 .hal 文件编译成一组 .h 和 .cpp 文件,这些自动生成的文件用于编译客户端/服务端实现链接到的共享库,同时也可以生成Android.bp编译文件。
3.1 生成的文件
HIDL 软件包中自动生成的文件会关联到与该软件包同名的单个共享库(例如 android.hardware.samples@1.0)。该共享库还会导出单个头文件 IFoo.h,它可以包含在客户端和服务器中。在 Binder 化模式下,使用 hidl-gen 编译器以 IFoo.hal 接口文件作为输入会自动生成以下文件:
3.2 编译工具
hidl-gen 源码路径:system/tool/hidl
版本中默认没有hidl-gen,我们需要先编译生成hidl-gen这个工具:
make -j18 hidl-gen
或
./build.sh xxx -m hidl-gen
编译之后会在out 下生成
3.3 使用方法
方法:
hidl-gen -o output-path -L language (-r interface-root) fqname
例子:
1.生成Android.bp
hidl-gen -L androidbp -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.demo@1.0
2.生成demo源码
hidl-gen -o hardware/interfaces/demo/1.0/default/ -Lc++-impl -r android.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.demo@1.0
3.生成current.txt文件
hidl-gen -L hash -r android.hardware:hardware/interfaces -r android.hardware:hardware/interfaces -r android.hidl:system/libhidl/transport android.hardware.demo@1.0 >> ./current.txt
参数说明:
-L:语言类型,包括c++, c++-headers, c++-sources, export-header, c++-impl, java, java-constants, vts, makefile, androidbp, androidbp-impl, hash等。hidl-gen可根据传入的语言类型产生不同的文件。
fqname:完全限定名称的输入文件。比如本例中android.hardware.demo@1.0,要求在源码目录下必须有hardware/interfaces/ gunder /1.0/目录。对于单个文件来说,格式如下:package@version::fileName,比如android.hardware.demo@1.0::types.Feature。对于目录来说。格式如下package@version,比如android.hardware.demo@1.0。
-r:格式package:path,可选,对fqname对应的文件来说,用来指定包名和文件所在的目录到Android系统源码根目录的路径。如果没有制定,前缀默认是:android.hardware,目录是Android源码的根目录。
-o:存放hidl-gen产生的中间文件的路径。
可以使用hidl-gen 查看帮助,如下图所示: