Android HIDL学习 - HelloWord入门(整理1)

概述

HAL 接口定义语言(简称 HIDL,发音为“hide-l”)是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。HIDL 允许指定类型和方法调用(会汇集到接口和软件包中)。从更广泛的意义上来说,HIDL 是用于在可以独立编译的代码库之间进行通信的系统。

HIDL 旨在用于进程间通信 (IPC)。进程之间的通信采用 Binder 机制。对于必须与进程相关联的代码库,还可以使用直通模式(在 Java 中不受支持)。

HIDL 可指定数据结构和方法签名,这些内容会整理归类到接口(与类相似)中,而接口会汇集到软件包中。尽管 HIDL 具有一系列不同的关键字,但 C++ 和 Java 程序员对 HIDL 的语法并不陌生。此外,HIDL 还使用 Java 样式的注释。
详细可以了解官方参考链接

入手准备

程序员有个癖好,无论是学习什么新知识,都喜欢以HelloWorld作为一个简单的例子来开头,咱们也不例外。
OK,咱这里都是干货,废话就不多说啦,学习HIDL呢咱们还是需要一些准备工作和门槛的。
准备工作:

Android BSP编译环境, 需要做整编
Android 设备的BSP代码
Android设备,用来跑测试代码

我这边使用的是MTK的一款设备,基于Android O系统这个平台来做开发。

当然了,如果手头上没有设备的话,你也可以使用Andnroid模拟器做开发,Android模拟器的镜像可以使用官方的AOSP代码来编译,但是注意的是如果使用模拟器,kernel要去下载goldfish的代码,这个我这里就不赘述了,可以google了解一下。

实例应用

接下来我们开始第一个HIDL应用实例,我们需要给这个简单的例子起一个牛逼的名字,我这里叫Naruto,不要问我为什么,哥是一个铁打的火影迷,哈哈,就这么定了,就叫Naruto了,那么那么我们就来说一段故事吧:

咱们可是要写一个Android的HAL,大家不要把初衷搞混了,我们看看AOSP有哪些HAL:

  • Camera
  • Audio
  • Sensor
  • 等等
    在这里插入图片描述
    这些啊都是Android设备上的硬件,因为Google理论上只关心Android的框架层和上层软件,但是上层软件依赖于底层的硬件实现,但是每家手机厂商,或者说是CPU厂商底层硬件的实现都是不一样的,所以这个HAL层基本都是手机厂商或者CPU厂商去实现的,Google只是作为一个框架的指导,和Framework层API的接口定义,这些接口的实现都得由HAL去完成。

那么我们的Naruto就肩负了这个重任喽,控制底层硬件嘛,底层硬件都是由Linux kernel驱动控制的,提供文件读写就可以简单控制驱动啦,咱们这边就搞虚拟驱动好了,省略了kernel driver的实现;

等等,我们这个是HelloWorld,好吧,Naruto,你就提供一个HelloWorld的接口吧,大材小用了。目的是让我们知道写一个HIDL的流程这就够了!!!

1.HIDL接口文件定义

进入代码,我们假设Naruto作为标准AOSP的HAL,我们就把代码揉进标准HAL(hardware/interface)层去,进入代码目录创建HIDL目录:

mkdir -p hardware/interfaces/naruto/1.0/default

接着创建接口描述文件INaruto.hal,放在刚才创建的1.0/目录中:

// INaruto.hal
package android.hardware.naruto@1.0;

interface INaruto {
    helloWorld(string name) generates (string result);
};

没错这是一个Google定义的语言格式具体数据类型转换请查看对应关系,C++和Java的结合体,我相信咱们搞Android BSP来发的,什么语言不会呢,对不:

  • 汇编:bootloader和kernel中可能会用到
  • C语言:这你丫不会,你玩毛的Linux Kernel啊
  • C++:这你丫不会,你就别搞Android底层开发了,HAL和中间库
  • Java:这么再不会就自杀吧,framework和app的代码都是Java的
  • Python:这个不会么也没事,编译相关的
  • Shell:这个不可能不会
  • Makefile:肯定会的,不会跳楼吧

这里我们定义了一个INaruto接口文件,简单的添加了一个helloWorld接口,传入是一个string,返回一个string,后面我们会来实现这个接口。

2. 生成HAL相关文件

如上,hal接口文件已经定义完成,接下来我们需要继承实现这个接口,那么我们应该如何继承如何编写呢,幸运的是Google还是帮我们提供了一套工具hidl-gen来生成HAL层相关的代码框架和代码实例,这样子我们只需要关心实现部分,而不需要写一堆无用代码,浪费时间在搞Makefile和一些低级错误上。

  • hidl-gen 可以自动帮我们生成hal层实例框架和Android.mk/bp编译控制
    因此,系统已经帮我们提供了一个执行脚本hardware/interface/update-makefiles.sh, 如下执行将会自动为我们生成所需要的文件:
./hardware/interface/update-makefiles.sh

查看我们最新的代码目录: hardware/interface/naruto:

