"网上有很多介绍 hidl 的文章,但没有一篇是介绍得比较全面的,都会遗漏一些小细节, 由于这样,那样的小细节,自己到处找资料,折腾了两天才把 hidl 的demo 跑起来。 以下作详细的记录,手把手教你从零创建自己的 hidl 服务。 关于什么是 hidl ,及为什么要用 hidl 的理论知识这里就 ...."

android8 添加 hidl 服务

网上有很多介绍 hidl 的文章,但没有一篇是介绍得比较全面的,都会遗漏一些小细节,
由于这样,那样的小细节,自己到处找资料,折腾了两天才把 hidl 的demo 跑起来。
以下作详细的记录,手把手教你从零创建自己的 hidl 服务。
关于什么是 hidl ,及为什么要用 hidl 的理论知识这里就不作介绍了,
可以参考官网的介绍 : hidl概览

1. 添加 hal 目录

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

mkdir -p device/kktv/common/hardware/interfaces/tvserver/1.0

2. 定义 hal 接口

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

package vendor.kktv.hardware.tvserver@1.0;

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

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

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

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

#include "TVServer.h"

namespace vendor {
namespace kktv {
namespace hardware {
namespace tvserver {
namespace V1_0 {
namespace implementation {

// Methods from ITVServer follow.
Return<void> TVServer::hello(const hidl_string& name, hello_cb _hidl_cb) {
    char buf[100];
    ::memset(buf, 0, 100);
    ::snprintf(buf, 100, "hello tvserver, %s", name.c_str());
    hidl_string result(buf);
    _hidl_cb(result);
    return Void();
}


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

//ITVServer* HIDL_FETCH_ITVServer(const char* /* name */) {
//    return new TVServer();
//}

}  // namespace implementation
}  // namespace V1_0
}  // namespace tvserver
}  // namespace hardware
}  // namespace kktv
}  // namespace vendor

4. 生成 Android.bp

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

此时目录结构是这样的

└── tvserver
    └── 1.0
        ├── default
        │   ├── Android.bp
        │   ├── TVServer.cpp
        │   └── TVServer.h
        └── ITVServer.hal

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

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

#!/bin/bash

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

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

把 update-makefiles.sh 脚本放到 device/kktv/common/hardware/interfaces 目录,然后在 android 根目录执行

./device/kktv/common/hardware/interfaces/update-makefiles.sh

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

├── 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 值

123456 vendor.kktv.hardware.tvserver@1.0::ITVServer

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

7. 更新 manifest.xml

manifest.xml 中加上

    <hal format="hidl">
        <name>vendor.kktv.hardware.tvserver</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>ITVServer</name>
            <instance>default</instance>
        </interface>
    </hal>

一般来说 product 目录下会定义自己的 manifest.xml ,如果没有的话,就得查一下编译时用的到底是哪个 manifext.xml 了。
这里的例子用的的 android 默认的 device/generic/goldfish/manifest.xml。

8. 模块编译

mmm device/kktv/common/hardware/interfaces/tvserver/1.0

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

optional_subdirs = [
    ...
    "device/*/*",
    ...
]

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

subdirs = [
    "hardware",
]

加完之后,再去

mmm device/kktv/common/hardware/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 文件:

#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::kktv::hardware::tvserver::V1_0::ITVServer;
using vendor::kktv::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.kktv.hardware.tvserver@1.0-service.rc 文件,用来自动启动服务:

service kktvserver /vendor/bin/hw/vendor.kktv.hardware.tvserver@1.0-service
    class hal
    user system
    group system

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

cc_binary {
    name: "vendor.kktv.hardware.tvserver@1.0-service",
    relative_install_path: "hw",
    proprietary: true,
    init_rc: ["vendor.kktv.hardware.tvserver@1.0-service.rc"],
    srcs: ["main.cpp"],

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

}

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

mmm device/kktv/common/hardware/interfaces/tvserver/1.0

10. 添加到 PRODUCT_PACKAGES

修改 device.mk 增加

PRODUCT_PACKAGES += \
    vendor.kktv.hardware.tvserver@1.0-service

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

11. 添加 selinux 规则

在 $(PRODUCT_DIR)/sepolicy 下新增 kktvserver.te

type kktvserver, domain;

type kktvserver_exec, exec_type, vendor_file_type, file_type;
hwbinder_use(kktvserver);
init_daemon_domain(kktvserver)
add_hwservice(kktvserver, kktvserver_hwservice)

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

修改 file_contexts,增加

/vendor/bin/hw/vendor.kktv.hardware.tvserver@1.0-service    u:object_r:kktvserver_exec:s0

修改 hwservice.te, 增加

type kktvserver_hwservice, hwservice_manager_type;

修改 hwservice_contexts.te, 增加

vendor.kktv.hardware.tvserver::ITVServer u:object_r:kktvserver_hwservice:s0

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

generic_kktv:/ # ps -A | grep tvserver
system        1384     1    8740   3880 binder_thread_read ab76cac4 S vendor.kktv.hardware.tvserver@1.0-service
generic_kktv:/ #

12. 编写 client 调用服务

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

#include <vendor/kktv/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::kktv::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

cc_binary {
    relative_install_path: "hw",
    defaults: ["hidl_defaults"],
    name: "TVServerTest",
    proprietary: true,
    srcs: ["TVServerTest.cpp"],

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

}

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

在 device.mk 中加上

PRODUCT_PACKAGES += \
    TVServerTest

然后整编译系统,运行:

generic_kktv:/vendor/bin/hw # ./TVServerTest                                                                                                          
hello tvserver, qiushao
generic_kktv:/vendor/bin/hw #

成功调用了 hidl 服务。

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

├── Android.bp
└── interfaces
    ├── Android.bp
    ├── current.txt
    ├── tvserver
    │   ├── 1.0
    │   │   ├── Android.bp
    │   │   ├── Android.mk
    │   │   ├── default
    │   │   │   ├── Android.bp
    │   │   │   ├── main.cpp
    │   │   │   ├── TVServer.cpp
    │   │   │   ├── TVServer.h
    │   │   │   └── vendor.kktv.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 服务的同学。

0     0     0     0     0    
0 回帖