Android系统开发--13.添加hidl系统服务

Android 8.0 之后,google 大力推行 treble 架构了,我们也要顺势而为,使用 hdil 来定义厂商的服务。网上有很多介绍 hidl 的文章,但没有一篇是介绍得比较全面的,都会遗漏一些小细节,由于这样,那样的小细节,自己到处找资料,折腾了两天才把 hidl 的demo 跑起来。以下作详细的记录,手把手教你从零创建自己的 hidl 服务。关于什么是 hidl ,及为什么要用 hidl 的理论知识这里就不作介绍了,可以参考官网的介绍 : hidl概览

1. 添加 hal 目录

网上的 demo 全部都是直接在 android/hardware/interfaces 目录下添加了,
但我觉得最好是在自己的 product 目录下添加比较好维护一点。但在 device/product 添加的时候遇到了一点问题,定义的模块系统找不到。
原因及解决办法后面再说。

1
mkdir -p device/qiushao/common/hidl/interfaces/tvserver/1.0

2. 定义 hal 接口

在 device/qiushao/common/hidl/interfaces/tvserver/1.0 目录下创建文件 ITVServer.hal

1
2
3
4
5
package vendor.qiushao.hardware.tvserver@1.0;

interface ITVServer {
hello(string name) generates (string result);
};

3. 根据 .hal 自动生成 cpp 实现

1
2
3
PACKAGE=vendor.qiushao.hardware.tvserver@1.0
LOC=device/qiushao/common/hidl/interfaces/tvserver/1.0/default
hidl-gen -o $LOC -Lc++-impl -rvendor.qiushao.hardware:device/qiushao/common/hidl/interfaces $PACKAGE

执行完以上命令之后在 default 目录下生成了 TVServer.h 和 TVServer.cpp 这两个文件
我们需要对 TVServer.cpp 实现进行完善,只需要对我们定义的 hello 接口进行实现就行了,修改如下:

1
2
3
4
5
6
7
8
Return<void> TVServer::hello(const hidl_string& name, hello_cb _hidl_cb) {
char buf[100];
::memset(buf, 0, 100);
::snprintf(buf, 100, "hello %s from TVServer", name.c_str());
hidl_string result(buf);
_hidl_cb(result);
return Void();
}

4. 生成 Android.bp

1
hidl-gen -o $LOC -Landroidbp-impl -rvendor.qiushao.hardware:device/qiushao/common/hidl/interfaces $PACKAGE

此时目录结构是这样的

1
2
3
4
5
6
7
└── tvserver
└── 1.0
├── default
│   ├── Android.bp
│   ├── TVServer.cpp
│   └── TVServer.h
└── ITVServer.hal

5. 自动生成整个模块的 Android.mk Android.bp

这里需要用到 update-makefiles.sh 这个脚本,我们可以从 $(TOP)/hardware/interfaces 里面 copy 一份过来放到 device/qiushao/common/hidl/interfaces 目录,稍作修改即可

1
2
3
4
5
6
7
8
#!/bin/bash

source $ANDROID_BUILD_TOP/system/tools/hidl/update-makefiles-helper.sh

do_makefiles_update \
"vendor.qiushao.hardware:device/qiushao/common/hidl/interfaces" \
"android.hardware:hardware/interfaces" \
"android.hidl:system/libhidl/transport"

然后在 android 根目录执行

1
./device/qiushao/common/hidl/interfaces/update-makefiles.sh

执行完后,目录结构是这样的

1
2
3
4
5
6
7
8
9
10
11
├── tvserver
│   ├── 1.0
│   │   ├── Android.bp
│   │   ├── Android.mk
│   │   ├── default
│   │   │   ├── Android.bp
│   │   │   ├── TVServer.cpp
│   │   │   └── TVServer.h
│   │   └── ITVServer.hal
│   └── Android.bp
└── update-makefiles.sh

自动生成了几个 Android.bp, Android.mk 文件

6. 更新 current.txt

current.txt 记录了所有 hal 接口的 hash 值,接口有变化时,同时需要更新 current.txt 中的 hash 值
在 interfaces 目录下新建 current.txt 文件,随便写个 hash 值

1
123456 vendor.qiushao.hardware.tvserver@1.0::ITVServer