├── 1.0
│   ├── Android.bp	//自动生成
│   ├── Android.mk  //自动生成
│   ├── default
│   │   ├── Android.bp  //需要自己编写
│   │   ├── android.hardware.naruto@1.0-service.rc  //用于加入开机启动服务
│   │   ├── Naruto.cpp  //自动生成
│   │   ├── Naruto.h    //自动生成
│   │   └── service.cpp   // hidl服务端注册入口程序
│   └── INaruto.hal  //定义接口
└── Android.bp    //自动生成

是不是so easy,我们写代码就写了一个INaruto.hal,其余代码都是自动生成的,特别是Naruto.cpp和Naruto.h这两个文件是实现接口的关键文件。

3. 实现HAL服务端的共享库

打开Naruto.h文件:

// Naruto.h
#ifndef ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H
#define ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H

#include <android/hardware/naruto/1.0/INaruto.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>

namespace android {
namespace hardware {
namespace naruto {
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 Naruto : public INaruto {
    // 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 naruto
}  // namespace hardware
}  // namespace android

#endif  // ANDROID_HARDWARE_NARUTO_V1_0_NARUTO_H

我们知道,HIDL的实现有两种方式,一种是Binderized模式,另一种是Passthrough模式,我们看到上面有两行注释掉的代码,看来这个代码是关键,来选择实现方式是Binderized还是Passthrough。

我们这里使用Passthrough模式来演示,其实大家后面尝试这两种方式后会发现其实这两种本质是一样的,目前大部分厂商使用的都是Passthrough来延续以前的很多代码,但是慢慢的都会被改掉的,所以我们来打开这个注释 - extern “C” INaruto* HIDL_FETCH_INaruto(const char* name);

打开并修改Naruto.cpp:

// Naruto.cpp
#include "Naruto.h"

namespace android {
namespace hardware {
namespace naruto {
namespace V1_0 {
namespace implementation {

// Methods from INaruto follow.
Return<void> Naruto::helloWorld(const hidl_string& name, helloWorld_cb _hidl_cb) {
    // TODO implement
    char buf[100];

    memset(buf, 0, 100);
    snprintf(buf, 100, "HelloWorld, %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_INaruto(const char* /* name */) {
    return new Naruto();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace naruto
}  // namespace hardware
}  // namespace android

这个文件我们做了两处改动:

