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
  1. 我们打开 HIDL_FETCH 的注释,让我们的 HIDL 代码使用 Passthrough 方式去实现。
  2. 添加 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 并链接到 生成的共享库。

我们这个实例会用到以下几个模块:

  1. android.hardware.hidl_daemon@1.0-impl.so
    Hidl_daemon模块实现端的代码编译生成,binder server端

  2. android.hardware.hidl_daemon@1.0.so
    Hidl_daemon模块调用端的代码,binder client端

  3. hidl_daemon_hal_service
    通过直通式注册binder service,暴露接口给client调用

  4. 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

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. 一款常用的 Squid 日志分析工具
  3. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  4. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  5. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  6. AndroidManifest.xml反编译
  7. Android(安卓)Studio引入Lambda以及介绍和简单实用
  8. Android开发 去掉标题栏方法 摘记
  9. 解决 android 下sqlite3_column_table_name 编译不过的问题

随机推荐

  1. c语言标识符有哪三类?
  2. c语言fopen打开文件失败的原因是什么?
  3. c语言如何求最大公约数和最小公倍数?
  4. 新手程序员应该知道的C语言和C++的区别
  5. 详解C语言中的复数操作
  6. freopen函数的用法详解
  7. 几款好用的C语言编译器推荐
  8. c语言和c++区别大吗
  9. 用c语言编写爱心的代码是什么?
  10. C语言自定义函数(图文详解)