Android HIDL学习 - 注册回调(整理2)

概述

上一节我们学会了如何创建HIDL的server端和client端,对于那些没玩过Android O或者以上的BSP开发者而言,可以吹上一阵子牛逼了,毕竟比人家多了一个技能,面试的时候也可以装一下了_

OK,我们还知道了在Android O或者以上的Android版本上创建一个HAL模块的一般流程是如何的,我们这一节来看一个比较简单的东西,也是每个模块基本必不可少的一个玩意儿,那就是回调函数。

注册回调

怎么个回事呢,我们来举一个栗子:
在这里插入图片描述
很多现有的 HAL 实现会与异步硬件通信,这意味着它们需要以异步方式通知客户端已发生的新事件。HIDL 接口可以用作异步回调,因为 HIDL 接口函数可以将 HIDL 接口对象用作参数。
我们把HAL独立为一个单独的进程,client也是一个单独的进程,那么对于一般的模块而言,都是需要从底层(HAL以及以下)获取数据,比如sensor,需要获取sensor数据,Camera,需要获取camera的raw、yuv等数据流,那么对于软件设计而言,如果是同步的话,很简单,我们通过getXXX()函数来获取即可,但是如果是异步的,比如底层的实现是中端的机制,你不知道他什么时候会出来数据,那么这个时候通常的,我们会通过callback来实现异步的回调。

看下面的图就比较清楚了:
在这里插入图片描述官方关于回调的介绍请参考

实例

我们这一节就来实现简单的回调机制。
这个例子很简单,写一个简单的HAL模块,就跟之前的差不多,然后我们在.hal文件里面加入一个setCallback函数,传入一个callback指针,当我们HAL的server端起来的时候会起一个线程,每隔1秒钟时间调用一下传入的这个回调函数,实现回调的机制,OK,废话不多说,上代码。

1. 创建接口目录

首先,和上一篇naruto一样先创建文件目录,即定义接口的位置。
我们现在将该接口定义到vendor厂商目录中,如下:

mkdir -p vendor/mediatek/proprietary/hardware/interfaces/hello/1.0/default

2. 定义HIDL接口

其次,开始定义HIDL 能够访问到的接口IHello.hal

// The file path: vendor/mediatek/proprietary/hardware/interfaces/hello/1.0/IHello.hal
// 定义我们的包名
package vendor.mediatek.hardware.hello@1.0;  
// 导入回调接口
import IHelloCallback;

interface IHello {
    // 初始化服务端实例行为
    init();
    // 释放服务端
    release();
    // 用于客户端向服务端注册回调函数
    setCallback(IHelloCallback callback);
};

定义了三个接口

  • init:做一些初始化的动作
  • release:做一些释放的动作
  • setCallback:让client端设置一个callback方法到server端

下面来看看这个callback里面都定义了些啥,我们要为这个callback定义一个接口IHelloCallback.hal

package vendor.mediatek.hardware.hello@1.0;

interface IHelloCallback {
    oneway onNotify(HalEvent event);
};

这个接口里面有一个回调方法,可以让server传一个HalEvent的结构体到client端,这个结构体也是自定义的,在types.hal,可以定义自己喜欢的类型,这里只是定义了一个简单的int成员变量通过这个onNotify传递给客户端。

package vendor.mediatek.hardware.hello@1.0;

struct HalEvent {
   int32_t value;
};

3. 生成hal代码框架

OK,HIDL的接口定义好之后,我们来使用一条牛逼的指令为我们生产代码框架:

# 自动生成impl实例文件
hidl-gen -o vendor/mediatek/proprietary/hardware/interfaces/hello/1.0/default -Lc++-impl -rvendor.mediatek.hardware:vendor/mediatek/proprietary/hardware/interfaces -randroid.hidl:system/libhidl/transport vendor.mediatek.hardware.hello@1.0
# 自动生成编译控制文件Android.bp - 只有mk可以不生成
hidl-gen  -Landroidbp -rvendor.mediatek.hardware:vendor/mediatek/proprietary/hardware/interfaces -randroid.hidl:system/libhidl/transport vendor.mediatek.hardware.hello@1.0
# 自动生成编译控制文件Android.mk - 只有bp可以不生成
hidl-gen  -Lmakefile -rvendor.mediatek.hardware:vendor/mediatek/proprietary/hardware/interfaces -randroid.hidl:system/libhidl/transport vendor.mediatek.hardware.hello@1.0
# 增加顶层Android.bp编译控制内容 - 这个文件通过hidl-gen无法自动生成,需要其他脚本才可以自动生成,在这里我们只能手动编写增加这个文件
// The file path: vendor/mediatek/proprietary/hardware/interfaces/hello/Android.bp
// This is an autogenerated file, do not edit.
subdirs = [
    "1.0",
    "1.0/default",
]

以上是通过hidl-gen手动运行该命令自动生成的上述文件。执行起来还是很麻烦,在这里只是让大家知道hidl-gen是如何输入命令执行的。在真正开发过程中Google已经给我们提供了一个自动生成的脚本,就像前一篇所使用的update-makefiles.sh全部都给搞定,这样就不怕有些文件无法通过hidl-gen自动生成比如上面的Android.bp还需要手动编写或者某些文件遗漏等等细节问题。

# 上述那些文件,通过这个脚本将全部搞定,一步撸到底 很爽
./vendor/mediatek/proprietary/hardware/interfaces/update-makefiles.sh

现在的文件目录如下:

.
├── 1.0
│   ├── Android.bp	# 自动生成update-makefiles.sh或者手动执行hidl-gen命令
│   ├── Android.mk  # 自动生成update-makefiles.sh或者手动执行hidl-gen命令
│   ├── default
│   │   ├── Android.bp #自动生成update-makefiles.sh或者手动执行hidl-gen命令
│   │   ├── HelloCallback.cpp #自动生成update-makefiles.sh或者手动执行hidl-gen命令
│   │   ├── HelloCallback.h #自动生成update-makefiles.sh或者手动执行hidl-gen命令
│   │   ├── Hello.cpp #自动生成update-makefiles.sh或者手动执行hidl-gen命令
│   │   ├── Hello.h #自动生成update-makefiles.sh或者手动执行hidl-gen命令
│   │   ├── service.cpp #手动增加编辑 - 服务端注册启动入口
│   │   └── vendor.mediatek.hardware.hello@1.0-service.rc #用于开机自启动行为
│   ├── IHelloCallback.hal  # 手动定义回调接口
│   ├── IHello.hal 	# 手动定义接口
│   └── types.hal # 自定义数据类型
└── Android.bp  # 自动生成update-makefiles.sh或手动增加编辑

4. 编写服务端注册入口

在vendor分区,要起一个service来handle这个HIDL 接口,这个我们在上一节中有详细讲到,贴一下代码:

#include <vendor/mediatek/hardware/hello/1.0/IHello.h>
#include <hidl/LegacySupport.h>

using vendor::mediatek::hardware::hello::V1_0::IHello;
using android::hardware::defaultPassthroughServiceImplementation;

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

然后是makefile中增加:

# The file path: vendor/mediatek/proprietary/hardware/interfaces/hello/1.0/default/Android.bp
cc_binary {
 name: "vendor.mediatek.hardware.hello@1.0-service",
 relative_install_path: "hw",
 defaults: ["hidl_defaults"],
 vendor: true,
 init_rc: ["vendor.mediatek.hardware.hello@1.0-service.rc"],
 srcs: [
 "service.cpp",
 ],
 shared_libs: [
 "liblog",
 "libutils",
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.mediatek.hardware.hello@1.0",
 ],
}

5.实现hello接口服务端实例

接下来,需要完善impl实例代码的填充Hello.cpp,如下:

//The file path: vendor/mediatek/proprietary/hardware/interfaces/hello/1.0/default/Hello.cpp
#define LOG_TAG     "Example"
#include "Hello.h"
#include <log/log.h>

namespace vendor {
namespace mediatek {
namespace hardware {
namespace hello {
namespace V1_0 {
namespace implementation {

// Methods from IHello follow.
Return<void> Hello::init() {
    // TODO implement
    mExit = false;
    // 调用Thread::run()方法启动Thread线程工作
    run("Hello example");
    return Void();
}

Return<void> Hello::release() {
    // TODO implement
    mExit = true;
    return Void();
}

Return<void> Hello::setCallback(const sp<IHelloCallback>& callback) {
    // TODO implement
    mCallback = callback;
    if(mCallback != nullptr) {
        ALOGD("setCallback: done!");
    }
    return Void();
}

bool Hello::threadLoop()
{
    static int32_t count = 0;
    HalEvent event;
    while(!mExit) {
    ::sleep(1);
    event.value = count ++;
    if(mCallback != nullptr) {
        mCallback->onNotify(event);
    }
    }
    ALOGD("threadLoop: exit");
    return false;
}

// Methods from ::android::hidl::base::V1_0::IBase follow.

IHello* HIDL_FETCH_IHello(const char* /* name */) {
    return new Hello();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace hello
}  // namespace hardware
}  // namespace mediatek
}  // namespace vendor

头文件,填充:

//The file path: vendor/mediatek/proprietary/hardware/interfaces/hello/1.0/default/Hello.h
#ifndef VENDOR_MEDIATEK_HARDWARE_HELLO_V1_0_HELLO_H
#define VENDOR_MEDIATEK_HARDWARE_HELLO_V1_0_HELLO_H

#include <vendor/mediatek/hardware/hello/1.0/IHello.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <utils/Thread.h>

namespace vendor {
namespace mediatek {
namespace hardware {
namespace hello {
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;
// 需要继承Thread类
struct Hello : public IHello, public android::Thread {
    // Methods from IHello follow.
    Return<void> init() override;
    Return<void> release() override;
    Return<void> setCallback(const sp<IHelloCallback>& callback) override;

    // Methods from ::android::hidl::base::V1_0::IBase follow.
    /**
     * 重写thread工作任务
     * 1s钟执行一次回调
     * /
    bool threadLoop() override;
    bool mExit;
    sp<IHelloCallback> mCallback;
};

// FIXME: most likely delete, this is only for passthrough implementations
   extern "C" IHello* HIDL_FETCH_IHello(const char* name);

}  // namespace implementation
}  // namespace V1_0
}  // namespace hello
}  // namespace hardware
}  // namespace mediatek
}  // namespace vendor

#endif  // VENDOR_MEDIATEK_HARDWARE_HELLO_V1_0_HELLO_H

在init函数里面调用run方法去启动线程,线程的主体是threadLoop函数,可以看到在线程里面,是一个死循环,会每隔1秒钟去callback一次方法,还是很简单的。

6.编译服务端

在AOSP根目录执行mmm编译会生成如下文件:

# 编译执行命令
mmm vendor/mediatek/proprietary/hardware/interfaces/hello

/system/lib/vendor.mediatek.hardware.hello@1.0.so和
/vendor/lib/hw/vendor.mediatek.hardware.hello@1.0-impl.so及
/vendor/bin/hw/vendor.mediatek.hardware.hello@1.0-service

7.HIDL Client客户端测试程序

按照惯例,还是需要写一个测试程序来验证,我们服务端的正确性。其他不表,请看如下代码:

// The file path: vendor/mediatek/proprietary/external/hello_client/hello_client.cpp
#define LOG_TAG     "HelloClient"
#include <log/log.h>
#include <vendor/mediatek/hardware/hello/1.0/types.h>
#include <vendor/mediatek/hardware/hello/1.0/IHello.h>
#include <vendor/mediatek/hardware/hello/1.0/IHelloCallback.h>
#include <hidl/Status.h>
#include <hidl/HidlSupport.h>

using android::sp;
using android::hardware::Return;
using android::hardware::Void;
using vendor::mediatek::hardware::hello::V1_0::HalEvent;
using vendor::mediatek::hardware::hello::V1_0::IHello;
using vendor::mediatek::hardware::hello::V1_0::IHelloCallback;

// 声明定义一个回调接口实例需要继承回调接口
class HelloCallback: public IHelloCallback {
public:
 HelloCallback() {
}

~HelloCallback() {
}
// 该函数指针需要注册到服务端,由服务端主动调用
Return<void> onNotify(const HalEvent& event) {
 ALOGD("onNotify: value = %d", event.value);
 return Void();
}
};

int main(void)
{
 sp<IHello> service = IHello::getService();
 if(service == nullptr) {
 ALOGE("main: failed to get hello service");
 return -1;
 }

 ALOGD("main: get hello service ok");
sp<HelloCallback> callback = new HelloCallback();
service->setCallback(callback);
service->init();
// 忙等待10s后释放服务端
::sleep(10);
service->release();
// 释放完最好等待一会, 因为这个回调接口中定义的方法是oneway必须保证线程安全,
// 在这我们简单延时等待一会,让这个函数指针确保完成, 否则会导致服务端奔溃crash
::sleep(3);

return 0;
}

OK,在hello_client端就是简单的打印了callback回来的event里面的数据。这就类似于camera主动推送raw data/yuv数据给客户端同理。
接下来,编写Makefile进行编译控制:

// vendor/guomin/proprietary/external/hello_client/Android.bp
cc_binary {
 name: "hello_client",
 srcs: [
 "hello_client.cpp",
 ],
 shared_libs: [
 "liblog",
 "libutils",
 "libhidlbase",
 "libhidltransport",
 "libutils",
 "vendor.mediatek.hardware.hello@1.0",
 ],
}

以上,通过以下指令, 编译后会生成在/system/bin/hello_client中。

mmm vendor/mediatek/proprietary/external/hello_client

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

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

同理,修改完之后需要全编整个系统,重新刷固件到设备中才可以。否则客户端在getService()的时候会出现无法获取服务问题。

8.测试实例

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

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

同上一篇一样,我们需要将上述文件分别push到设备对应目录中:

adb push vendor.mediatek.hardware.hello@1.0.so /system/lib/
adb push vendor.mediatek.hardware.hello@1.0-impl.so /vendor/lib/hw/
adb push vendor.mediatek.hardware.hello@1.0-service /system/bin
adb push hello_client /system/bin	  

首先,启动service程序进行注册服务:
在这里插入图片描述

07-28 17:36:41.757  1797  1797 D vndksupport: Loading /vendor/lib/hw/vendor.mediatek.hardware.hello@1.0-impl.so from current namespace instead of sphal namespace.
07-28 17:36:41.760  1797  1797 I ServiceManagement: Removing namespace from process name vendor.mediatek.hardware.hello@1.0-service to hello@1.0-servi.
07-28 17:36:41.762  1797  1797 I         : Registration complete for vendor.mediatek.hardware.hello@1.0::IHello/default.

然后,运行客户端测试程序hello_client:
在这里插入图片描述

07-29 10:38:01.449  2060  2060 D HelloClient: main: get hello service ok
07-29 10:38:02.451  2060  2061 D HelloClient: onNotify: value = 0
07-29 10:38:03.452  2060  2061 D HelloClient: onNotify: value = 1
07-29 10:38:04.452  2060  2061 D HelloClient: onNotify: value = 2
07-29 10:38:05.453  2060  2061 D HelloClient: onNotify: value = 3
07-29 10:38:06.454  2060  2061 D HelloClient: onNotify: value = 4
07-29 10:38:07.455  2060  2061 D HelloClient: onNotify: value = 5
07-29 10:38:08.455  2060  2061 D HelloClient: onNotify: value = 6
07-29 10:38:09.456  2060  2061 D HelloClient: onNotify: value = 7
07-29 10:38:10.457  2060  2061 D HelloClient: onNotify: value = 8
07-29 10:38:11.457  2060  2061 D HelloClient: onNotify: value = 9

总结

1.命名规范

函数名称、变量名称和文件名应该是描述性名称;避免过度缩写。将首字母缩略词视为字词(例如,请使用 INfc,而非 INFC)。

2.目录结构和文件命名

.
├── ROOT-DIRECTORY (vendor/mediatek/proprietary/hardware/interfaces)
│ └── MODULE (hello)
│ └── SUBMODULE (可选,可以有多层)
│ ├── Android.bp
└── VERSION (1.0)
├── Android.bp
├── IINTERFACE_1.hal
├── IINTERFACE_2.hal

└── IINTERFACE_N.hal

其中:

  • ROOT-DIRECTORY 为:hardware/interfaces(如果是核心 HIDL 软件包)。
    vendor/VENDOR/interfaces(如果是供应商软件包),其中 VENDOR 指 SoC 供应商或 OEM/原始设计制造商 (ODM)。
  • MODULE 应该是一个描述子系统的小写字词(例如 nfc)。如果需要多个字词,请使用嵌套式 SUBMODULE。可以嵌套多层。
  • VERSION 应该与版本中所述的版本完全相同 (major.minor)。
  • IINTERFACE_X 应该是含有 UpperCamelCase/PascalCase 的接口名称(例如 INfc/IHello/IHelloCallback),如接口名称中所述。

例如:
.
├─ nfc
├── 1.0
│ ├── Android.bp
│ ├── Android.mk
│ ├── default
│ │ ├── Android.bp
│ │ ├── android.hardware.nfc@1.0-service.rc
│ │ ├── Nfc.cpp
│ │ ├── Nfc.h
│ │ ├── OWNERS
│ │ └── service.cpp
│ ├── INfcClientCallback.hal
│ ├── INfc.hal
│ ├── types.hal
│ └── vts
│ ├── Android.mk
│ └── functional
│ ├── Android.bp
│ └── VtsHalNfcV1_0TargetTest.cpp
└── Android.bp
注意:所有文件都必须采用不可执行的权限(在 Git 中)。

3.软件包名称

软件包名称必须采用以下完全限定名称 (FQN) 格式(称为 PACKAGE-NAME):

PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION
其中:

  • PACKAGE 是映射到 ROOT-DIRECTORY 的软件包。具体来说,PACKAGE 是:
    android.hardware(如果是核心 HIDL 软件包)(映射到 源码目录hardware/interfaces)。
    vendor.VENDOR.hardware(如果是供应商软件包),其中 VENDOR 指 SoC 供应商或原始设备制造商 (OEM)/原始设计制造商 (ODM)(映射到 vendor/VENDOR/[proprietary]/hardware/interfaces)。
  • MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION 与目录结构中所述结构内的文件夹名称完全相同。
  • 软件包名称应为小写。如果软件包名称包含多个字词,则这些字词应用作子模块或以 snake_case 形式书写。不允许使用空格。软件包声明中始终使用 FQN。

4.导入接口包

导入包采用以下 3 种格式之一:

  • 完整软件包导入:import PACKAGE-NAME;
  • 部分导入:import PACKAGE-NAME::UDT;(或者,如果导入的类型是在同一个软件包中,则为 import UDT;)
import vendor.mediatek.hardware::IHello;
  • 仅类型导入:import PACKAGE-NAME::types;
  • PACKAGE-NAME 遵循软件包名称中的格式。当前软件包的 types.hal(如果存在)是自动导入的(请勿对其进行显式导入)。

本文参考链接:https://www.jianshu.com/p/ca6823b897b5

  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值