  • 打开了HIDL_FETCH_I的注释,让我们的HIDL使用Passthrough方式去实现
  • 添加helloWorld函数的实现,简单的做了字符串拼接(学过C/C++)的同学应该都看得懂

接下来我们编写一下default/Android.bp文件生成我们需要impl实现的so库。

default/Android.bp
cc_library_shared {
    name: "android.hardware.naruto@1.0-impl",
    relative_install_path: "hw",
    proprietary: true,
    srcs: [
        "Naruto.cpp",
    ],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "android.hardware.naruto@1.0",
    ],
}

最终会生成一个android.hardware.naruto@1.0-impl.so, 生成在/vendor/lib/hw/下,如果是64bits的平台会生成在/vendor/lib64/hw,在这里我用的是32bits平台,最终会生成在/vendor/lib/hw/目录下;
我们可以用mmm编译生成。

mmm hardware/interface/naruto

这样将会生成两个so文件,一个是INaruto接口框架库文件:android.hardware.naruto@1.0.so 一个是INaruto实现接口库文件:android.hardware.naruto@1.0-impl.so
他们直接的调用流程大概如下:
在这里插入图片描述

4. Hal server端启动注册程序

现在,我们有了HIDL接口及接口的实现实例,接下来需要将这个服务端注册到HWServiceManager中,客户端才能通过binder接口getService()获取到服务端的接口并使用它。

还记得我们之前创建的两个文件吗,我们还没有去实现呢,先来看一下rc文件

# android.hardware.naruto@1.0-service.rc
service naruto_hal_service /vendor/bin/hw/android.hardware.naruto@1.0-service
    class hal
    user system
    group system

很简单,就是在设备启动的时候执行/vendor/bin/hw/android.hardware.naruto@1.0-service程序,这个程序就是去注册服务的到serviceManager管理中的,具体实现如下:

service.cpp文件:

// service.cpp
# define LOG_TAG "android.hardware.naruto@1.0-service"

# include <android/hardware/naruto/1.0/INaruto.h>
# include <hidl/LegacySupport.h>

using android::hardware::naruto::V1_0::INaruto;
using android::hardware::defaultPassthroughServiceImplementation;

int main() {
    return defaultPassthroughServiceImplementation<INaruto>();
}

这个service是注册了INaruto接口文件里面的接口,作为binder server端,很简单就一句话,因为我们使用了passthrough的模式,Android帮我们封装了这个函数,不需要我们自己去addService啦,直接调用defaultPassthroughServiceImplementation即可。 还有另一种注册方式是针对binderies的:
服务端的注册实现

::android::sp<IFoo> myFoo = new FooImpl();
::android::sp<IFoo> mySecondFoo = new FooAnotherImpl();
status_t status = myFoo->registerAsService();
status_t anotherStatus = mySecondFoo->registerAsService("another_foo");

接下来,在Android.bp中添加对应的编译控制逻辑:

cc_binary {
    name: "android.hardware.naruto@1.0-service",
    defaults: ["hidl_defaults"],
    proprietary: true,
    relative_install_path: "hw",
    srcs: ["service.cpp"],
    init_rc: ["android.hardware.naruto@1.0-service.rc"],
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "liblog",
        "android.hardware.naruto@1.0",
        "android.hardware.naruto@1.0-impl",
    ],
}

编译后可以在, vendor/bin/hw/下找到对应的文件。

OK,我们server端的进程和实现端共享库已经完成了。

但是这个时候你如果烧录镜像,会发现这个进程会启动失败,原因是因为我们没有给这个进程配sepolicy,所以正确的做法是要给他加上selinux的权限,我们这里就不去做了,因为我们可以用root权限(直接push到设备)去手动起这个service。

好了,接下来要看看client的代码怎么写了。

5.HIDL Client端测试程序

我们服务端有了,接下来就需要编写一个客户端来验证服务端接口是否正确。
我写代码喜欢一步一步来,每一步都搞个测试代码来测试,一来是验证每一步的功能,而来呢是为后面测试使用。

我有个同事,写代码贼快,写完了之后就不知道咋调试了,这种方式不好,不好,大家不要效仿。

写代码不是一件难事,写好代码是一件不容易的事情,好的代码都是通过大量测试来改善的,没有谁可以一次性的写好代码,所以大家在设计阶段一定要把测试接口留出来,不然的话后面返工去re-design的话,会很没面子,没办法,做我们这一行的,天天都在赶进度,你TMD跟老板说要返工做re-design,老板不剁了你不可。

// naruto_test.cpp
# include <android/hardware/naruto/1.0/INaruto.h>
# include <hidl/Status.h>
# include <hidl/LegacySupport.h>
# include <utils/misc.h>
# include <hidl/HidlSupport.h>
# include <stdio.h>

using android::hardware::naruto::V1_0::INaruto;
using android::sp;
using android::hardware::hidl_string;

int main()
{
    int ret;

    android::sp<INaruto> service = INaruto::getService();
    if(service == nullptr) {
        printf("Failed to get service\n");
        return -1;
    }

    service->helloWorld("SvenCheng", [&](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 := naruto_test
LOCAL_SRC_FILES := \
    naruto_client.cpp \

LOCAL_SHARED_LIBRARIES := \
   liblog \
   libhidlbase \
   libutils \
   android.hardware.naruto@1.0 \

include $(BUILD_EXECUTABLE)

以上,通过mmm vendor/mediatek/proprietary/external/naruto编译完成后会生成在/system/bin/naruto_test中。

重要:
记得在manifest文件里添加vendor接口的定义,不然在client端是没法拿到service的,在相应的manifest.xml里面加入:重要:
记得在manifest文件里添加vendor接口的定义,不然在client端是没法拿到service的,在相应的manifest.xml里面加入:

# device/mediatek/mt8167/manifest.xml
<hal format="hidl">
    <name>android.hardware.naruto</name>
    <transport>hwbinder</transport>
    <version>1.0</version>
    <interface>
        <name>INaruto</name>
        <instance>default</instance>
    </interface>
</hal>

manifest.xml根据各家厂商放置的位置各不同,需要自己去辨别是在什么位置,一般都是在device///manifest.xml

修改manifest.xml文件后需要重新编译整个aosp并烧录镜像到设备中才可以,否则启动程序时会报:
无法找到接口getService()

6.测试实例

通过以上,我们总共产出了如下文件:

  • android.hardware.naruto@1.0.so: Naruto模块hal框架接口,暴露给客户端使用。
  • android.hardware.naruto@1.0-impl.so: Naruto模块服务端的代码实现,接口被调用后在binder server端的真正行为。
  • android.hardware.naruto@1.0-service: Naruto服务端启动注册入口程序
  • android.hardware.naruto@1.0-service.rc: Android native 进程入口
  • naruto_test: 客户端调用程序

我们需要将上述文件分别push到对应目录中:

adb push android.hardware.naruto@1.0.so /system/lib/
adb push android.hardware.naruto@1.0-impl.so /vendor/lib/hw/
adb push android.hardware.naruto@1.0-service /system/bin
adb push naruto_test /vendor/bin/hw	  #必须这个目录,否则会出现dlsysm失败问题

开始启动service:

aiv8167sm3_bsp:/ # android.hardware.naruto@1.0-service

开始启动client:

aiv8167sm3_bsp:/ # naruto_test
HelloWorld, SvenCheng

看到没有,我们的测试代码传入"SvenCheng"字符串,结果输出"HelloWorld, SvenCheng", 符合我们的预期结果。

所以本篇就结束喽,吃瓜群众还不赶快码代码,好记性不如烂笔头啊,自己不写一遍怎么记得住。

这知识简单的如本HIDL的使用,不要着急,后面会有别的知识点,毕竟这只是一个简单的HelloWorld。

作者:木叶风神
本文参考链接:https://www.jianshu.com/p/ca6823b897b5

  • 11
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值