Android系统开发入门-11.添加hidl服务

Android 8.0 之后,google 为了解决 Android 版本碎片化问题, 推出了 treble 架构,参考 Android Treble架构解析。核心思想就是 vendor 分区和 system 分区的隔离。把厂商的更改限制在 vendor 分区,system 分区由 google 把控。system 分区需要访问 vendor 分区的话,需要通过 hidl 的形式来访问。 网上有很多介绍 hidl 的文章,但没有一篇是介绍得比较全面的,都会遗漏一些小细节,由于这样,那样的小细节,自己到处找资料,折腾了两天才把 hidl 的demo 跑起来。以下作详细的记录,手把手教你从零创建自己的 hidl 服务。关于什么是 hidl ,及为什么要用 hidl 的理论知识这里就不作介绍了,可以参考官网的介绍 : hidl概览

1. 添加 hal 目录

网上的 demo 全部都是直接在 android/hardware/interfaces 目录下添加了,
但我觉得最好是在自己的 device 目录下添加比较好维护一点。

1
mkdir -p device/qiushao/pure/models/hidl/tvserver/1.0

2. 定义 hal 接口

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

1
2
3
4
5
package device.qiushao.pure.tvserver@1.0;

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

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

1
2
3
qiushao@qiushao-pc:~/source/android-10$ PACKAGE=device.qiushao.pure.tvserver@1.0
qiushao@qiushao-pc:~/source/android-10$ LOC=device/qiushao/pure/models/hidl/tvserver/1.0/default
qiushao@qiushao-pc:~/source/android-10$ hidl-gen -o $LOC -Lc++-impl -rdevice.qiushao.pure:device/qiushao/pure/models/hidl $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();
}

我们还需要一个进程来容纳我们的服务,创建一个 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
#include <hidl/HidlTransportSupport.h>
#include <utils/Looper.h>
#include <utils/StrongPointer.h>
#include "TVServer.h"
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using device::qiushao::pure::tvserver::V1_0::ITVServer;
using device::qiushao::pure::tvserver::V1_0::implementation::TVServer;

int main(int argc, char **argv) {
// sleep for a second, wait /data/vendor to be mounted
sleep(1);

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

android::sp<ITVServer> service = new TVServer();
android::status_t ret = service->registerAsService();

if (ret != android::NO_ERROR) {
}

joinRpcThreadpool();

return 0;
}

4. 开机自动启动服务

在 tvserver/1.0/default 目录下创建 device.qiushao.pure.tvserver@1.0-service.rc 文件:

1
2
3
4
service tvserver /vendor/bin/hw/device.qiushao.pure.tvserver@1.0-service
class hal
user root
group root

5. 生成 Android.bp

1
hidl-gen -o $LOC -Landroidbp-impl -rdevice.qiushao.pure:device/qiushao/pure/models/hidl $PACKAGE

在 Android 9.0 之后,查看自动生成的 Android.bp 文件,里面有些提示,说明自动生成的文件还需要稍作修改,我们修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cc_binary {
name: "device.qiushao.pure.tvserver@1.0-service",
init_rc: ["device.qiushao.pure.tvserver@1.0-service.rc"],
relative_install_path: "hw",
vendor: true,
srcs: [
"TVServer.cpp",
"main.cpp",
],
shared_libs: [
"libhidlbase",
"libhidltransport",
"libutils",
"device.qiushao.pure.tvserver@1.0",
],
}

其中 device.qiushao.pure.tvserver@1.0-service.rc 文件会被安装到 /vendor/etc/init/ 目录。系统启动时会自动加载这个目录下的所有 rc 文件。

此时目录结构是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models/hidl$ tree tvserver/
tvserver/
└── 1.0
├── default
│   ├── Android.bp
│   ├── main.cpp
│   ├── TVServer.cpp
│   └── TVServer.h
│   └── device.qiushao.pure.tvserver@1.0-service.rc
└── ITVServer.hal

2 directories, 6 files
qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models/hidl$

6. 自动生成 hal 接口的 Android.bp

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# Script to update Android make-files for HAL and VTS modules.

set -e