再执行一遍 update-makefiles.sh,这个时候就会发现提示 hash 值不正确了,同时会给出正确的 hash 值,我们把正确的 hash 值替换到 current.txt 即可。

7. 更新 manifest.xml

复制 device/generic/goldfish/manifest.xml 到 device/qiushao/generic_pure/manifest.xml
manifest.xml 中加上

1
2
3
4
5
6
7
8
9
<hal format="hidl">
<name>vendor.qiushao.hardware.tvserver</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>ITVServer</name>
<instance>default</instance>
</interface>
</hal>

generic_pure/product_copy_files.mk 中加上:

1
2
PRODUCT_COPY_FILES += \
device/qiushao/generic_pure/manifest.xml:vendor/manifest.xml

8. 模块编译

1
mmm device/qiushao/common/hidl/interfaces/tvserver/1.0

执行完这个命令后,我们到 $(TARGET_OUT)/vendor/lib/hw 目录下发现并没有我们定义的模块。
而且把 TVServer.cpp 故意修改得有语法错误了,也照样编译过了,说明我们的模块压根没有编译到!
折腾了一整天之后,才找到了原因:Android.bp 是不会递归查找的!!!
这就是文章开头第一步提到的在 device/product 下面添加 hidl 的坑
从 $(TOP)/Android.bp 里面看:

1
2
3
4
5
optional_subdirs = [
...
"device/*/*",
...
]

可见系统只会找 device 目录的两层目录而已。我们的 hal 接口已经在 device 目录下的四五层目录了,所以系统并没有找到我们的 hal 模块的 Android.bp。
解决的方法很简单,从 device/qiushao/common 目录下开始增加 Android.bp 文件, 指定下级目录,一直加到 interfaces 目录。

1
2
3
subdirs = [
"hidl",
]

加完之后,再去

1
mmm device/qiushao/common/hidl/interfaces/tvserver/1.0

可以发现 $(TARGET_OUT)/vendor/lib/hw 目录下生成了我们定义的 so 库 vendor.kktv.hardware.tvserver@1.0-impl.so

9. 添加 main.cpp, init.rc

前面我们只是编译出了各种库而已,服务进程还是需要手动写的。
在 default 目录下增加 main.cpp 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>

#include "TVServer.h"

using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using vendor::qiushao::hardware::tvserver::V1_0::ITVServer;
using vendor::qiushao::hardware::tvserver::V1_0::implementation::TVServer;

int main(int /*argc*/, char** argv) {
android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
LOG(INFO) << "start TVServer...";

configureRpcThreadpool(4, true /* callerWillJoin */);

// Setup hwbinder service
android::sp<ITVServer> service = new TVServer();
CHECK_EQ(service->registerAsService(), android::NO_ERROR)
<< "Failed to register TVServer HAL";

joinRpcThreadpool();

LOG(INFO) << "TVServer Hal is terminating...";
return 0;
}

在 default 目录下再增加 vendor.qiushao.hardware.tvserver@1.0-service.rc 文件,用来自动启动服务:

1
2
3
4
service tvserver /vendor/bin/hw/vendor.qiushao.hardware.tvserver@1.0-service
class hal
user system
group system

修改 default/Android.bp,增加以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cc_binary {
name: "vendor.qiushao.hardware.tvserver@1.0-service",
relative_install_path: "hw",
proprietary: true,
init_rc: ["vendor.qiushao.hardware.tvserver@1.0-service.rc"],
srcs: ["main.cpp"],

shared_libs: [
"liblog",
"libcutils",
"libdl",
"libbase",
"libutils",
"libhardware",
"libhidlbase",
"libhidltransport",
"vendor.qiushao.hardware.tvserver@1.0",
"vendor.qiushao.hardware.tvserver@1.0-impl",
],

}

加完之后,再去模块编译一下,确保编译正确。

1
mmm device/qiushao/common/hidl/interfaces/tvserver/1.0

10. 添加到 PRODUCT_PACKAGES

修改 device.mk 增加

1
2
PRODUCT_PACKAGES += \
vendor.qiushao.hardware.tvserver@1.0-service

然后去编译整个系统, 运行。
发现我们的服务并没有跑起来,看 log 也没有任何相关的错误信息, 一脸蒙逼。
后面想添加服务肯定是要加 selinux 规则的,先加上再说,加上之后,就开始发现selinux 权限错误提示了,慢慢增加权限就行。
关于 selinux 的具体语法规则,这里就不作详细介绍了,因为我也是一知半解。

