Android(安卓)HIDL学习(2) --- HelloWorld
ndroid HIDL学习[2] --- HelloWorld
- 二、HelloWorld
- 2.1 准备工作
- 2.2 hidl_daemon
- 2.2.1 HIDL 接口文件定义
- 2.2.2 生成 HAL 相关文件
- 2.2.3 实现HAL实现端的共享库
- 2.2.4 代码调用流程
- 2.2.5 启动binder server端进程
- 2.2.6 HIDL Client测试代码
二、HelloWorld
2.1 准备工作
- Android BSP编译环境
- Android设备的BSP代码
- Android设备,用来跑测试代码
2.2 hidl_daemon
我们看看AOSP有哪些HAL:
- Camera
- Audio
- Sensor
- 等等
这些都是Android 设备上的硬件,因为Google 理论上只关心 Android 的框架层 和 上层软件,
但是 上层软件 依赖于底层的硬件实现,
但每家手机厂商或者CPU 厂商底层的硬件实现都是不一样的,
所以这个 HAL 层基本都是手机厂商或者CPU 厂商来实现的。
Google只是作为一个框架的指导,和Framework层API的接口定义,这些接口的实现都得由HAL去完成。
那么我们的 HIDL_Demon 就肩负了这个重任,控制底层硬件,
底层硬件都是由 Linux Kernel 驱动控制的,提供文件读写就可以简单控制驱动。
2.2.1 HIDL 接口文件定义
进入代码,我们假设 hidl_daemon作为标准 AOSP 的 HAL, 我们就把代码揉进标准 HAL 层去,
进入代码目录,创建 HIDL 目录:
mkdir -p hardware/interfaces/hidl_daemon/1.0/default
接着创建接口描述文件 Ihidl_daemon.hal 放在刚才的目录:
package android.hardware.hidl_daemon@1.0;interface IHidl_daemon { helloWorld(string name) generates (string result);};
没错,这是一个Google定义的语言格式,C++和Java的结合体。
这里我们定义了一个INaruto接口文件,简单的添加了一个helloWorld接口,传入是一个string,返回一个string,后面我们会来实现这个接口。
2.2.2 生成 HAL 相关文件
Google帮我们提供了一些工具来生成HAL层相关的代码框架和代码实例,
这样子我们只需要关心实现部分,而不需要写一堆无用代码,浪费时间在搞Makefile和一些低级错误上。
使用hidl-gen工具
PACKAGE=android.hardware.hidl_daemon@1.0LOC=hardware/interfaces/hidl_daemon/1.0/default/make hidl-gen -j64hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGEhidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
然后使用脚本来更新Makefile,自动生成Android,mk, Android.bp
./hardware/interfaces/update-makefiles.sh
现在,我们来添加两个空文件:
touch hardware/interfaces/hidl_daemon/1.0/default/android.hardware.hidl_daemon@1.0-service.rctouch hardware/interfaces/hidl_daemon/1.0/default/service.cpp
- 现在我们的代码目录: hardware/interface/hidl_daemon:
├── 1.0│ ├── Android.bp│ ├── Android.mk│ ├── default│ │ ├── Android.bp│ │ ├── android.hardware.hidl_daemon@1.0-service.rc│ │ ├── Hidl_daemon.cpp│ │ ├── Hidl_daemon.h│ │ └── service.cpp│ └── IHidl_daemon.hal└── Android.bp
是不是so easy,我们写代码就写了一个 hidl_daemon.hal,其余代码都是自动生成的,
特别是 hidl_daemon.cpp 和 hidl_daemon.h 这两个文件是实现接口的关键文件。
2.2.3 实现HAL实现端的共享库
此时打开 Hidl_daemon.cpp 和 Hidl_daemon.h 这两个代码文件,我们要开始正式写代码了。
打开 Hidl_daemon.h
struct Hidl_daemon: public IHidl_daemon { // Methods from INaruto follow. Return<void> helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) override; // Methods from ::android::hidl::base::V1_0::IBase follow.};// FIXME: most likely delete, this is only for passthrough implementations// extern "C" IHidl_daemon* HIDL_FETCH_INaruto(const char* name);
我们知道,HIDL 的实现有两种方式,一种是 Binderized 模式,另一种是 Passthrough 模式,
我们看到上面有两行注释掉的代码,看来这个代码是关键,来选择实现方式是 Binderized 还是 Passthrough。
我们这时使用 Passthrough 模式来演式,其实大家后面尝试这两种方式后会发现,其实这两种本质是一样的。
目前大部分厂商使用的都是 Passthrough 来延续以前的很多代码,
但是慢慢的都会被改掉的,所以目前我们打开这个 注释。
@ Hidl_daemon.h# ifndef ANDROID_HARDWARE_NARUTO_V1_0_HIDL_DAENOM_H# define ANDROID_HARDWARE_NARUTO_V1_0_HIDL_DAEMON_H# include # include # include namespace android {namespace hardware {namespace hidl_daemon{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 Hidl_daemon: public IHidl_daemon { // Methods from INaruto follow. Return<void> helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) override; // Methods from ::android::hidl::base::V1_0::IBase follow.};// FIXME: most likely delete, this is only for passthrough implementationsextern "C" INaruto* HIDL_FETCH_INaruto(const char* name);} // namespace implementation} // namespace V1_0} // namespace hidl_daemon} // namespace hardware} // namespace android# endif // ANDROID_HARDWARE_NARUTO_V1_0_HIDL_DAEMON_H
@ Hidl_daemon.cpp# include "Hidl_daemon.h"namespace android {namespace hardware {namespace hidl_daemon{namespace V1_0 {namespace implementation {// Methods from INaruto follow.Return<void> Hidl_daemon::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) { // TODO implement char buf[100]; ::memset(buf, 0x00, 100); ::snprintf(buf, 100, "Hello World, %s", name.c_str()); hidl_string result(buf); _hidl_cb(result); return Void();}// Methods from ::android::hidl::base::V1_0::IBase follow.INaruto* HIDL_FETCH_IHidl_daemon(const char* /* name */) { return new Hidl_daemon();}} // namespace implementation} // namespace V1_0} // namespace hidl_daemon} // namespace hardware} // namespace android
- 我们打开 HIDL_FETCH 的注释,让我们的 HIDL 代码使用 Passthrough 方式去实现。
- 添加 helloWorld 函数的实现,简单的做了字符串的拼接
然后,可以查看一下 Android.bp 文件看下编译生成啥。
cc_library_shared { name: "android.hardware.hidl_daemon@1.0-impl", relative_install_path: "hw", proprietary: true, srcs: [ "Hidl_daemon.cpp", ], shared_libs: [ "libhidlbase", "libhidltransport", "libutils", "android.hardware.hidl_daemon@1.0", ],}
最终会生成 android.hardware.hidl_daemon@1.0-impl.so , 生成在 /vendor/lib64/hw 下,
我们用 mmm 编译生成看看。
$ mmm hardware/interfaces/hidl_daemon/1.0/default/# OUT_DIR=out[2/2] bootstrap out/soong/.minibootstrap/build.ninja.in[1/1] out/soong/.bootstrap/bin/minibp out/soong/.bootstrap/build.ninja[2/3] glob hardware/interfaces/*/Android.bp[1/1] out/soong/.bootstrap/bin/soong_build out/soong/build.ninjaNo need to regenerate ninja file[100% 3/3] out/soong/.bootstrap/bin/soong_build out/soong/build.ninja[100% 18/18] build 'out/target/product/hon660/obj/SHARED_LIBRARIES/android.hardware.hidl_daemon@1.0-impl_intermediates/android.hardware.hidl_daemon@1.0-impl.so.toc'#### build completed successfully (02:35 (mm:ss))
2.2.4 代码调用流程
前面我们实现了代码端的编译,我们这节来看下整个 HIDL 的调用流程,
因为里面涉及到了好几个库文件,我们来看下这之前的关系。
HIDL 软件包中自动生成的文件会链接到与软件包同名的单个共享库。
该共享库还会导出单个头文件 Hidl_daemon.h,用于在 binder 客户端和服务端的接口文件。
下面的图诠释了我们的 IHidl_daemon.hal 编译后生成的文件走向。
从官网拷贝过来的,大家不用在乎文件名哈:
-
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 存根的实现的类。
当客户端调用接口的方法时,代理会自动从客户端封送参数,关将事务发送到绑定内核驱动程序,该内核 驱动程序会将事务传送到另一端的存根(该存根随后会调用实际的服务器)
这些文件的结构类似于由 aidl-cpp 生成的文件。
获立于 HIDL 使用的 RPC 机制的唯一一个自动生成的文件是 IFoo.h,
其他所有文件都与HIDL 使用的 HwBinder RPC 机制相关联。
因此,客户端和服务器实现不得直接引用除 IFoo 之外的任何内容。
为满足这项要求,请只包含 IFoo.h 并链接到 生成的共享库。
我们这个实例会用到以下几个模块:
-
android.hardware.hidl_daemon@1.0-impl.so
Hidl_daemon模块实现端的代码编译生成,binder server端 -
android.hardware.hidl_daemon@1.0.so
Hidl_daemon模块调用端的代码,binder client端 -
hidl_daemon_hal_service
通过直通式注册binder service,暴露接口给client调用 -
android.hardware.hidl_daemon@1.0-service.rc
Android native 进程入口
大概流程就是这个样子。
2.2.5 启动binder server端进程
还记得我们之前创建的两个文件吗,我们还没有去实现呢,先来看一下rc文件
service hidl_daemon_hal_service /vendor/bin/hw/android.hardware.hidl_daemon@1.0-service class hal user system group system
很简单,就是在设备启动的时候执行/vendor/bin/hw/android.hardware.hidl_daemon@1.0-service程序:
# define LOG_TAG "android.hardware.hidl_daemon@1.0-service"# include # include using android::hardware::hidl_daemon::V1_0::INaruto;using android::hardware::defaultPassthroughServiceImplementation;int main() { return defaultPassthroughServiceImplementation<IHidl_daemon>();}
这个service是注册了INaruto接口文件里面的接口,作为 binder server 端,
很简单就一句话,
因为我们使用了 passthrough 的模式,Android 帮我们封装了这个函数,不需要我们自己去 addService 啦。
cc_binary { name: "android.hardware.hidl_daemon@1.0-service", defaults: ["hidl_defaults"], proprietary: true, relative_install_path: "hw", srcs: ["service.cpp"], init_rc: ["android.hardware.hidl_daemon@1.0-service.rc"], shared_libs: [ "libhidlbase", "libhidltransport", "libutils", "liblog", "android.hardware.hidl_daemon@1.0", "android.hardware.hidl_daemon@1.0-impl", ],}
编译后可以在, vendor/bin/hw/下找到对应的文件。
OK,我们server端的进程和实现端共享库已经完成了。
但是这个时候你如果烧录镜像,会发现这个进程会启动失败,
原因是因为我们没有给这个进程配sepolicy,所以正确的做法是要给他加上selinux的权限,
我们这里就不去做了,因为我们可以用root权限去手动起这个service。
好了,接下来要看看client的代码怎么写了。
2.2.6 HIDL Client测试代码
测试代码如下:
@ client.cpp# include # include # include # include # include # include using android::hardware::naruto::V1_0::INaruto;using android::sp;using android::hardware::hidl_string;int main(){ int ret; android::sp<IHidl_daemon> service = IHidl_daemon::getService(); if(service == nullptr) { printf("Failed to get service\n"); return -1; } service->helloWorld("aaaaaa", [&](hidl_string result) { printf("%s\n", result.c_str()); }); return 0;}
实例化binder service,通过INaruto::getService(),获取到binder server端接接口代理类,然后就可以调用他的方法了,我们这里调用helloWorld接口,然后通过callback获取结果。
Makefile
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_PROPRIETARY_MODULE := trueLOCAL_MODULE := hidl_daemon_testLOCAL_SRC_FILES := \ client.cpp \LOCAL_SHARED_LIBRARIES := \ liblog \ libhidlbase \ libutils \ android.hardware.hidl_daemon@1.0 \include $(BUILD_EXECUTABLE)
记得在manifest文件里添加vendor接口的定义,
不然在client端是没法拿到service的,在相应的manifest.xml里面加入:
<hal format="hidl"> <name>android.hardware.hidl_daemonname> <transport>hwbindertransport> <version>1.0version> <interface> <name>IHidl_daemonname> <instance>defaultinstance> interface>hal>
然后我们来测试一下代码吧:
手动运行service:
/vendor/bin/hw/android.hardware.hidl_daemon@1.0-service
动行测试代码
./hidl_daemon_test
更多相关文章
- 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
- 一款常用的 Squid 日志分析工具
- GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
- RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
- Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
- AndroidManifest.xml反编译
- Android(安卓)Studio引入Lambda以及介绍和简单实用
- Android开发 去掉标题栏方法 摘记
- 解决 android 下sqlite3_column_table_name 编译不过的问题