if [ -z "$ANDROID_BUILD_TOP" ]; then
echo "Missing ANDROID_BUILD_TOP env variable. Run 'lunch' first."
exit 1
fi

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

do_makefiles_update \
"device.qiushao.pure:device/qiushao/pure/models/hidl"

然后在 Android 根目录执行

1
./device/qiushao/pure/models/hidl/update-makefiles.sh

这个命令生成了 device/qiushao/pure/models/hidl/tvserver/1.0/Android.bp 这个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// This file is autogenerated by hidl-gen -Landroidbp.

hidl_interface {
name: "device.qiushao.pure.tvserver@1.0",
root: "device.qiushao.pure",
product_specific: true,
srcs: [
"ITVServer.hal",
],
interfaces: [
"android.hidl.base@1.0",
],
gen_java: true,
}

我们需要在这个文件最前面添加以下配置才行,不然会编译不过:

1
2
3
4
hidl_package_root {
name: "device.qiushao.pure",
path: "device/qiushao/pure/models/hidl",
}

目前目录结构是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models/hidl$ tree 
.
├── tvserver
│   └── 1.0
│   ├── Android.bp
│   ├── default
│   │   ├── Android.bp
│   │   ├── main.cpp
│   │   ├── TVServer.cpp
│   │   └── TVServer.h
│   │   └── device.qiushao.pure.tvserver@1.0-service.rc
│   └── ITVServer.hal
└── update-makefiles.sh

3 directories, 9 files
qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models/hidl$

7. 更新 current.txt

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

1
123456 device.qiushao.pure.tvserver@1.0::ITVServer

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

8. 模块编译

1
mmm device/qiushao/pure/models/hidl/tvserver/1.0

编译完成后,可以发现 $(TARGET_OUT)/vendor/bin/hw 目录下生成了我们的服务 device.qiushao.pure.tvserver@1.0-service

9. 更新 manifest.xml

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

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

pure/product_copy_files.mk 中加上:

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

10. 添加到 PRODUCT_PACKAGES

修改 device.mk 增加

1
2
PRODUCT_PACKAGES += \
device.qiushao.pure.tvserver@1.0-service

11. 添加 selinux 规则

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

1
BOARD_SEPOLICY_DIRS += device/qiushao/pure/sepolicy

然后我们就可以在 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 {map read open getattr};
allow tvserver system_file:dir {read open getattr search};

新增 file_contexts

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

新增 hwservice.te

1
type tvserver_hwservice, hwservice_manager_type;

新增 hwservice_contexts

1
device.qiushao.pure.tvserver::ITVServer u:object_r:tvserver_hwservice:s0

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

1
2
3
130|pure:/ # ps -A | grep qiushao
root 1648 1 15420 3864 futex_wait_queue_me 0 S device.qiushao.pure.tvserver@1.0-service
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
24
25
#include <device/qiushao/pure/tvserver/1.0/ITVServer.h>
#include <hidl/Status.h>
#include <utils/misc.h>
#include <hidl/HidlSupport.h>
#include <stdio.h>
#include <string>

using namespace std;

using ::android::hardware::hidl_string;
using ::android::sp;
using device::qiushao::pure::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
cc_binary {
name: "TVServerTest",
vendor: true,
srcs: ["TVServerTest.cpp"],

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

}

在 device.mk 中加上

1
2
PRODUCT_PACKAGES += \
TVServerTest

然后整编译系统,运行:

1
2
3
pure:/ # TVServerTest                              
hello qiushao from TVServer
pure:/ #

成功调用了 hidl 服务。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models/hidl$ tree
.
├── current.txt
├── tvserver
│   └── 1.0
│   ├── Android.bp
│   ├── default
│   │   ├── Android.bp
│   │   ├── device.qiushao.pure.tvserver@1.0-service.rc
│   │   ├── main.cpp
│   │   ├── TVServer.cpp
│   │   └── TVServer.h
│   ├── ITVServer.hal
│   └── test
│   ├── Android.bp
│   └── TVServerTest.cpp
└── update-makefiles.sh

4 directories, 11 files
qiushao@qiushao-pc:~/source/android-10/device/qiushao/pure/models/hidl$

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

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