Android 8.1 从零开始写 HAL – (2) 实现 HAL 主体
注意:本文基于 Android 8.1 进行分析
Qidi 2020.07.18 (Markdown & Haroopad)
【前言】
通过上一篇文章《Android 8.1 从零开始写 HAL – (1) 定义接口》的努力,我们定义好了 demoComponent HAL 的接口和参数,也了解到编译时会自动产生 Binder 框架代码。通过 Binder 机制,经过 demoComponent HAL 的 Bp 和 Bn 端,用户进程就可以调用到我们的 demoService 了。
在打通 Bp/Bn 通路前,我们有必要让 demoComponent HAL 的主体 —— demoService —— 先鲜活起来。
一、配置 HAL
因为不同的产品可能使用不同的外设,所以每个产品都有自己的资源清单 manifest.xml
,位于目录 /device/<CompanyName>/<PlatformName>/<ProductName>/
下。清单中会列出该款产品支持的所有外设、服务和它们的 HAL 类型。
我们也应该把正在创建的 demoComponent HAL 添加进这个清单里。以我用的 Intel 平台为例,产品代号就不说了,manifest.xml
位于 /device/harman/broxton/XXXX/
目录下。添加完成后看起来类似下面这个样子:
<manifest version="1.0" type="device">
......
<hal format="hidl">
<name>vendor.harman.hardware.demoComponent.demoService</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IDemoServiceDef</name>
<instance>default</instance>
</interface>
</hal>
......
<sepolicy>
<version>27.0</version>
</sepolicy>
</manifest>
其中 <hal format="hidl">
表示 HAL 使用 HIDL 描述; <name>
的值可以看出来就是接口描述文件所在的位置,但中间少了个 “.interfaces” ;<transport>
表示 HAL 实现所依赖的 binder 类型; <version>
表示 HAL 版本,回忆上一篇文章,我们在编写接口描述文件时,文件存放路径里也有个 HAL 版本号,这两个版本号要一致; <interface>
节点用来标明 HAL 接口,下属的 <name>
指定了 HAL 的接口描述文件为 IDemoServiceDef.hal
, <instance>
指定了 HAL 的实现位于 default/
目录下。
default/
目录需要我们自己创建,位于 /vendor/<CompanyName>/hardware/interfaces/<ComponentName>/<SubComponentName>/<VersionCode>/
。 还是以我用的平台为例,完整路径为 /vendor/harman/hardware/interfaces/demoComponent/demoService/1.0/default
。
二、实现 HAL 主体
接下来我们要实现 demoComponent HAL 的主体。 因为 Binder 化后的 HAL 是以服务进程的形式运行在 Android native 层的,所以我给这个主体取名为 demoService。在 default/
目录下新建 DemoServiceImpl.h
和 DemoServiceImpl.cpp
。
这之后要做的事大家就很熟悉了 —— 在 DemoServiceImpl.h
里引用必要的头文件、声明类和方法:
/*****************************************************************************
* Copyright (C) 2020 Qidi.Huang
*
* Brief:
* Declaration of demo service interfaces.
*
* Author: huang_qi_di@hotmail.com
*****************************************************************************/
#pragma once
#include <hardware/hardware.h>
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <vendor/harman/hardware/demoComponent/demoService/1.0/IDemoServiceDef.h>
#include <vendor/harman/hardware/demoComponent/demoService/1.0/IDemoCallback.h>
#include "DemoServiceBinderInterface.h"
using namespace android;
using namespace vendor::harman::hardware::demoComponent::demoService::V1_0;
class DemoServiceImpl : public IDemoServiceDef {
public:
DemoServiceImpl();
~DemoServiceImpl() {}
virtual ::android::hardware::Return<int32_t> setStatus(const DemoData& sta) override;
virtual ::android::hardware::Return<int32_t> registerCallback(const ::android::sp<IDemoCallback>& cb) override;
virtual ::android::hardware::Return<int32_t> unregisterCallback(const ::android::sp<IDemoCallback>& cb) override;
private:
android::sp<IDemoCallback> callback = nullptr;
};
DemoServiceImpl
类继承自 IDemoServiceDef
类,并对接口进行覆写,同时声明了一个 IDemoCallback
指针用来保存回调函数。
大家可能会困惑IDemoServiceDef
类和接口声明是哪里来的?我们可以通过名字推测它是由 IDemoServiceDef.hal
生成的。实际上和推测一样,编译阶段自动生成的文件会被放在 /out/soong/.intermediates/vendor/harman/hardware/interfaces/demoComponent/demoService/1.0/
目录,头文件和源文件可以分别在 vendor.harman.hardware.demoComponent.demoService@1.0_genc++_headers
和 vendor.harman.hardware.demoComponent.demoService@1.0_genc++
中找到。 在 DemoServiceImpl.h
里需要 #include
引用这些自动生成的头文件。
下方展示的是自动生成的头文件 IDemoServiceDef.h
的部分代码:
#ifndef HIDL_GENERATED_VENDOR_HARMAN_HARDWARE_DEMOCOMPONENT_DEMOSERVICE_V1_0_IDEMOSERVICEDEF_H
#define HIDL_GENERATED_VENDOR_HARMAN_HARDWARE_DEMOCOMPONENT_DEMOSERVICE_V1_0_IDEMOSERVICEDEF_H
#include <android/hidl/base/1.0/IBase.h>
#include <vendor/harman/hardware/demoComponent/demoService/1.0/IDemoCallback.h>
#include <vendor/harman/hardware/demoComponent/demoService/1.0/types.h>
#include <android/hidl/manager/1.0/IServiceNotification.h>
#include <hidl/HidlSupport.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <utils/NativeHandle.h>
#include <utils/misc.h>
namespace vendor {
namespace harman {
namespace hardware {
namespace demoComponent {
namespace demoService {
namespace V1_0 {
struct IDemoServiceDef : public ::android::hidl::base::V1_0::IBase {
virtual bool isRemote() const override { return false; }
virtual ::android::hardware::Return<int32_t> setStatus(const DemoData& data) = 0;
virtual ::android::hardware::Return<int32_t> registerCallback(const ::android::sp<IDemoCallback>& cb) = 0;
virtual ::android::hardware::Return<int32_t> unregisterCallback(const ::android::sp<IDemoCallback>& cb) = 0;
// ....省略
};
std::string toString(const ::android::sp<IDemoServiceDef>&);
} // namespace V1_0
} // namespace demoService
} // namespace demoComponent
} // namespace hardware
} // namespace harman
} // namespace vendor
#endif // HIDL_GENERATED_VENDOR_HARMAN_HARDWARE_DEMOCOMPONENT_DEMOSERVICE_V1_0_IDEMOSERVICEDEF_H
然后在 DemoServiceImpl.cpp
中实现各方法:
/*****************************************************************************
* Copyright (C) 2020 Qidi.Huang
*
* Brief:
* Implementation of demo service interfaces.
*
* Author: huang_qi_di@hotmail.com
*****************************************************************************/
#include "DemoServiceImpl.h"
using namespace vendor::harman::hardware::demoComponent::demoService::V1_0;
DemoServiceImpl::DemoServiceImpl() {
}
::android::hardware::Return<int32_t> DemoServiceImpl::setStatus(const DemoData& sta) {
ALOGI("DemoServiceImpl setStatus");
// 省略设置状态的代码
ALOGI("DemoService invokes callback");
// 在最后执行回调函数发送通知
callback->onCallbackEvent(sta);
return 0;
}
::android::hardware::Return<int32_t> DemoServiceImpl::registerCallback(const ::android::sp<IDemoCallback>& cb) {
ALOGI("DemoServiceImpl registerCallback");
// 保存回调函数
callback = cb;
ALOGI("DemoService callback function saved");
return 0;
}
::android::hardware::Return<int32_t> DemoServiceImpl::unregisterCallback(const ::android::sp<IDemoCallback>& cb) {
ALOGI("DemoServiceImpl unregisterCallback");
if (callback == cb) {
ALOGI("DemoService callback function cleared.");
// 清空回调函数
callback = nullptr;
} else {
ALOGI("DemoService callback function mismatch, uncleared.");
}
return 0;
}
这 3 个接口的实现写得很简单,直接看代码注释就可以,无需赘言。
三、将 HAL 注册为 Binder 服务
因为 Binder 化的 HAL 以独立本地进程的形式运行,所以必定需要 main()
函数作为进程启动入口。我们当然可以把 main()
写在 DemoServiceImpl.cpp
中,但为了与接口实现进行区分,我在 default/
目录下新建源文件 DemoService.cpp
,在该文件中实现且仅实现 main() 函数。如下:
/*****************************************************************************
* Copyright (C) 2020 Qidi.Huang
*
* Brief:
* Entry of demo service.
*
* Author: huang_qi_di@hotmail.com
*****************************************************************************/
#define LOG_TAG "vendor.harman.demoComponent.demoService@1.0-service"
#include <android/log.h>
#include <binder/ProcessState.h>
#include <hidl/LegacySupport.h>
#include "DemoServiceImpl.h"
int main(int /* argc */, char* /* argv */ []) {
android::ProcessState::initWithDriver("/dev/hwbinder"); // 初始化 Binder 驱动
auto service = std::make_unique<DemoServiceImpl>(); // 构造 DemoServiceImpl 实例
android::hardware::configureRpcThreadpool(4, true /* callerWillJoin */);
ALOGI("DemoService registerAsService");
android::status_t status = service->registerAsService(); // 注册为 Binder 服务
if (status != android::OK) {
ALOGE("Unable to register DemoService (%d)", status);
return 1;
}
android::hardware::joinRpcThreadpool();
return 1;
}
在 main()
函数中主要干了些事:使用设备节点 /dev/hwbinder
初始化 Binder 驱动,并以单例(Singleton)的方式将 DemoServiceImpl
类的一个实例注册为 Binder 服务。
如果你希望 HAL 进程也能与其它本地进程交互,那么在初始化驱动的时候应该使用 /dev/vndbinder
。
【结语】
如果说用户端进程相当于一条公交路线的始发站,那么 demoComponent 的 demoService 就相当于这条公交路线的终点站。汽车要从始发站开到终点站,还需要一条完好的公路,这条公路就是 demoComponent Bp/Bn 端实现。
下一篇文章 《Android 8.1 从零开始写 HAL – (3) 实现 Bp、Bn 端》要说明的就是 demoComponent BpBn 端的实现方式。