11. 添加 selinux 规则

Android8.1 系统原生的 selinux 规则在 system/sepolicy 目录下,
我们可以直接修改里面的规则,但不推荐这么做,最好是在 device 目录下添加厂商自己的规则。
在 BoardConfig.mk 里面添加:

1
BOARD_SEPOLICY_DIRS += device/qiushao/generic_pure/sepolicy

然后我们就可以在 generic_pure/sepolicy 目录下添加我们的 selinux 规则了。

新增 tvserver.te

1
2
3
4
5
6
7
8
9
type tvserver, domain;

type tvserver_exec, exec_type, vendor_file_type, file_type;
hwbinder_use(tvserver);
init_daemon_domain(tvserver)
add_hwservice(tvserver, tvserver_hwservice)

allow tvserver hwservicemanager_prop:file {read open getattr};
allow tvserver system_file:dir {read open getattr search};

新增 file_contexts

1
/vendor/bin/hw/vendor.qiushao.hardware.tvserver@1.0-service    u:object_r:tvserver_exec:s0

新增 hwservice.te

1
type tvserver_hwservice, hwservice_manager_type;

新增 hwservice_contexts.te

1
vendor.qiushao.hardware.tvserver::ITVServer u:object_r:tvserver_hwservice:s0

然后去编译整个系统, 运行,嘿,我们的服务跑起来了:

1
2
3
generic_pure:/ # ps -A | grep tvserver
system 1384 1 8740 3880 binder_thread_read ab76cac4 S vendor.qiushao.hardware.tvserver@1.0-service
generic_pure:/ #

12. 编写 client 调用服务

在 1.0 目录下增加 test 目录,新建 TVServerTest.cpp 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <vendor/qiushao/hardware/tvserver/1.0/ITVServer.h>
#include <hidl/Status.h>
#include <hidl/LegacySupport.h>
#include <utils/misc.h>
#include <hidl/HidlSupport.h>
#include <stdio.h>

using ::android::hardware::hidl_string;
using ::android::sp;
using vendor::qiushao::hardware::tvserver::V1_0::ITVServer;

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

service->hello("qiushao", [&](hidl_string result){
printf("%s\n", result.c_str());
});
return 0;
}

新建 Android.bp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cc_binary {
relative_install_path: "hw",
defaults: ["hidl_defaults"],
name: "TVServerTest",
proprietary: true,
srcs: ["TVServerTest.cpp"],

shared_libs: [
"liblog",
"libhardware",
"libhidlbase",
"libhidltransport",
"libutils",
"vendor.qiushao.hardware.tvserver@1.0",
],

}

再执行一遍 update-makefiles.sh, 脚本会自动更新 device/qiushao/common/hardware/interfaces/tvserver 目录下的 Android.bp 文件,
把 test 目录加上了。

在 device.mk 中加上

1
2
PRODUCT_PACKAGES += \
TVServerTest

然后整编译系统,运行:

1
2
3
generic_pure:/vendor/bin/hw # ./TVServerTest                                                                                                          
hello qiushao from TVServer
generic_pure:/vendor/bin/hw #

成功调用了 hidl 服务。

最终代码目录结构是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
├── Android.bp
└── interfaces
├── Android.bp
├── current.txt
├── tvserver
│   ├── 1.0
│   │   ├── Android.bp
│   │   ├── Android.mk
│   │   ├── default
│   │   │   ├── Android.bp
│   │   │   ├── main.cpp
│   │   │   ├── TVServer.cpp
│   │   │   ├── TVServer.h
│   │   │   └── vendor.qiushao.hardware.tvserver@1.0-service.rc
│   │   ├── ITVServer.hal
│   │   └── test
│   │   ├── Android.bp
│   │   └── TVServerTest.cpp
│   └── Android.bp
└── update-makefiles.sh

经过了这么多的步骤终于完成了一个 hidl 的 hello world。
以后我们只要在这个框架上慢慢添加功能就行了。

网上大部分文章会忽略掉 current.txt, manifest.xml, selinux 及在 device/product 目录下添加模块的这些问题,导致自己跟着那些文章实现的时候,总是遇到各种问题,希望这篇记录能够帮助到想创建 hidl 服务的同学。