一、前言
正如 Android HIDL 概述 一文中简单的对 HIDL 的演进和新架构下 Framework 与 Hal 层之间的通信做了介绍。但是笔者的目的是想完整的实现从上层 APP 到 hal 之间通信过程,由此可以更加深刻的理解这种机制。
二、Binderized Mode (绑定式)简介
从上文介绍,我们知道 绑定模式 是 google 为了向前兼容而定义的一种类型,且 Android 8.0 及后续版本的设备都必须只支持这种模式。这种模式下 Framework 与 Hal 分别位于不同的进程中,其实从具体实现来讲这种模式也更应该被称为 Binder 化的直通式。本文将通过这种方式实现一个 HIDL 服务。
三、环境/工具准备
- Ubuntu 20.04 TLS
- Android 源码:Android 9.0,编译烧录详见 Android源码编译烧录
- hidl-gen 工具:Android 系统自带,需要配置一下环境变量
四、HIDL 实现
本文目的是实现一个具有 加减乘除 运算的 HIDL 服务,命名为 银河一号(GalaxyOne)。HIDL用起来非常简单,在系统源码中的 hardware/interfaces 目录下有很多的 HIDL,我们仿照其他 HIDL 来创建自己的目录:hardware/interfaces/galaxy_one/1.0
4.1 创建 IGalaxyOne.hal 文件
hardware/interfaces/galaxy_one/1.0/IGalaxyOne.hal
这里定义了四种基本的运算:加、减、乘、除,这是上层调用 HAL 的入口,内容如下:
package android.hardware.galaxy_one@1.0;
interface IGalaxyOne{
//加法
add(uint32_t a,uint32_t b) generates (uint32_t result);
//减法
sub(uint32_t a,uint32_t b) generates (uint32_t result);
//乘法
mul(uint32_t a,uint32_t b) generates (uint32_t result);
//除法
div(uint32_t a,uint32_t b) generates (uint32_t result);
};
4.2 hidl-gen 生成 HIDL 框架
在使用 hidl-gen 之前需要先做两件事:
1、hidl-gen 由 Android 提供,使用之前需要先配置一下系统路径,如我这里所做的:
# vim ~/.bashrc
export PATH=/home/zsk/AOSP/out/soong/host/linux-x86/bin:$PATH
2、Ubuntu 新的终端窗口必须先设定一些 Android 环境变量:
source build/envsetup.sh
lunch aosp_sailfish-userdebug // lunch mode 根据需求修改
make hidl-gen
配置完成之后在 源码根目录 下执行如下命令:
PACKAGE=android.hardware.galaxy_one@1.0
LOC=hardware/interfaces/galaxy_one/1.0/default/
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
命令执行成功之后会发现在 hardware/interfaces/galaxy_one/1.0 目录下多了一个 default 目录,进入之后发现有如下文件:
之后执行 update-makefiles.sh 脚本来为 HIDL 生成对应的 Android.bp 文件,此脚本位于 hardware/interfaces 目录下,同样可在源码根目录下执行:
./hardware/interfaces/update-makefiles.sh
接下来我们需要添加两个空文件:
touch hardware/interfaces/galaxy_one/1.0/default/android.hardware.galaxy_one@1.0-service.rc
touch hardware/interfaces/galaxy_one/1.0/default/service.cpp
完成之后,整个工程结构如下所示:
4.3 调用流程
上述过程已经将 HIDL 服务所需要的全本文件配置完成,虽然其中很多文件是空的,或者没有具体实现,我们现在先放在一边,先来对整体的调用流程及各个文件的功效略作说明。
- Application:指上层应用
- JNI:指 framework 层,getService 获取 hal 层 service
- android.hardware.galaxy_one@1.0.so:由 IGalaxyOne.hal 生成的接口库,由 hardware/interfaces/galaxy_one/1.0/Android.bp 通过 IGalaxyOne.hal 生成,这样只要这个接口库不变,那么 framework 的更新和 hal 层就隔绝开了
- android.hardware.galaxy_one@1.0-service.rc:设备开机时通过 rc 文件启动此服务
- galaxy_hal_service:service的名,可通过国 start galaxy_hal_service 启动服务
- android.hardware.galaxy_one@1.0-impl.so:实现库,上层应用的最终调用
关于 Application、JNI 这两层内容会在稍后用两个篇幅去分析,此处暂不理会。现在我们就着这个调用过程将需要的内容补充完成。
4.3.1 接口库生成
android.hardware.galaxy_one@1.0.so,由 hardware/interfaces/galaxy_one/1.0/Android.bp 通过 IGalaxyOne.hal 生成,Android.bp 文件是在上面一些列命令执行之后生成,而接口库是当我们最终执行编译模块时生成,可以说这个过程不需要我们手动参与,Android.bp 内容如下:
// This file is autogenerated by hidl-gen -Landroidbp.
hidl_interface {
name: "android.hardware.galaxy_one@1.0", //此处设置接口库的名字
root: "android.hardware",
vndk: {
enabled: true,
},
srcs: [
"IGalaxyOne.hal",
],
interfaces: [
"android.hidl.base@1.0",
],
gen_java: true,
}
4.3.2 实现库生成
android.hardware.galaxy_one@1.0-impl.so,由 hardware/interfaces/galaxy_one/1.0/default/Android.bp 通过 GalaxyOne.cpp 生成,注意这个 Android.bp 文件是位于 default 目录下,同样的在最后模块编译时生成,原始内容如下:
cc_library_shared {
name: "android.hardware.galaxy_one@1.0-impl",
relative_install_path: "hw",
proprietary: true,
srcs: [
"GalaxyOne.cpp",
],
shared_libs: [ //这里可以添加我们需要的库
"liblog",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
"android.hardware.galaxy_one@1.0",
],
}
4.3.3 GalaxyOne.cpp 实现
在 4.3.2 中,实现库是由 GalaxyOne.cpp 编译而成,现在我们来将此文件补充完成:
GalaxyOne.h:
Binder化直通式,同样需要将 HIDL_FETCH_XXX 打开,至于原因我们在后面会提及
#ifndef ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H
#define ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H
#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <log/log.h>
namespace android {
namespace hardware {
namespace galaxy_one {
namespace V1_0 {
namespace implementation {
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;
struct GalaxyOne : public IGalaxyOne {
// Methods from ::android::hardware::galaxy_one::V1_0::IGalaxyOne follow.
Return<uint32_t> add(uint32_t a, uint32_t b) override;
Return<uint32_t> sub(uint32_t a, uint32_t b) override;
Return<uint32_t> mul(uint32_t a, uint32_t b) override;
Return<uint32_t> div(uint32_t a, uint32_t b) override;
// Methods from ::android::hidl::base::V1_0::IBase follow.
};
// FIXME: most likely delete, this is only for passthrough implementations
extern "C" IGalaxyOne* HIDL_FETCH_IGalaxyOne(const char* name);
} // namespace implementation
} // namespace V1_0
} // namespace galaxy_one
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_GALAXY_ONE_V1_0_GALAXYONE_H
GalaxyOne.cpp:
#include "GalaxyOne.h"
namespace android {
namespace hardware {
namespace galaxy_one {
namespace V1_0 {
namespace implementation {
// Methods from ::android::hardware::galaxy_one::V1_0::IGalaxyOne follow.
Return<uint32_t> GalaxyOne::add(uint32_t a, uint32_t b) {
uint32_t result = a + b;
ALOGE("GalaxyOne::add a = %d,b = %d,result = %d",a,b,result);
return result;
}
Return<uint32_t> GalaxyOne::sub(uint32_t a, uint32_t b) {
uint32_t result = a - b;
ALOGE("GalaxyOne::sub a = %d,b = %d,result = %d",a,b,result);
return result;
}
Return<uint32_t> GalaxyOne::mul(uint32_t a, uint32_t b) {
uint32_t result = a * b;
ALOGE("GalaxyOne::mul a = %d,b = %d,result = %d",a,b,result);
return result;
}
Return<uint32_t> GalaxyOne::div(uint32_t a, uint32_t b) {
uint32_t result = a / b;
ALOGE("GalaxyOne::div a = %d,b = %d,result = %d",a,b,result);
return result;
}
// Methods from ::android::hidl::base::V1_0::IBase follow.
IGalaxyOne* HIDL_FETCH_IGalaxyOne(const char* /* name */) {
ALOG("galaxy_one service init success....");
return new GalaxyOne();
}
} // namespace implementation
} // namespace V1_0
} // namespace galaxy_one
} // namespace hardware
} // namespace android
4.3.3 模块编译
现在除了需要的 rc 文件没有补充、galaxy-hal-service 服务没有生成外其余均已配置好了,现在进行编译生成对应的库。进入根目录下执行如下命令:(注意是在刚刚执行过的 source build/envsetup.sh 和 lunch 的窗口下编译,若是新窗口则需要重新执行这两条命令)
mmm hardware/interfaces/galaxy_one/1.0
此时应该可以在 out/tartget/product/XXX/vendor/lib64/hw 和 out/tartget/product/XXX/system/lib64/hw 目录下找到 android.hardware.galaxy_one@1.0.so 和 android.hardware.galaxy_one@1.0-impl.so 两个动态库
4.3.4 serice 生成
上面过程将需要的动态库生成完毕,接下来我们需要生成对应的 service 可执行文件,这个过程一共分为三步:
1、在 /default 下的 Android.bp 文件中添加如下内容
cc_binary {
name: "android.hardware.galaxy_one@1.0-service",
defaults: ["hidl_defaults"],
relative_install_path: "hw",
vendor: true,
srcs: [
"service.cpp"
],
init_rc: ["android.hardware.galaxy_one@1.0-service.rc"],
shared_libs: [
"liblog",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
"android.hardware.galaxy_one@1.0",
],
}
2、补充 service.cpp 内容
内容很简单,defaultPassthroughServiceImplementation 帮我们自动注册服务:
#define LOG_TAG "android.hardware.galaxy_one@1.0-service"
#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/LegacySupport.h>
#include "GalaxyOne.h"
// Generated HIDL files
using android::hardware::galaxy_one::V1_0::IGalaxyOne;
using android::hardware::galaxy_one::V1_0::implementation::GalaxyOne;
using android::hardware::defaultPassthroughServiceImplementation;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
int main() {
return defaultPassthroughServiceImplementation<IGalaxyOne>();
}
3、补充 rc 文件
注意这里的 galaxy-hal-service 相当于这个服务的别名,系统就是根据这个文件在启动的同时也将这个 service 启动,因此在下面我们手动启动测试时没有什么作用,不过这里先补充完整。
service galaxy-hal-service /vendor/bin/hw/android.hardware.galaxy_one@1.0-service
class hal
user system
group system
同样执行 mmm hardware/interfaces/galaxy_one/1.0 命令,完成之后就会得到如下二进制可执行文件:
out/target/product/sailfish/vendor/bin/hw/android.hardware.galaxy_one@1.0-service
4.4 client 端
经过一系列过程之后,我们得到了三个产物
1、android.hardware.galaxy_one@1.0.so
2、android.hardware.galaxy_one@1.0-impl.so
3、android.hardware.galaxy_one@1.0-service
现在需要模拟一个客户端来测试调用,因此在 default 目录下新建 test 目录,并新建 client.cpp、Android.bp 文件,具体结构如下:
client.cpp 内容如下:
#include <android/hardware/galaxy_one/1.0/IGalaxyOne.h>
#include <hidl/Status.h>
#include <log/log.h>
using android::sp;
using android::hardware::galaxy_one::V1_0::IGalaxyOne;
using android::hardware::Return;
int main(){
android::sp<IGalaxyOne> service = IGalaxyOne::getService();
if (service == nullptr) {
ALOGD("faile to get galaxy_one service......");
return -1;
}
ALOGE("success to get galaxy_one service.....");
uint32_t addResult = service->add(3,4);
ALOGE("galaxy_one service add: result = %d",(int)addResult);
uint32_t subResult = service->sub(8,3);
ALOGE("galaxy_one service sub: result = %d",(int)subResult);
uint32_t mulResult = service->mul(3,8);
ALOGE("galaxy_one service mul: result = %d",(int)mulResult);
uint32_t divResult = service->div(8,2);
ALOGE("galaxy_one service div: result = %d",(int)divResult);
return 0;
}
Android.bp 内容如下:
cc_binary {
name: "galaxy_test", //表示生成的 client 名称
srcs: [
"client.cpp"
],
shared_libs: [
"liblog",
"android.hardware.galaxy_one@1.0",
"libhidlbase",
"libhidltransport",
"libhwbinder",
"libutils",
],
}
mmm hardware/interfaces/galaxy_one/1.0 命令编译之后,可以在 out/target/product/XXX/system/bin 目录下找到 galaxy_test 。
五、验证服务
5.1 push 设备
现在我们一共得到 4 个产物,使用 adb 命令将其 push 到手机对应目录下:
1、android.hardware.galaxy_one@1.0-impl.so ===> /vendor/lib64/hw
2、android.hardware.galaxy_one@1.0.so ===> vendor/lib64
3、android.hardware.galaxy_one@1.0-service ===> /vendor/bin/hw
4、galaxy_test ===> /system/bin
5.2 修改设备 manifest.xml
HIDL 想要被 framework 获取使用还需要在 manifest.xml 中注册,此在手机 vendor/etc/vintf 下,我们将这个文件 pull 出来添加如下代码之后再 push 原来位置:
<hal format="hidl">
<name>android.hardware.galaxy_one</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IGalaxyOne</name>
<instance>default</instance>
</interface>
<fqname>@1.0::IGalaxyOne/default</fqname>
</hal>
5.3 运行 service
这里我们手动启动,用于测试
./vendor/bin/hw/android.hardware.galaxy_one@1.0-service
5.4 运行 client
./system/bin/galaxy_test
运行成功之后可查看日志,有如下内容则表示服务建立成功: