上一节我们学会了如何创建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,废话不多说,上代码。
首先,和上一篇naruto一样先创建文件目录,即定义接口的位置。
我们现在将该接口定义到vendor厂商目录中,如下:
mkdir -p vendor/mediatek/proprietary/hardware/interfaces/hello/1.0/default
其次,开始定义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);
};
定义了三个接口
下面来看看这个callback里面都定义了些啥,我们要为这个callback定义一个接口IHelloCallback.hal
package [email protected];
interface IHelloCallback {
oneway onNotify(HalEvent event);
};
这个接口里面有一个回调方法,可以让server传一个HalEvent的结构体到client端,这个结构体也是自定义的,在types.hal,可以定义自己喜欢的类型,这里只是定义了一个简单的int成员变量通过这个onNotify传递给客户端。
package [email protected];
struct HalEvent {
int32_t value;
};
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 [email protected]
# 自动生成编译控制文件Android.bp - 只有mk可以不生成
hidl-gen -Landroidbp -rvendor.mediatek.hardware:vendor/mediatek/proprietary/hardware/interfaces -randroid.hidl:system/libhidl/transport [email protected]
# 自动生成编译控制文件Android.mk - 只有bp可以不生成
hidl-gen -Lmakefile -rvendor.mediatek.hardware:vendor/mediatek/proprietary/hardware/interfaces -randroid.hidl:system/libhidl/transport [email protected]
# 增加顶层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 #手动增加编辑 - 服务端注册启动入口
│ │ └── [email protected] #用于开机自启动行为
│ ├── IHelloCallback.hal # 手动定义回调接口
│ ├── IHello.hal # 手动定义接口
│ └── types.hal # 自定义数据类型
└── Android.bp # 自动生成update-makefiles.sh或手动增加编辑
在vendor分区,要起一个service来handle这个HIDL 接口,这个我们在上一节中有详细讲到,贴一下代码:
#include
#include
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: "[email protected]",
relative_install_path: "hw",
defaults: ["hidl_defaults"],
vendor: true,
init_rc: ["[email protected]"],
srcs: [
"service.cpp",
],
shared_libs: [
"liblog",
"libutils",
"libhidlbase",
"libhidltransport",
"libutils",
"[email protected]",
],
}
接下来,需要完善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
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
#include
#include
#include
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 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一次方法,还是很简单的。
在AOSP根目录执行mmm编译会生成如下文件:
# 编译执行命令
mmm vendor/mediatek/proprietary/hardware/interfaces/hello
/system/lib/[email protected]和
/vendor/lib/hw/[email protected]及
/vendor/bin/hw/[email protected]
按照惯例,还是需要写一个测试程序来验证,我们服务端的正确性。其他不表,请看如下代码:
// The file path: vendor/mediatek/proprietary/external/hello_client/hello_client.cpp
#define LOG_TAG "HelloClient"
#include
#include
#include
#include
#include
#include
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",
"[email protected]",
],
}
以上,通过以下指令, 编译后会生成在/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()的时候会出现无法获取服务问题。
通过以上,我们总共产出了如下文件:
同上一篇一样,我们需要将上述文件分别push到设备对应目录中:
adb push [email protected] /system/lib/
adb push [email protected] /vendor/lib/hw/
adb push [email protected] /system/bin
adb push hello_client /system/bin
07-28 17:36:41.757 1797 1797 D vndksupport: Loading /vendor/lib/hw/[email protected] from current namespace instead of sphal namespace.
07-28 17:36:41.760 1797 1797 I ServiceManagement: Removing namespace from process name [email protected] to [email protected].
07-28 17:36:41.762 1797 1797 I : Registration complete for [email protected]::IHello/default.
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
函数名称、变量名称和文件名应该是描述性名称;避免过度缩写。将首字母缩略词视为字词(例如,请使用 INfc,而非 INFC)。
.
├── 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
其中:
例如:
.
├─ nfc
├── 1.0
│ ├── Android.bp
│ ├── Android.mk
│ ├── default
│ │ ├── Android.bp
│ │ ├── [email protected]
│ │ ├── 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 中)。
软件包名称必须采用以下完全限定名称 (FQN) 格式(称为 PACKAGE-NAME):
PACKAGE.MODULE[.SUBMODULE[.SUBMODULE[…]]]@VERSION
其中:
导入包采用以下 3 种格式之一:
import vendor.mediatek.hardware::IHello;
本文参考链接:https://www.jianshu.com/p/ca6823b897b5