Android HIDL服务实现-(Binderized Mode)(二)

一、前言

正如 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 服务所需要的全本文件配置完成,虽然其中很多文件是空的,或者没有具体实现,我们现在先放在一边,先来对整体的调用流程及各个文件的功效略作说明。

Binder 化直通式
  • 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.shlunch 的窗口下编译,若是新窗口则需要重新执行这两条命令)

mmm  hardware/interfaces/galaxy_one/1.0

此时应该可以在 out/tartget/product/XXX/vendor/lib64/hwout/tartget/product/XXX/system/lib64/hw 目录下找到 android.hardware.galaxy_one@1.0.soandroid.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

运行成功之后可查看日志,有如下内容则表示服务建立成功:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容