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 HIDL学习(2) --- HelloWorld_第1张图片

这些都是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.0
LOC=hardware/interfaces/hidl_daemon/1.0/default/
make hidl-gen -j64
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

然后使用脚本来更新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.rc
touch 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 implementations
extern "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: "[email protected]",
    relative_install_path: "hw",
    proprietary: true,
    srcs: [
        "Hidl_daemon.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "[email protected]",
    ],
}

最终会生成 [email protected] , 生成在 /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.ninja
No 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/[email protected]_intermediates/[email protected]'

#### build completed successfully (02:35 (mm:ss))

 

2.2.4 代码调用流程

前面我们实现了代码端的编译,我们这节来看下整个 HIDL 的调用流程,
因为里面涉及到了好几个库文件,我们来看下这之前的关系。

HIDL 软件包中自动生成的文件会链接到与软件包同名的单个共享库。
该共享库还会导出单个头文件 Hidl_daemon.h,用于在 binder 客户端和服务端的接口文件。

下面的图诠释了我们的 IHidl_daemon.hal 编译后生成的文件走向。
从官网拷贝过来的,大家不用在乎文件名哈:
Android HIDL学习(2) --- HelloWorld_第2张图片

  • 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. [email protected]
    Hidl_daemon模块实现端的代码编译生成,binder server端

  2. [email protected]
    Hidl_daemon模块调用端的代码,binder client端

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

  4. [email protected]
    Android native 进程入口

Android HIDL学习(2) --- HelloWorld_第3张图片
大概流程就是这个样子。

 

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/[email protected]程序:

# define LOG_TAG "[email protected]"

# 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: "[email protected]",
    defaults: ["hidl_defaults"],
    proprietary: true,
    relative_install_path: "hw",
    srcs: ["service.cpp"],
    init_rc: ["[email protected]"],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "liblog",
        "[email protected]",
        "[email protected]",
    ],
}

编译后可以在, 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 := true
LOCAL_MODULE := hidl_daemon_test
LOCAL_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/[email protected]

动行测试代码
./hidl_daemon_test

你可能感兴趣的:(Android HIDL学习(2) --- HelloWorld)