Android HIDL理解(基于Android O)
1 概述
HIDL是Hardware Interface Definition Language的简称。在Android Project Treble被提出,在android O中被全面的推送。
2 HIDL
2.1 hidl设计目的
设计 HIDL 这个机制的目的,主要目的是把框架(framework)与 HAL 进行隔离,使得框架部分可以直接被覆盖、更新,而不需要重新对 HAL 进行编译。HAL 的部分将会放在设备的 /vendor 分区中,并且是由设备供应商(vendors)或 SOC 制造商来构建。这使得框架部分可以通过 OTA 方式更新,同时不需要重新编译 HAL。
HIDL 实际上是用于进行进程间通信(Inter-process Communication,IPC)的。进程间的通信可以称为 Binder 化(Binderized)。对于必须连接到进程的库,也可以使用 passthough 模式(但在Java中不支持)。
HIDL 接口具有client端和server端的实现:
client端是指通过HIDL调用方法的一方
server端是指实现HIDL的接口,接受client的调用并返回数据的一方
在从 libhardware HAL 转换为 HIDL HAL 的过程后,HAL 的实现成为server端,而调用 HAL 的进程则成为client。默认实现可提供Passthrough和binderized式 HAL:
binderized mode:
以HIDL表示的 HAL。这些 HAL 取代了早期 Android 版本中使用的传统 HAL 和旧版 HAL。在Binder HAL 中,Android framework和 HAL 之间通过 Binder 进程间通信 (IPC) 调用进行通信。所有在推出时即搭载了 Android 8.0 或后续版本的设备都必须只支持binder HAL。
Passthrough Mode:
以 HIDL 封装的传统 HAL 或旧版 HAL。这些 HAL 封装了现有的 HAL,可在binder 和 Same-Process(Passthrough)模式下使用。升级到 Android 8.0 的设备可以使用Passthrough HAL。
演化过程,如下所示:
binder化后的通讯过程:
2.2 hidl语法
HIDL 语言与 C 语言是相似的(但没有使用 C 的预处理器),以下没有描述的标点符号(除了明显使用 | 与 = 的)是语法的一部分:
符号 |
说明 |
|
文档的注释。 |
/* */ |
多行注释 |
// |
单行注释 |
[empty] |
表示为空 |
? |
可选项 |
… |
该序列包含 0 个或者多个如前述使用分隔符隔开的项 |
, |
分割序列元素 |
; |
标记每个元素的结束位置 |
constexpr |
C 风格的常量表达式 |
import_name |
一个包或者接口的名称,其标准在 HIDL Versioning 中有详细阐述 |
示例:
下面列出的是 HIDL 相关的术语:
术语 |
翻译 |
说明 |
binderized |
Binder 化 |
标明 HIDL 被用于进程间的远程方法调用,它是通过一个类似 Binder 的机制来实现的。 |
callback, asynchronous |
异步回调 |
由 HAL 用户所提供的接口,通过 HIDL 的方法传递给 HAL,并由 HAL 来调用,以随时返回所需数据 |
callback, synchronous |
同步回调 |
从服务端的 HIDL 方法实现向客户端返回数据,未使用的方法返回 void 或一个原始类型值。 |
client |
客户端 |
调用一个特定接口的方法的进程。一个 HAL 或者 Framework 进程可以是一个接口的客户端,也可以是另一个接口的服务端。 |
extends |
扩展 |
表示了一个将方法与(或)类型添加到另一个接口的接口。一个接口只能扩展另外的一个接口。它可以用于对于同一个包名的从版本升级,或者也可用来在一个旧的包的基础之上建立一个新的包(比如供应商的扩展)。 |
generates |
生成 |
表示一个返回值给客户端的接口方法。为了返回一个非原始类型值,或者更多的值给客户端,则需要生成一个同步回调函数。 |
interface |
接口 |
这是方法与类型的集合,在 C++ 与 Java 中转换为类。在接口中的所有方法都被调用在同一方向:客户端进程调用由服务端进程实现的方法。 |
oneway |
单向 |
这个概念应用到 HIDL 方法时,表示其无返回值,且不会阻塞。 |
package |
包 |
共享同一版本的接口与数据类型的集合。 |
passthrough |
透明传输 |
服务端是一个共享库时的 HIDL 模式,通过 dlopen 被客户端打开。在这个 passthrough 模式下,客户端与服务端是相同的进程,但是是不同的代码库。仅仅用于将旧的代码库引用到 HIDL 模型中。 |
transport |
传送带 |
HIDL 在客户端与服务端之间移动数据的基本结构。 |
server |
服务端 |
实现了接口方法的进程。另请参阅 passthrough 的解释。 |
version |
版本 |
包的版本。包含两个整数,表示主版本与从版本。从版本的升级可以增加(但不会改变原来的)类型或者方法。 |
2.3 Packages
HIDL是围绕接口构建的,接口是面向对象语言中用于定义行为的抽象类型。 每个接口都是包的一部分。
package的名称可以包含子包比如package.subpackage。公共HIDL包的根目录在hardware/interfaces 或 vendor/vendorname。包名称在根目录下形成一个或多个子目录。
定义包的所有文件都在同一目录中。 例如,可以在hardware / interfaces / example / extension / light / 2.0下找到包[email protected]。
下表列出了包前缀和位置:
包目录包含扩展名为.hal的文件。 每个文件都必须包含一个package声明,用于命名该文件所属的包和版本。 文件types.hal(如果存在)不定义接口,而是定义包中每个接口可访问的数据类型。
3 示例
首先进入android更目录,创建示例目录:
在1.0目录中添加.hal文件,内容如下:
很简单就是获取vampire的爱好,返回一个string字符串内容就是我们需要的,接口定义完毕。
下面调用update-makefiles.sh脚本自动生成Android.bp
执行结束后会自动增加Android.bp文件:
Android.mk用于生成java,暂时不管,先看一下Android.bp的内容:
可以看到通过IVampir.hal后面会生成多个文件:
VampireAll.cpp/IVampire.h/IHwVampire.h/BnHwVampire.h/BpHwVampire.h/BsVampire.h
这些文件的作用是什么呢,android的官方文档给出了解释:
文件 |
说明 |
IFoo.h |
描述 C++ 类中的纯 IFoo 接口;它包含 IFoo.hal 文件中的 IFoo 接口中所定义的方法和类型,必要时会转换为 C++ 类型。不包含与用于实现此接口的 RPC 机制(例如 HwBinder)相关的详细信息。类的命名空间包含软件包名称和版本号,例如 ::android::hardware::samples::IFoo::V1_0。客户端和服务器都包含此标头:客户端用它来调用方法,服务器用它来实现这些方法。 |
IHwFoo.h |
包含用于对接口中使用的数据类型进行序列化的函数的声明。开发者不得直接包含其标头 |
BpFoo.h |
从 IFoo 继承的类,可描述接口的 HwBinder 代理(客户端)实现。开发者不得直接引用此类。 |
BnFoo.h |
保存对 IFoo 实现的引用的类,可描述接口的 HwBinder 存根(服务器端)实现。开发者不得直接引用此类。 |
fooAll.cpp |
包含 HwBinder 代理和 HwBinder 存根的实现的类。当客户端调用接口方法时,代理会自动从客户端封送参数,并将事务发送到绑定内核驱动程序,该内核驱动程序会将事务传送到另一端的存根 |
可以看出这些文件包含了binder话后对于binder通讯的一些封装,作为client和service都需要引用它,client是为了调用而service则是为了实现功能。
再看一下这个文件最终的归宿:
可以看到最终所有文件都会被打包到[email protected]这个动态库中,而我们client的调用与service的实现都会使用这个动态库,到这hidl描述的interface就实现了,我们只需要mmm hardware/interfaces/vampire/就可以生成需要的interface 库文件。
既然hidl接口共享库完成了,下面我们来实现以下这个接口,也就是service的实现,首先利用android提供的工具生成IVampire接口实现代码:
执行结束后会新增如下代码:
然后我们进入Vampire.cpp去实习以下接口函数:
在需要实现的接口里面会提醒TODO implement,我们建立一个字符串便调用cb,注意我们打开了HIDL_FETCH表示是passthrough模式。
然后我们再看一下Android.bp文件:
可以看到Vampire.cpp会生成[email protected]库文件,它是service的实现共享库,下面我们就需要创建一个service进程用于client的访问。
创建service.cpp:
主函数会获取IVampire的实现代码,创建service并注册到系统中。我们需要将service生成一个可执行文件,为了演示方便我们将其加到default的Android.bp中:
为了实现自动启动还需要添加[email protected]文件:
到这service端就准备OK了,下面就是client的调用了,创建client.cpp文件:
你可能注意到我们在.hal定义的返回值被替换为一个回调函数了,我们实现以下,通过getService获取服务,然后调用hobby来获取,我们将client也编译为可执行文件:
下面只要轻松的mm或者mmm以下就可以获取需要的各种库文件与可执行文件了。
看一下当前目录下的文件:
将client和service放在一起只是为了实现方便对比,实际过程中可以放置在不同的地方,
发现没有IVampire.h等文件啊,不要急执行mm或者mmm后会在out/soong/.intermediates/hardware/interfaces/vampire/1.0中生成这些中间文件。
生成的库文件与可执行文件如下:
各个文件的相互关系和调用流程如下:
4 测试验证
测试第一步将生成的文件push到手机的对应位置中,为了方便我们手动启动service,不使用rc文件:
然后执行client:
logcat一下:
说是下manifest.xml中未添加vampire,这个文件带代码的device/qcom/project中,编译后8.0在vendor目录下,9.0在vendor/etc/vintf中,好的添加权限:
添加后push到手机,在执行:
成功返回。
5总结
简单点理解hidl的目的是为了用户方便快速的开发hal的库文件或者说service,而不需要客户去关心如何实现binder,通过hidl-gen工具利用.hal文件将用户定义的接口需要binderized 的部分自动封装生成代码,客户只需要知道如何实现接口即service的实现,如何调用接口即client的使用即可。
参考:
https://www.jianshu.com/p/ca6823b897b5
https://blog.csdn.net/gh201030460222/article/details/80551897
https://source.android.google.cn/devices/architecture/hidl
https://blog.csdn.net/junwua/article/details/80594202