Android 自定义HAL开发篇之 HIDL篇——从入门到实战(上)

Android 自定义 HAL 开发篇之 HIDL 篇 ------ 从入门到实战 (上)

本文基于 Android 12,从零带你实现一个完整的自定义 HIDL HAL:从 .hal 接口定义、代码生成、服务注册、SELinux 配置,到 Java/Framework 层调用,再到回调崩溃、多客户端订阅等真实踩坑。

全文不堆 API 文档,而是按"基础版 → 踩坑 → 修复 → 进阶 "四阶段迭代一个 Hello HAL,让你每写一段代码都清楚它在解决什么问题。

📌 关于版本 :HIDL 自 Android 13 起已进入冻结状态(不再接受新接口),新 HAL 官方推荐走 AIDL。本文面向 Android 12 及以下老平台维护、遗留 HIDL 接口理解、Treble 架构入门。后续我会另开 AIDL 篇,并把本文的演进路线更新到 Android 13/14。


目录

  • [前言:为什么还要学 HIDL](#前言:为什么还要学 HIDL "#%E5%89%8D%E8%A8%80%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%98%E8%A6%81%E5%AD%A6-hidl")
  • [第 1 章 搭骨架:让 HAL 跑起来](#第 1 章 搭骨架:让 HAL 跑起来 "#%E7%AC%AC-1-%E7%AB%A0-%E6%90%AD%E9%AA%A8%E6%9E%B6%E8%AE%A9-hal-%E8%B7%91%E8%B5%B7%E6%9D%A5")
  • [第 2 章 SELinux 配置](#第 2 章 SELinux 配置 "#%E7%AC%AC-2-%E7%AB%A0-selinux-%E9%85%8D%E7%BD%AE")
  • [第 3 章 加入回调:能推送了,然后崩了](#第 3 章 加入回调:能推送了,然后崩了 "#%E7%AC%AC-3-%E7%AB%A0-%E5%8A%A0%E5%85%A5%E5%9B%9E%E8%B0%83%E8%83%BD%E6%8E%A8%E9%80%81%E4%BA%86%E7%84%B6%E5%90%8E%E5%B4%A9%E4%BA%86")
  • [第 4 章 修复崩溃:死亡通知两条防线](#第 4 章 修复崩溃:死亡通知两条防线 "#%E7%AC%AC-4-%E7%AB%A0-%E4%BF%AE%E5%A4%8D%E5%B4%A9%E6%BA%83%E6%AD%BB%E4%BA%A1%E9%80%9A%E7%9F%A5%E4%B8%A4%E6%9D%A1%E9%98%B2%E7%BA%BF")
  • [第 5 章 进阶:多客户端订阅](#第 5 章 进阶:多客户端订阅 "#%E7%AC%AC-5-%E7%AB%A0-%E8%BF%9B%E9%98%B6%E5%A4%9A%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%AE%A2%E9%98%85")

前言:为什么还要学 HIDL

一句话定位

HIDL(HAL Interface Definition Language) 是 Google 在 Android 8.0(Project Treble)引入的接口描述语言,用来把"Framework ↔ HAL"之间的调用从"直接函数调用"变成"跨进程 Binder 调用",从而让系统框架和厂商 HAL 可以独立编译、独立升级

一句话:HIDL 是给 HAL 用的 Binder 接口定义语言。

Project Treble 与 HAL 架构

在 Treble 之前,Framework 和 HAL 耦合在一个进程里,升级 Framework 往往要连带重编 HAL,设备厂商升级成本极高。Treble 把 HAL 拆成独立进程,中间用 HIDL(后来是 AIDL)做契约:

scss 复制代码
┌──────────────┐      HIDL / AIDL      ┌──────────────────┐
│  Framework   │  ───────────────────▶  │  HAL Service     │
│ (system_server│       hwbinder        │ (vendor 进程)     │
│  / App)      │  ◀───────────────────  │ cc_binary        │
└──────────────┘                        │  + cc_library    │
                                        └──────────────────┘
  • cc_binary :真正的 HAL 服务进程(如 vendor.custom.hello_hidl@1.0-service),开机由 init 启动,向 hwservicemanager 注册自己。
  • cc_library_shared / cc_library_static :HAL 接口的实现库(@1.0-impl),里面是硬件操作的实际代码。

本文走"纯绑定模式(Passthrough 的一种)"------实现库以 static_libs 形式被 service 进程静态链入,结构最简单,适合入门。

为什么 2026 年还学 HIDL

你需要准备什么

  • 一套能编译的 AOSP 源码(Android 12,本文基于 Rockchip 平台,其他平台路径类似)
  • 基本的 AOSP 编译流程(source build/envsetup.shlunchmake
  • 一点点 C++ 和 Binder 基础
  • 一台能跑的设备或模拟器

全文地图:四阶段迭代

我不会一上来就甩一坨完整代码,而是分四个阶段逐步把 Hello HAL 喂大:

阶段 实现版本 达成目标 对应章节
① 基础版 版本 A 同步调用 App→HAL 全通 第 1 章
② 踩坑版 版本 B 能推送回调,但 App 退出 HAL 崩溃 第 3 章
③ 修复版 版本 C 死亡通知修好崩溃,单客户端稳定 第 4 章
④ 进阶版 版本 D map<pid_t> 支持多客户端订阅 第 5 章

每一阶段的代码都是上一阶段的增量,到第 6 章汇总成完整版。准备好就开干。


第 1 章 搭骨架:让 HAL 跑起来

本章目标 :定义一个只有三个同步方法的 Hello HAL,从 .hal 到服务注册、编译运行,跑通"App 调 calculate_plus → HAL 返回结果"的完整链路。

本章不涉及回调、不涉及订阅------那俩是后面几章的坑主。先把骨架立住。

本章我们的 HAL 接口长这样:

hidl 复制代码
interface IHello {
    calculate_plus(uint32_t a, uint32_t b) generates (uint32_t total);  // 加法
    write(string name) generates (uint32_t result);                      // 写一个字符串
    read() generates (string name);                                      // 读回那个字符串
};

1.1 创建目录与定义 .hal 接口

HIDL 接口文件放在 vendor/custom/hardware/interfaces/ 下,按"接口名/版本号"组织。

bash 复制代码
mkdir -p vendor/custom/hardware/interfaces/hello_hidl/1.0
cd vendor/custom/hardware/interfaces/hello_hidl/1.0
touch IHello.hal

IHello.hal

hidl 复制代码
// 定义包名,最后跟一个版本号
package vendor.custom.hello_hidl@1.0;

// 定义 HAL 服务对外提供的接口
interface IHello {
    // 计算加法,generates 后面跟的是返回类型
    calculate_plus(uint32_t a, uint32_t b) generates (uint32_t total);

    // 写入一个字符串(模拟"写驱动")
    write(string name) generates (uint32_t result);

    // 读回刚才写入的字符串(模拟"读驱动")
    read() generates (string name);
};

几点说明:

  • package 名格式是 厂商.模块@版本,这里 vendor.custom 是我们的包根,hello_hidl 是模块名,@1.0 是版本。
  • HIDL 的返回值用 generates 声明,和普通函数的 return 不太一样,这是 HIDL 的语法特点(它把"返回"也当成一种同步回调来处理)。
  • 本章没有 types.hal、没有 IHelloCallback.hal------基础版用不到自定义结构和回调,保持最简。

1.2 创建 update-makefiles.sh

HIDL 的 Android.bp 不是手写的,而是由 hidl-gen 工具根据 .hal 自动生成。update-makefiles.sh 就是批量调用 hidl-gen 生成各接口 Android.bp 的脚本。其实这块如果你们拿到源码,源码的hardware/interfaces下就有

vendor/custom/hardware/interfaces/ 下创建:

bash 复制代码
cd vendor/custom/hardware/interfaces/
touch update-makefiles.sh

update-makefiles.sh

bash 复制代码
#!/bin/bash

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

do_makefiles_update \
    "vendor.custom:vendor/custom/hardware/interfaces" \
    "android.hardware:hardware/interfaces" \
    "android.hidl:system/libhidl/transport"

do_makefiles_update 的参数格式是 包根:对应路径。我们声明了三个包根:自己的 vendor.custom、系统的 android.hardwareandroid.hidl

确保已经 source build/envsetup.sh && lunch 过(否则 $ANDROID_BUILD_TOP 为空),然后执行:

bash 复制代码
chmod +x update-makefiles.sh
./update-makefiles.sh

1.3 根目录的 Android.bpcurrent.txt

执行完脚本后,1.0/ 目录下会自动生成一个 Android.bp。但还需要在 vendor/custom/hardware/interfaces/ 根目录补两个东西。

Android.bp(声明包根):

bash 复制代码
cd vendor/custom/hardware/interfaces/
touch Android.bp
bp 复制代码
hidl_package_root {
    name: "vendor.custom",
    use_current: true,
}

hidl_package_root 告诉编译系统:vendor.custom 这个包根对应 vendor/custom/hardware/interfaces/ 目录。

current.txt(接口哈希登记,用于版本兼容性校验):

bash 复制代码
touch current.txt

此时 current.txt 先留空,1.9 节会用 hidl-gen -L hash 往里填哈希。

执行 update-makefiles.sh 后,1.0/ 下自动生成的 Android.bp 长这样:

hello_hidl/1.0/Android.bp

bp 复制代码
// This file is autogenerated by hidl-gen -Landroidbp.
hidl_interface {
    name: "vendor.custom.hello_hidl@1.0",
    root: "vendor.custom",
    system_ext_specific: true,
    srcs: [
        "IHello.hal",
    ],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    gen_java: true,
}

注意 gen_java: true------它会顺带生成 Java 绑定,第 6 章 App 调用就靠它。

先单独编一下这个接口库,确认 .hal 没写错:

bash 复制代码
mmm vendor/custom/hardware/interfaces/hello_hidl/1.0/

编译通过,说明接口定义没问题。

1.4 用 hidl-gen 生成 C++ 实现骨架

接口库只是"契约",真正的硬件逻辑要我们手写。hidl-gen 能帮我们生成 C++ 实现的骨架文件(.h / .cpp),我们往里填业务即可。

先建 default 目录(注意拼写,原文档里写成 defualt 是笔误):

bash 复制代码
mkdir -p vendor/custom/hardware/interfaces/hello_hidl/1.0/default

设置两个变量方便复用:

bash 复制代码
PACKAGE=vendor.custom.hello_hidl@1.0
LOC=vendor/custom/hardware/interfaces/hello_hidl/1.0/default

① 生成 C++ 实现骨架

bash 复制代码
hidl-gen -o $LOC -L c++-impl \
    -r vendor.custom:vendor/custom/hardware/interfaces/ \
    -r android.hidl:system/libhidl/transport \
    $PACKAGE

这会生成 Hello.hHello.cpp(里面是空实现)。

② 生成 impl 的 Android.bp

bash 复制代码
hidl-gen -o $LOC -L androidbp-impl \
    -r vendor.custom:vendor/custom/hardware/interfaces/ \
    -r android.hidl:system/libhidl/transport \
    $PACKAGE

此时目录结构:

bash 复制代码
vendor/custom/hardware/interfaces/
├── Android.bp              # 包根声明
├── current.txt
├── update-makefiles.sh
└── hello_hidl/
    └── 1.0/
        ├── Android.bp       # 自动生成的接口库
        ├── IHello.hal
        └── default/
            ├── Android.bp   # 自动生成的 impl
            ├── Hello.h
            └── Hello.cpp

1.5 编写 service 主程序

Hello.h/.cpp 是实现,还需要一个 main() 把它跑起来并注册到 hwservicemanager。创建 HelloService.cpp

bash 复制代码
cd vendor/custom/hardware/interfaces/hello_hidl/1.0/default
touch HelloService.cpp

HelloService.cpp

cpp 复制代码
#define LOG_TAG "vendor.custom.hello_hidl@1.0-service"

#include <hidl/HidlTransportSupport.h>  // configureRpcThreadpool, joinRpcThreadpool
#include <android/log.h>                // ALOGD, ALOGE

#include "Hello.h"

// libhwbinder:
using ::android::hardware::configureRpcThreadpool;
using ::android::hardware::joinRpcThreadpool;
using ::android::sp;
using ::android::OK;
using ::android::status_t;

int main() {
    sp<::vendor::custom::hello_hidl::V1_0::IHello> service = nullptr;
    status_t status;

    service = new ::vendor::custom::hello_hidl::implementation::Hello();
    if (service == nullptr) {
        ALOGD("Failed to create Hello service instance.");
        return 1;
    }

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

    // 注册 binder 服务到 hwservicemanager
    status = service->registerAsService();
    if (status != OK) {
        ALOGE("Failed to register Hello service, status=%d.", status);
        return 1;
    }

    ALOGD("Hello service started successfully.");
    joinRpcThreadpool();

    return 0;
}

解读一下这个 main 的三步走:

  1. new Hello():创建实现实例。
  2. configureRpcThreadpool(1, true) :配一个 binder 线程,true 表示主线程会自己 join 进线程池。
  3. registerAsService() :把自己注册到 hwservicemanager,名字默认是 default。之后 joinRpcThreadpool() 阻塞主线程开始接请求。

1.6 添加 .rc.xml

HAL 服务要被 init 启动、要被 VINTF 校验,需要配两个文件。

vendor.custom.hello_hidl@1.0-service.rc

bash 复制代码
touch vendor.custom/hardware/interfaces/hello_hidl/1.0/default/vendor.custom.hello_hidl@1.0-service.rc
rc 复制代码
service hello-hidl /vendor/bin/hw/vendor.custom.hello_hidl@1.0-service
    class hal
    user system
    group system

class hal 让它在 class_start hal 时启动;user/group system 是运行身份。

vendor.custom.hello_hidl@1.0-service.xml(VINTF manifest 片段):

bash 复制代码
touch vendor/custom/hardware/interfaces/hello_hidl/1.0/default/vendor.custom.hello_hidl@1.0-service.xml
xml 复制代码
<manifest version="1.0" type="device">
    <hal format="hidl">
        <name>vendor.custom.hello_hidl</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>IHello</name>
            <instance>default</instance>
        </interface>
    </hal>
</manifest>

这个 XML 告诉系统:"本设备提供了一个名为 vendor.custom.hello_hidl::IHello/default 的 HIDL 服务,走 hwbinder 传输。" VINTF 框架会拿它和 compatibility matrix 做匹配校验。

1.7 配置 default/Android.bp(纯绑定模式)

hidl-gen 生成的 Android.bp 只是骨架,我们要改成完整的编译规则。本章是"纯绑定模式"------impl 静态库被打包进 service 二进制。

default/Android.bp

bp 复制代码
cc_defaults {
    name: "hello-hidl_defaults",
    relative_install_path: "hw",
    defaults: ["hidl_defaults"],

    // 模块属于 vendor 分区,受 Vendor 链接规则约束
    vendor: true,
    // 厂商私有代码,法律/发布标记
    proprietary: true,

    cflags: [
        "-Wno-unused-parameter",
        "-Wall",
    ],
    shared_libs: [
        "libcutils",
        "liblog",
        "libbase",
        "libsysutils",
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "vendor.custom.hello_hidl@1.0",
    ],
}

cc_library_static {
    name: "vendor.custom.hello_hidl@1.0-impl",
    defaults: ["hello-hidl_defaults"],
    srcs: [
        "Hello.cpp",
    ],
}

// 真正跑起来的 service
cc_binary {
    name: "vendor.custom.hello_hidl@1.0-service",
    defaults: ["hello-hidl_defaults"],
    init_rc: ["vendor.custom.hello_hidl@1.0-service.rc"],
    vintf_fragments: ["vendor.custom.hello_hidl@1.0-service.xml"],
    srcs: [
        "HelloService.cpp",
    ],
    static_libs: [
        "vendor.custom.hello_hidl@1.0-impl",  // 纯绑定式,实现被打包进 service
    ],
}

几个关键点:

  • relative_install_path: "hw" → 二进制装到 /vendor/bin/hw/,和 .rc 里的路径对齐。
  • cc_library_staticHello.cpp 编成静态库,再被 cc_binary 静态链入------这样部署只有一个二进制,简单。
  • init_rc / vintf_fragments 让 Soong 自动把 .rc.xml 装到对应位置。

1.8 基础版 Hello 实现(版本 A)

现在填 Hello.hHello.cpp本章只实现三个同步方法,没有任何回调、线程、订阅------那是后面章节的事。

Hello.h

cpp 复制代码
#pragma once

#include <vendor/custom/hello_hidl/1.0/IHello.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>

#include <string>

namespace vendor::custom::hello_hidl::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;

struct Hello : public V1_0::IHello {
    Hello();
    ~Hello();

    // 计算加法
    Return<uint32_t> calculate_plus(uint32_t a, uint32_t b) override;
    // 写入字符串
    Return<uint32_t> write(const hidl_string& name) override;
    // 读回字符串
    Return<void> read(read_cb _hidl_cb) override;

private:
    std::string mStoredString;  // 暂存 write() 写入的字符串
};

}  // namespace vendor::custom::hello_hidl::implementation

Hello.cpp

cpp 复制代码
#include "Hello.h"

#define LOG_TAG "HelloHAL"
#include <log/log.h>

namespace vendor::custom::hello_hidl::implementation {

using ::android::hardware::hidl_string;

Hello::Hello() {
    ALOGI("Hello HAL instance created");
}

Hello::~Hello() {
    ALOGI("Hello HAL instance destroyed");
}

// ---- calculate_plus ----
Return<uint32_t> Hello::calculate_plus(uint32_t a, uint32_t b) {
    uint32_t total = a + b;
    ALOGI("calculate_plus: %u + %u = %u", a, b, total);
    return total;
}

// ---- write ----
Return<uint32_t> Hello::write(const hidl_string& name) {
    mStoredString = name.c_str();
    ALOGI("write: stored \"%s\"", mStoredString.c_str());
    return 0;  // 0 表示成功
}

// ---- read ----
Return<void> Hello::read(read_cb _hidl_cb) {
    ALOGI("read: returning \"%s\"", mStoredString.c_str());
    _hidl_cb(mStoredString);  // 通过回调返回结果
    return Void();
}

}  // namespace vendor::custom::hello_hidl::implementation

说明:

  • read() 的签名里有个 read_cb _hidl_cb------这是 HIDL 为 generates (string name) 生成的回调类型,调用它就是把结果传回给调用方。它不是第 2 章那种异步回调,只是 HIDL 表达"返回值"的方式。
  • mStoredString 暂存 write() 的内容,read() 原样吐回,模拟一个最简的"写驱动→读驱动"。

1.9 更新 current.txt 的哈希

接口定义有变动时,要把它的哈希写进 current.txt,否则后续 hidl_interface 编译会报版本校验错误:

bash 复制代码
hidl-gen -L hash \
    -r vendor.custom:vendor/custom/hardware/interfaces \
    $PACKAGE -o $LOC >> vendor/custom/hardware/interfaces/current.txt

哈希用于保证接口的向后兼容------一旦发布,1.0 的方法签名就不能再改,要加功能只能出 1.1

1.10 VINTF 兼容性矩阵

光有设备侧 manifest(1.6 节的 .xml)还不够,框架侧要声明"我期望这个 HAL 可选存在"。在设备配置里加一个 compatibility matrix 片段:

device/<platform>/common/manifests/frameworks/vendor.custom.hello_hidl@1.0-service.xml

xml 复制代码
<compatibility-matrix version="1.0" type="framework">
    <hal format="hidl" optional="true">
        <name>vendor.custom.hello_hidl</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>IHello</name>
            <instance>default</instance>
        </interface>
    </hal>
</compatibility-matrix>

关键属性 optional="true"------告诉系统"这个 HAL 不是必选项,设备不提供也不影响兼容性"。

然后在设备的 vintf.mk(或等效 mk 文件)里注册:

makefile 复制代码
DEVICE_FRAMEWORK_COMPATIBILITY_MATRIX_FILE += \
    device/<platform>/common/manifests/frameworks/vendor.custom.hello_hidl@1.0-service.xml

1.11 加入 device.mk 并编译

在产品的 device.mk 里把三个产物加进去:

makefile 复制代码
PRODUCT_PACKAGES += \
    vendor.custom.hello_hidl@1.0 \
    vendor.custom.hello_hidl@1.0-service

重新编译:

bash 复制代码
mmm vendor/custom/hardware/interfaces/hello_hidl/1.0/default/

编译产物大致是:

kotlin 复制代码
out/target/product/xxx/vendor/bin/hw/vendor.custom.hello_hidl@1.0-service
out/target/product/xxx/vendor/etc/init/vendor.custom.hello_hidl@1.0-service.rc
out/target/product/xxx/vendor/etc/vintf/manifest/vendor.custom.hello_hidl@1.0-service.xml

1.12 (可选)Java 侧 jar 包怎么选

gen_java: true 会在编译产物里生成一组 Java 绑定 jar,路径类似:

bash 复制代码
out/soong/.intermediates/vendor/custom/hardware/interfaces/hello_hidl/
  1.0/vendor.custom.hello_hidl-V1.0-java/android_common/

里面有几个子目录,用途不同:

目录 内容 用途
turbine/ 仅头文件 jar(方法体为空) 编译系统内部加速依赖推导
javac/ 当前模块自己的实现 jar 不含静态依赖
combined/ javac 产物 + 所有静态依赖合并 导给 IDE、转 dex 都用它
turbine-combined/ turbine + 静态依赖的 header jar 合并 编译期推导用

无脑选 combined/ 下的 vendor.custom.hello_hidl-V1.0-java.jar 就行------它是完整产物,拖进 Android Studio 当 library 依赖,代码补全上下文齐全。第 6 章写 App 调用时就是用它。


本章小结

第 1 章我们搭起了 HAL 的骨架:

  • 定义了只有三个同步方法的 IHello.hal
  • hidl-gen 生成了接口库和 C++ 实现骨架
  • 写了 HelloService.cpp 把服务注册到 hwservicemanager
  • 配好了 .rc / .xml / Android.bp / VINTF 矩阵
  • 实现了基础版 Hello(版本 A),开机日志确认服务已注册

到这里,calculate_plus / write / read 三个同步接口已经能从 App 一路调到 HAL。

但真实的硬件场景里,HAL 往往需要主动 把数据推给上层------比如传感器周期上报、驱动事件通知。这种"HAL → App"的反向推送,靠同步调用做不到,需要回调机制。而回调,正是后面一系列问题的源头。

但是在此之前,我们先完善SELinux 的配置。


第 2 章 SELinux 配置

本章目标:给 Hello HAL 配好 SELinux 策略,让它能在 enforcing 模式下正常运行。

2.1 两种配置方式

SELinux 策略可以放在两个地方:

方式 位置 推荐度
专属目录 vendor/custom/.../default/sepolicy/ ⭐ 推荐,策略跟着 HAL 走
平台专属 device/<platform>/common/sepolicy/vendor/ 不推荐,策略分散难维护

本章采用专属目录方式 ------把所有 SELinux 文件放在 HAL 自己的 default/sepolicy/ 下。

2.2 文件清单

bash 复制代码
vendor/custom/hardware/interfaces/hello_hidl/1.0/default/sepolicy/
├── hello_hidl.te             # HAL 域策略(核心)
├── hello_hidl_test.te        # 测试程序域策略(可选)
├── file_contexts             # 文件安全上下文
└── hwservice_contexts        # HwService 注册上下文

然后在 device/<platform>/common/modules/sepolicy.mk 里注册:

makefile 复制代码
BOARD_SEPOLICY_DIRS += vendor/custom/hardware/interfaces/hello_hidl/1.0/default/sepolicy

2.3 file_contexts --- 文件安全上下文

sepolicy/file_contexts

bash 复制代码
# hello_hidl 相关文件的安全标签
/dev/hello                                                    u:object_r:hello_hidl_dev_t:s0
/vendor/bin/hw/vendor\.custom\.hello_hidl@1\.0-service         u:object_r:hello_hidl_exec:s0
/vendor/bin/hello_hidl_test                                    u:object_r:hello_hidl_test_exec:s0

这三行告诉系统:/dev/hello 设备节点打 hello_hidl_dev_t 标签,service 二进制打 hello_hidl_exec 标签,测试程序打 hello_hidl_test_exec 标签。

2.4 hello_hidl.te --- HAL 域策略(核心)

sepolicy/hello_hidl.te

ini 复制代码
# ============================================================
# vendor.custom.hello_hidl HAL --- SELinux domain policy
# ============================================================

# ---- 类型声明 ----
type hello_hidl, domain;
type hello_hidl_exec, exec_type, vendor_file_type, file_type;
type hello_hidl_dev_t, dev_type;
type hello_hidl_hwservice, hwservice_manager_type;
type hello_hidl_service, vendor_service, service_manager_type;

# ---- 服务端 ----

# 由 init 启动
init_daemon_domain(hello_hidl);

# 向 hwservicemanager 注册
add_hwservice(hello_hidl, hello_hidl_hwservice)
allow hello_hidl hidl_base_hwservice:hwservice_manager { add };

# hwbinder 通信 + hwservicemanager 属性访问
hwbinder_use(hello_hidl)
get_prop(hello_hidl, hwservicemanager_prop)

# 跨域 binder 调用:service ↔ hwservicemanager / system_server / system_app
binder_call(hello_hidl, hwservicemanager)
binder_call(hello_hidl, system_server)
binder_call(hello_hidl, system_app)
binder_call(system_server, hello_hidl)
binder_call(system_app, hello_hidl)

# 设备节点
allow hello_hidl hello_hidl_dev_t:chr_file { open read write };

# ---- 客户端 ----

# 允许 system_server / system_app 在 hwservicemanager 中查找本 HAL
allow system_server hello_hidl_hwservice:hwservice_manager { find };
allow system_app hello_hidl_hwservice:hwservice_manager { find };

# ---- hwservicemanager 跨域调用 ----
allow hwservicemanager hello_hidl:binder { transfer call };

# ---- 测试程序 ----
# 由 hello_hidl_test.te 定义域,这里补 hwbinder 跨域调用
binder_call(hello_hidl_test, hello_hidl)
binder_call(hello_hidl, hello_hidl_test)

逐段解读:

  • 类型声明:定义了 domain、exec、dev_t、hwservice、service 五个类型
  • init_daemon_domain :让 init 启动 service 二进制时自动切到 hello_hidl
  • add_hwservice :允许向 hwservicemanager 注册 hello_hidl_hwservice
  • hwbinder_use + binder_call:打通 hwbinder 通信链路
  • 客户端 find 权限:允许 system_server / system_app 查找本 HAL
  • 测试程序跨域:让测试程序能调用 HAL

2.5 hello_hidl_test.te --- 测试程序域(可选)

如果你写了 native 测试程序(hello_hidl_test),需要给它单独的域:

sepolicy/hello_hidl_test.te

scss 复制代码
type hello_hidl_test, domain;
type hello_hidl_test_exec, exec_type, vendor_file_type, file_type;

# 从 shell 执行时自动切换域
domain_auto_trans(shell, hello_hidl_test_exec, hello_hidl_test);

# 允许在 hwservicemanager 中查找 HAL
get_prop(hello_hidl_test, hwservicemanager_prop)
allow hello_hidl_test hello_hidl_hwservice:hwservice_manager { find };
hwbinder_use(hello_hidl_test);

# 跨域调用:测试程序 ↔ HAL 服务
binder_call(hello_hidl_test, hello_hidl)
binder_call(hello_hidl, hello_hidl_test)

2.6 hwservice_contexts --- 服务注册上下文

sepolicy/hwservice_contexts

arduino 复制代码
vendor.custom.hello_hidl::IHello    u:object_r:hello_hidl_hwservice:s0

这行把 HIDL 服务名 vendor.custom.hello_hidl::IHello 映射到 hello_hidl_hwservice 标签。hwservicemanager 注册和查找时都会校验这个上下文。

2.7 调试 SELinux 拒绝

编译后若遇到 SELinux 拒绝,用以下命令定位:

bash 复制代码
# 查看 SELinux 拒绝日志
adb logcat -b all -d | grep "avc.*denied.*hello"

# 调试期间临时放开(生产环境不要用!)
adb shell setenforce 0

setenforce 0 切到 permissive 模式,只记录拒绝不真正拦截,方便排查。调完记得 setenforce 1 恢复。

刷机开机后,logcat 应该能看到:

ruby 复制代码
HelloHAL: Hello HAL instance created
HidlServiceManagement: Registered vendor.custom.hello_hidl@1.0::IHello/default
Hello service started successfully.

看到这三行,说明你的 HAL 服务已经起来并注册成功了。🎉。下一章我们给 Hello 加上订阅推送,然后......你会踩到 HIDL 的一个经典坑。


本章小结

第 2 章配好了 SELinux:

  • 4 个策略文件放在 HAL 自己的 sepolicy/ 目录下
  • file_contexts 给设备节点和二进制打标签
  • hello_hidl.te 定义域策略:init 启动、hwservicemanager 注册、binder 通信、客户端查找
  • hwservice_contexts 注册服务名映射
  • setenforce 0 + logcat 排查拒绝

第 3 章 加入回调:能推送了,然后崩了

本章目标 :给 Hello 加上"HAL → App"的回调推送机制。我们会先跑通一个单客户端订阅版,然后演示 App 退出时 HAL 崩溃的经典坑------并搞清楚它为什么崩溃。

3.1 为什么 HAL 需要回调?看看 Vehicle HAL 怎么做的

在动手之前,先看一张图------这是 Android Automotive OS(AAOS)里最经典的 HIDL HAL 之一:Vehicle HAL

hardware/interfaces/automotive/vehicle/2.0/default/ --- AAOS Vehicle HAL 的实现目录

注意看 utils/ 下面这三个文件:

复制代码
utils/
├── IVehicle.hal           ← 服务端接口(App 调用它)
├── IVehicleCallback.hal   ← 回调接口(HAL 用它推数据给 App)
└── types.hal              ← 自定义数据结构

这就是 HIDL 回调模式的标准模板:

文件 角色 方向
IVehicle.hal 服务端接口 App → HAL(调用、查询、订阅)
IVehicleCallback.hal 回调接口 HAL → App(属性变化通知)
types.hal 共享数据 双向传递的结构体

Vehicle HAL 的典型工作流是这样的:

  1. CarService (Framework 层)拿到 IVehicle 服务
  2. 调用 ivehicle.subscribe(callback, propertyId) ------ 把自己的 IVehicleCallback 实现传进去
  3. Vehicle HAL 收到 CAN 总线数据后,调用 callback->onPropertyEvent(events) 把传感器数据推回来
  4. CarService 拿到数据做策略处理

关键点subscribe() 时传进来的 callback 对象是一个跨进程 binder 代理------它的实体在客户端进程里。如果客户端挂了,这个代理就变成"悬空指针"。Vehicle HAL 原厂代码里用了完善的死亡通知机制来处理这个问题。

我们的 Hello HAL 就要模仿这套设计:

Vehicle HAL 我们的 Hello HAL 含义
IVehicle IHello 服务端主接口
IVehicleCallback IHelloCallback 回调接口
PropertyValues[] TemperatureAndPressure 自定义数据结构
subscribe(property, callback) subscribe(callback) 订阅推送
onPropertyEvent(values) onValueSet(value) / onValueSetError(err) 推送通知

理解了这套"原型",下面就开始写代码。

3.2 扩展 .hal:加入自定义类型和回调接口

回到我们的项目,现在要扩展 .hal 定义。新增三个文件(或者把新内容加到现有文件里):

types.hal(新增------自定义数据结构)
bash 复制代码
touch vendor/custom/hardware/interfaces/hello_hidl/1.0/types.hal
hidl 复制代码
package vendor.custom.hello_hidl@1.0;

// 模拟传感器数据:温度 + 气压
struct TemperatureAndPressure {
    uint64_t temperature;
    uint64_t pressure;
};

// 状态码枚举
enum StatusCode : int32_t {
    OK = 0,
    ERROR = -1,
    INTERNAL_ERROR = -2,
};

TemperatureAndPressure 是我们模拟的"传感器数据"------后面每秒递增一次,通过回调推给客户端。

IHelloCallback.hal(新增------回调接口)
bash 复制代码
touch vendor/custom/hardware/interfaces/hello_hidl/1.0/IHelloCallback.hal
hidl 复制代码
package vendor.custom.hello_hidl@1.0;

interface IHelloCallback {
    // 推送传感器数据(oneway = 异步,不阻塞 HAL 推送线程)
    oneway onValueSet(TemperatureAndPressure value);
    // 推送错误信息
    oneway onValueSetError(StatusCode errorCode);
};

两个关键设计点:

  • oneway 关键字 :表示这是一个异步单向调用。HAL 调用后不等返回就继续走,不会阻塞 notificationLoop。这对高频推送场景至关重要。
  • 两个方法 :正常数据走 onValueSet,异常情况走 onValueSetError,和 Vehicle HAL 的 onPropertyEvent / onPropertyError 一脉相承。
③ 更新 IHello.hal(追加订阅方法)

在原有三个同步方法基础上追加:

hidl 复制代码
package vendor.custom.hello_hidl@1.0;

import IHelloCallback;   // ← 新增导包

interface IHello {
    // ---- 同步方法(已有)----
    calculate_plus(uint32_t a, uint32_t b) generates (uint32_t total);
    write(string name) generates (uint32_t result);
    read() generates (string name);

    // ---- 订阅方法(新增)----
    // 客户端传入回调对象,订阅传感器数据推送
    subscribe(IHelloCallback callback) generates (StatusCode status);
    // 取消订阅
    unsubscribe(IHelloCallback callback) generates (StatusCode status);
};

3.3 重新生成代码

.hal 改过了,要重新跑一遍代码生成流程:

bash 复制代码
# 重新生成 impl 骨架(会覆盖 Hello.h/.cpp,注意备份!)
PACKAGE=vendor.custom.hello_hidl@1.0
LOC=vendor/custom/hardware/interfaces/hello_hidl/1.0/default

# 首先更新current.txt
hidl-gen -L hash \
    -r vendor.custom:vendor/custom/hardware/interfaces \
    $PACKAGE -o $LOC >> vendor/custom/hardware/interfaces/current.txt

# 重新生成接口库的 Android.bp(会自动包含新的 types/IHelloCallback)
cd vendor/custom/hardware/interfaces/
./update-makefiles.sh

hidl-gen -o $LOC -L c++-impl \
    -r vendor.custom:vendor/custom/hardware/interfaces/ \
    -r android.hidl:system/libhidl/transport \
    $PACKAGE

# 重新生成 impl 的 Android.bp
hidl-gen -o $LOC -L androidbp-impl \
    -r vendor.custom:vendor/custom/hardware/interfaces/ \
    -r android.hidl:system/libhidl/transport \
    $PACKAGE

# 重新编译接口库
mmm vendor/custom/hardware/interfaces/hello_hidl/1.0/

⚠️ hidl-gen -L c++-impl 会覆盖 Hello.hHello.cpp!如果之前已经填过业务逻辑,要先备份再执行。

此时目录结构变成了这样:

csharp 复制代码
hello_hidl/1.0/
├── IHello.hal              # 扩展后的服务接口
├── IHelloCallback.hal      # 新增的回调接口
├── types.hal               # 新增的自定义类型
├── Android.bp              # 自动更新(含新文件)
│
├── default/
│   ├── Android.bp          # 自动更新
│   ├── Hello.h             # 重新生成的骨架(含新方法声明)
│   ├── Hello.cpp           # 重新生成的空实现(含新方法桩)
│   ├── HelloCallback.h     # 新增:回调实现的头文件骨架
│   ├── HelloCallback.cpp   # 新增:回调实现的空桩
│   ├── HelloService.cpp    # 不变
│   ├── *.rc                # 不变
│   └── *.xml               # 不变

注意到 hidl-gen 多生成了 HelloCallback.hHelloCallback.cpp------这是 IHelloCallback 的 C++ 实现骨架。服务端的 HelloCallback 只是空桩,真正的实现在客户端。

3.4 单客户端踩坑版实现(版本 B)

现在开始填代码。本章的目标是做一个能跑但会崩溃的单客户端版本。(我chovy!拿代码TM给我拿好的啊!🤪)

HelloCallback(服务端空桩)

HelloCallback.h

cpp 复制代码
#pragma once

#include <vendor/custom/hello_hidl/1.0/IHelloCallback.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>

namespace vendor::custom::hello_hidl::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;

// IHelloCallback 在客户端实现;服务端保留空桩以便 impl 库编译通过
struct HelloCallback : public V1_0::IHelloCallback {

    Return<void> onValueSet(
        const ::vendor::custom::hello_hidl::V1_0::TemperatureAndPressure& value) override;

    Return<void> onValueSetError(
        ::vendor::custom::hello_hidl::V1_0::StatusCode errorCode) override;

};

}  // namespace vendor::custom::hello_hidl::implementation

HelloCallback.cpp

cpp 复制代码
#include "HelloCallback.h"
#include <android/log.h>

#define LOG_TAG "HelloHAL-Callback"
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

namespace vendor::custom::hello_hidl::implementation {

// 服务端空桩------客户端的 IHelloCallback 实现会通过 binder 被调用
Return<void> HelloCallback::onValueSet(
    const ::vendor::custom::hello_hidl::V1_0::TemperatureAndPressure& value) {

    ALOGI("onValueSet stub called (client would normally receive this): "
          "temp=%llu, press=%llu",
          (unsigned long long)value.temperature,
          (unsigned long long)value.pressure);
    return Void();
}

Return<void> HelloCallback::onValueSetError(
    ::vendor::custom::hello_hidl::V1_0::StatusCode errorCode) {

    ALOGI("onValueSetError stub called: errorCode=%d", static_cast<int32_t>(errorCode));
    return Void();
}

}  // namespace vendor::custom::hello_hidl::implementation

这两文件只是让编译通过的桩。真正的 IHelloCallback 实现(接收数据的那个)写在客户端 Java/C++ 侧。

Hello(单客户端踩坑版)

Hello.h(版本 B ------ 加入回调相关成员):

cpp 复制代码
#pragma once

#include <vendor/custom/hello_hidl/1.0/IHello.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>

#include <string>
#include <thread>
#include <atomic>

namespace vendor::custom::hello_hidl::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;

struct Hello : public V1_0::IHello {
    Hello();
    ~Hello();

    // ---- 同步方法 ----
    Return<uint32_t> calculate_plus(uint32_t a, uint32_t b) override;
    Return<uint32_t> write(const hidl_string& name) override;
    Return<void> read(read_cb _hidl_cb) override;

    // ---- 订阅方法 ----
    Return<V1_0::StatusCode> subscribe(
        const sp<V1_0::IHelloCallback>& callback) override;
    Return<V1_0::StatusCode> unsubscribe(
        const sp<V1_0::IHelloCallback>& callback) override;

private:
    std::string mStoredString;       // read/write 暂存

    // ====== 单客户端回调(版本 B ------ 故意不用 map 和 linkToDeath)======
    sp<V1_0::IHelloCallback> mCallback;   // 单个回调引用
    std::thread mNotifyThread;             // 推送线程
    std::atomic<bool> mThreadRunning{false};

    uint64_t mTemperature{0};      // 模拟温度值
    uint64_t mPressure{0};         // 模拟气压值

    void notificationLoop();        // 推送循环
    void startNotifyThreadIfNeeded();   // 启动线程
    void stopNotifyThreadIfEmpty();     // 停止线程
};

}  // namespace vendor::custom::hello_hidl::implementation

对比版本 A,新增了:

  • mCallback:单个回调强引用
  • mNotifyThread + mThreadRunning:后台推送线程
  • mTemperature / mPressure:模拟传感器数据

Hello.cpp(版本 B ------ 单客户端,无 linkToDeath):

cpp 复制代码
#include "Hello.h"

#define LOG_TAG "HelloHAL"
#include <log/log.h>

#include <chrono>
#include <unistd.h>

namespace vendor::custom::hello_hidl::implementation {

using ::android::hardware::hidl_string;

Hello::Hello() {
    ALOGI("Hello HAL instance created");
}

Hello::~Hello() {
    mThreadRunning = false;
    if (mNotifyThread.joinable()) {
        mNotifyThread.join();
    }
    ALOGI("Hello HAL instance destroyed");
}

// ---- calculate_plus / write / read(同版本 A,略)----

Return<uint32_t> Hello::calculate_plus(uint32_t a, uint32_t b) {
    uint32_t total = a + b;
    ALOGI("calculate_plus: %u + %u = %u", a, b, total);
    return total;
}

Return<uint32_t> Hello::write(const hidl_string& name) {
    mStoredString = name.c_str();
    ALOGI("write: stored \"%s\"", mStoredString.c_str());
    return 0;
}

Return<void> Hello::read(read_cb _hidl_cb) {
    ALOGI("read: returning \"%s\"", mStoredString.c_str());
    _hidl_cb(mStoredString);
    return Void();
}

// ============================================================
// 订阅 / 取消订阅(版本 B:单客户端,无 linkToDeath)
// ============================================================

Return<V1_0::StatusCode> Hello::subscribe(
    const sp<V1_0::IHelloCallback>& callback) {

    if (callback == nullptr) {
        ALOGE("subscribe: callback is null");
        return V1_0::StatusCode::ERROR;
    }

    if (mCallback != nullptr) {
        ALOGE("subscribe: already subscribed");
        return V1_0::StatusCode::ERROR;
    }

    // 🔴 版本 B 特征:直接存 callback,不调 linkToDeath
    mCallback = callback;

    startNotifyThreadIfNeeded();

    ALOGI("subscribe: callback registered, starting notification thread");
    return V1_0::StatusCode::OK;
}

Return<V1_0::StatusCode> Hello::unsubscribe(
    const sp<V1_0::IHelloCallback>& /*callback*/) {

    if (mCallback == nullptr) {
        ALOGE("unsubscribe: not subscribed");
        return V1_0::StatusCode::ERROR;
    }

    mCallback = nullptr;   // 清掉引用
    stopNotifyThreadIfEmpty();

    ALOGI("unsubscribe: callback cleared");
    return V1_0::StatusCode::OK;
}

// ---- 推送线程管理 ----

void Hello::startNotifyThreadIfNeeded() {
    if (!mThreadRunning) {
        if (mNotifyThread.joinable()) {
            mNotifyThread.join();
        }
        mThreadRunning = true;
        mTemperature = 0;
        mPressure = 0;
        mNotifyThread = std::thread(&Hello::notificationLoop, this);
        ALOGI("notification thread started");
    }
}

void Hello::stopNotifyThreadIfEmpty() {
    mThreadRunning = false;
    if (mNotifyThread.joinable()) {
        mNotifyThread.join();
        ALOGI("notification thread stopped");
    }
}

// ---- 推送循环 ----
void Hello::notificationLoop() {
    ALOGI("notificationLoop started, pushing every 1 second");

    while (mThreadRunning) {
        std::this_thread::sleep_for(std::chrono::seconds(1));

        if (!mThreadRunning) break;

        V1_0::TemperatureAndPressure value;
        value.temperature = mTemperature;
        value.pressure = mPressure;

        // 🔴 直接调 callback->onValueSet() ------ 如果客户端已死,这里会崩
        if (mCallback != nullptr && mThreadRunning) {
            ALOGI("pushing to client: t=%llu p=%llu",
                  (unsigned long long)value.temperature,
                  (unsigned long long)value.pressure);
            mCallback->onValueSet(value);   // ⚡ 危险操作!
        }

        mTemperature++;
        mPressure++;
    }

    mTemperature = 0;
    mPressure = 0;
    ALOGI("notificationLoop stopped");
}

}  // namespace vendor::custom::hello_hidl::implementation

🔴 版本 B 的 bug 标注了两处

  1. subscribe() 里没有 callback->linkToDeath() ------ 不知道客户端何时死亡
  2. notificationLoop 里直接调 mCallback->onValueSet() ------ 客户端死后这行就是致命一击

同时别忘了更新 default/Android.bp,把 HelloCallback.cpp 加到 srcs

bp 复制代码
cc_library_static {
    name: "vendor.custom.hello_hidl@1.0-impl",
    defaults: ["hello-hidl_defaults"],
    srcs: [
        "Hello.cpp",
        "HelloCallback.cpp",   # ← 新增
    ],
}

3.5 编译运行 + Java 端验证

重新编译:

bash 复制代码
mmm vendor/custom/hardware/interfaces/hello_hidl/1.0/default/

刷机开机,确认服务启动成功。然后在 Java 侧写个简单验证(详细代码在第 7 章,这里只放核心片段,心里有个数就行):

java 复制代码
// 1. 获取 HAL 服务
IHello service = IHello.getService();

// 2. 先测同步方法
int result = service.calculate_plus(10, 20);  // → 30 ✅
service.write("Hello from Java!");
service.read((status, name) -> {
    Log.i(TAG, "read: " + name);  // → "Hello from Java!" ✅
});

// 3. 订阅推送
MyHelloCallback callback = new MyHelloCallback(TAG);
int st = service.subscribe(callback);
if (st == StatusCode.OK) {
    Log.i(TAG, "subscribed! waiting for callbacks...");
    // logcat 会刷出:
    // t=0 p=0
    // t=1 p=1
    // t=2 p=2 ...
}

跑起来:subscribe 返回 OK,logcat 里每秒刷一行 t=0 p=0t=1 p=1......一切正常

3.6 💥 退出 App → HAL 崩溃

现在退出 App(或杀掉进程),观察 logcat:

sql 复制代码
--- APP 进程被杀 ---

*** *** *** *** ***
Build fingerprint: '...'
Binary crash handler enabled.
pid: xxx, tid: xxx, name: Binder:x  >>> vendor.custom.hello_hidl@1x <<<
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
    Abort message: 'Failed transact on interface ...'

HAL 进程跟着一起死了 😱

这不是偶发问题,而是 HIDL 回调的一个经典陷阱。先别急着改代码------我们先彻底搞清楚:HAL 为什么会跟着 App 一起死?

3.7 根因分析:崩溃链路全解

你的 notificationLoop 里持有客户端的 sp<IHelloCallback>(强引用),每秒调用 onValueSet 往外推数据。当 App 被杀时,发生了以下连锁反应:

scss 复制代码
App 进程被杀(用户按 Home 上滑 / am force-stop)
       │
       ▼
Binder 驱动检测到对端进程死亡
(hwbinder 连接断开)
       │
       ▼
HAL 服务端的 mCallback 变成"悬空代理"
(sp<> 还活着,但它指向的远程对象已经没了)
       │
       ▼
下一次 notificationLoop 到来
调用 mCallback->onValueSet(value)
       │
       ▼
Binder 事务发送失败
(对端不存在)
       │
       ▼
没有来处理这个错误 → 自动触发 abort() 触发 SIGABRT
       │
       ▼
❌ HAL 进程崩溃

核心问题有两个:

  1. 不知道客户端死了 :我们没有注册死亡通知,所以 mCallback 还是那个旧的强引用,不知道它已经失效。
  2. 要处理异常onValueSet() 在 C++ HIDL 中,底层其实是不抛出传统 C++ try-catch 异常的。真正导致 abort 的原因是 HIDL 的底层机制:oneway 方法会返回一个 Return 对象,当 IPC 事务失败(比如远端进程已死)时,该对象内部包含错误状态。如果开发者没有显式调用 .isOk() 来处理这个错误,那么当这个 Return 对象由于生命周期结束而发生析构时,系统底层(Bionic库)会自动触发 abort() 杀死当前进程 。

回过头来看 Vehicle HAL 原厂是怎么处理的------它做了两件事:

  1. linkToDeath:订阅时就向 Binder 驱动注册死亡监听,客户端一死立刻收到通知
  2. 异常兜底:即使死亡通知延迟,处理错误。

这两条防线,就是下一章我们要加的东西。


本章小结

第 3 章我们把 Hello 从纯同步接口升级成了带回调推送的版本:

  • 参照 AAOS Vehicle HALIVehicle / IVehicleCallback 模板,设计了 IHello / IHelloCallback
  • oneway 保证推送不阻塞 HAL 线程
  • 实现了单客户端踩坑版(版本 B) ------故意不留 linkToDeath
  • 验证了推送功能正常,然后演示了 App 退出 HAL 崩溃的经典坑
  • 分析了完整的崩溃链路:悬空代理 → 事务失败 → 未处理错误 → 进程 abort

崩溃原因清楚了,下一章修。


第 4 章 修复崩溃:死亡通知两条防线

本章目标 :给单客户端版加上死亡通知机制,修掉"App 退出 HAL 崩溃"的 bug。最终得到稳定可用的单客户端版(版本 C)

本章完成后,你的 Hello HAL 已经可以在生产环境支撑单个客户端的安全订阅了。多客户端支持在第 5 章。

4.1 修复方案总览

我们要加两层防护:

scss 复制代码
┌─────────────────────────────────────────────┐
│           防 线 体 系                         │
├────────────────────┬────────────────────────┤
│  防线 1:主动监听    │  防线 2:兜底清理       │
│                    │                        │
│  subscribe() 时    │  线程退出时的收尾       │
│  linkToDeath()     │                        │
│       │            │  ┌──────────────────┐  │
│       ▼            │  │ lock → 清空引用    │  │
│  客户端死亡时      │  │ → 重置计数器       │  │
│  serviceDied()    │  │ → 停止线程         │  │
│       │            │  └──────────────────┘  │
│       ▼            │                        │
│  onClientDied()    │  即使防线1延迟触发,   │
│  → 清空 mCallback  │  防线2也能保住进程     │
│  → 停止线程        │                        │
└────────────────────┴────────────────────────┘

4.2 防线 1:linkToDeath + ClientDeathRecipient

HIDL/Binder 提供了一个标准 API 来监听远端进程死亡:IBase::linkToDeath()

原理很简单:你传给它一个 hidl_death_recipient 子类对象,当 binder 对端进程被杀时,binder 驱动会在任意一个 binder 线程 中回调你的 serviceDied() 方法。

我们需要:

  1. 定义一个 ClientDeathRecipient 类继承 hidl_death_recipient
  2. subscribe() 时调用 callback->linkToDeath(deathRecipient)
  3. serviceDied() 中清空回调、停止线程
修改 Hello.h

在成员变量区域替换原来的单字段为以下内容:

cpp 复制代码
private:
    std::string mStoredString;

    friend class ClientDeathRecipient;  // ← 新增友元

    // 单个回调 + 死亡通知(版本 C)
    sp<V1_0::IHelloCallback> mCallback;
    sp<::android::hardware::hidl_death_recipient> mDeathRecipient;  // ← 新增

    std::thread mNotifyThread;
    std::atomic<bool> mThreadRunning{false};

    uint64_t mTemperature{0};
    uint64_t mPressure{0};

    void notificationLoop();
    void startNotifyThreadIfNeeded();
    void stopNotifyThreadIfEmpty();

    // 由死亡通知触发:清理回调
    void onClientDied();  // ← 新增
修改 Hello.cpp(版本 C)

首先加上 ClientDeathRecipient 类定义和 onClientDied 方法:

cpp 复制代码
#include "Hello.h"

#define LOG_TAG "HelloHAL"
#include <log/log.h>
#include <chrono>
#include <unistd.h>
#include <hidl/HidlSupport.h>
#include <hwbinder/IPCThreadState.h>   // ← 新增

namespace vendor::custom::hello_hidl::implementation {

using ::android::hardware::hidl_string;
using ::android::hardware::IPCThreadState;

// ============================================================
// 死亡通知 --- 监听客户端死亡,自动清理回调
// ============================================================
class ClientDeathRecipient : public ::android::hardware::hidl_death_recipient {
    Hello* mHello;
public:
    ClientDeathRecipient(Hello* hello) : mHello(hello) {}

    void serviceDied(uint64_t /*cookie*/,
                     const ::android::wp<::android::hidl::base::V1_0::IBase>& /*who*/)
                     override {
        ALOGI("ClientDeathRecipient: client died, auto-cleanup");
        if (mHello) {
            mHello->onClientDied();
        }
    }
};

// ============================================================
// onClientDied --- 死亡通知的处理逻辑
// ============================================================

void Hello::onClientDied() {
    mCallback = nullptr;        // 清掉悬空引用
    stopNotifyThreadIfEmpty();  // 停止推送线程
    ALOGI("onClientDied: callback cleared, thread stopped");
}

然后重写 subscribe()unsubscribe()

cpp 复制代码
// ---- subscribe(版本 C:加 linkToDeath)----

Return<V1_0::StatusCode> Hello::subscribe(
    const sp<V1_0::IHelloCallback>& callback) {

    if (callback == nullptr) {
        ALOGE("subscribe: callback is null");
        return V1_0::StatusCode::ERROR;
    }

    if (mCallback != nullptr) {
        ALOGE("subscribe: already subscribed");
        return V1_0::StatusCode::ERROR;
    }

    mCallback = callback;

    // ✅ 版本 C 关键改动:注册死亡通知
    mDeathRecipient = new ClientDeathRecipient(this);
    bool linked = mCallback->linkToDeath(mDeathRecipient, 0 /*cookie*/);
    if (!linked) {
        ALOGE("subscribe: linkToDeath failed, client may already be dead");
        mCallback = nullptr;
        mDeathRecipient = nullptr;
        return V1_0::StatusCode::ERROR;
    }

    startNotifyThreadIfNeeded();
    ALOGI("subscribe: callback registered with death notification");
    return V1_0::StatusCode::OK;
}

// ---- unsubscribe(版本 C:加 unlinkToDeath)----

Return<V1_0::StatusCode> Hello::unsubscribe(
    const sp<V1_0::IHelloCallback>& /*callback*/) {

    if (mCallback == nullptr) {
        ALOGE("unsubscribe: not subscribed");
        return V1_0::StatusCode::ERROR;
    }

    // ✅ 版本 C 关键改动:取消死亡通知绑定
    if (mDeathRecipient != nullptr && mCallback != nullptr) {
        mCallback->unlinkToDeath(mDeathRecipient);
        mDeathRecipient = nullptr;
    }

    mCallback = nullptr;
    stopNotifyThreadIfEmpty();

    ALOGI("unsubscribe: callback and death recipient cleared");
    return V1_0::StatusCode::OK;
}

同步方法和推送循环保持不变(notificationLoop 里已经有 if (mCallback != nullptr) 检查,作为防线 2 兜底):

cpp 复制代码
// notificationLoop 保持不变 ------ 已有 null 检查作为防线2
void Hello::notificationLoop() {
    ALOGI("notificationLoop started, pushing every 1 second");

    while (mThreadRunning) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        if (!mThreadRunning) break;

        V1_0::TemperatureAndPressure value;
        value.temperature = mTemperature;
        value.pressure = mPressure;

        // 防线 2:null 检查 ------ 即使防线1还没来得及触发,
        // 这里发现 mCallback 为 null 就跳过,不会崩
        if (mCallback != nullptr && mThreadRunning) {
            ALOGI("pushing to client: t=%llu p=%llu",
                  (unsigned long long)value.temperature,
                  (unsigned long long)value.pressure);
            mCallback->onValueSet(value);
        }

        mTemperature++;
        mPressure++;
    }

    mTemperature = 0;
    mPressure = 0;
    ALOGI("notificationLoop stopped");
}

4.3 linkToDeath 为什么这么快?内核级原理

你可能会问:linkToDeath 到底是怎么做到"客户端一死,HAL 立刻就知道"的?它不是轮询,也不是定时检测------而是由 Linux 内核的 Binder 驱动直接触发,几乎没有延迟。

这是 Android 系统高可用性的核心机制之一,值得花两分钟搞懂。

完整链路
arduino 复制代码
客户端进程崩溃(段错误 Segfault / 被 OOM 杀 / 被 force-stop)
       │
       ▼
Linux 内核自动回收该进程的所有资源
       │
       ├── 释放虚拟内存
       ├── 关闭所有打开的文件描述符(fd)
       │     │
       │     └── 其中就包括 /dev/hwbinder 的 fd  ⚡ 关键
       │
       ▼
Binder 驱动在内核态立刻感知到这个 binder 节点的消失
(fd 关闭 → 驱动收到 release 回调 → 节点被标记为 dead)
       │
       ▼
Binder 驱动"顺藤摸瓜":
遍历所有曾经向该节点注册过 linkToDeath 的进程
       │
       ▼
向每一个注册者发送 binderDied 回调
(通过 binder 线程投递,不需要用户态轮询)
       │
       ▼
HAL 进程的 ClientDeathRecipient::serviceDied() 被调用
       │
       ▼
onClientDied() → 清理回调 → 停止线程 ✅
几个关键点
  1. 内核驱动驱动,不是用户态轮询linkToDeath 注册时会在 binder 驱动里挂一个"死亡监听节点"。客户端进程死掉时,是内核主动推送死亡通知,不依赖任何用户态定时检查。

  2. fd 关闭是触发点 :进程崩溃时内核会自动关闭它的所有 fd,包括 /dev/hwbinder。Binder 驱动在 binder_release() 回调里发现节点没了,就触发死亡通知。这就是为什么几乎无延迟------fd 关闭和死亡通知在内核态同步完成。

  3. 覆盖所有死亡方式 :不管是段错误(SIGSEGV)、被 kill -9、被 OOM Killer、还是 am force-stop------只要进程没了,内核都会回收 fd,binder 驱动都能感知到。

  4. binder 线程投递serviceDied() 在 HAL 进程的某个 binder 线程中被调用,不是你自己的业务线程。所以 onClientDied() 里的清理逻辑要注意线程安全(这也是为什么版本 D 用 std::mutex 保护 mSubscriptions)。

💡 一句话总结linkToDeath 的可靠性来自 Linux 内核------进程死则 fd 关,fd 关则驱动知,驱动知则回调至。整条链路在内核态完成,用户态零延迟感知。

理解了 linkToDeath 的原理,我们再验证修复效果:

4.4 验证:App 退出 HAL 不再崩

编译刷机,重复上次的测试流程:

  1. App 启动 → subscribe → 每秒收到推送 ✅
  2. 退出 App(Home 上滑 / am force-stop
  3. 观察 logcat:
vbnet 复制代码
HelloHAL: subscribe: callback registered with death notification
HelloHAL: notification thread started
HelloHAL: pushing to client: t=0 p=0
HelloHAL: pushing to client: t=1 p=1
...
ClientDeathRecipient: client died, auto-cleanup     ← 防线1 触发
HelloHAL: onClientDied: callback cleared, thread stopped
HelloHAL: notificationLoop stopped                   ← 线程安全退出

HAL 进程稳如老狗 🎉

4.5 两条防线各自的作用时机

场景 防线 1(linkToDeath) 防线 2(null check)
正常 unsubscribe unlinkToDeath + 清空 ---
App 被 kill serviceDied → 清空 兜底:如果死亡通知延迟,下次循环 null check 跳过
App 被 OOM 杀 ✅ 同上 ✅ 同上
Binder 驱动异常 ❌ 可能不触发 ✅ null check 保命

两条防线互补:防线 1 是主力,防线 2 是保险

4.6 延伸:AAOS 的另一道高可用防线 ------ CarWatchDog

在研究 Vehicle HAL 的过程中,我还发现 AAOS 里有一个比 linkToDeath 更"狠"的高可用机制:CarWatchdog

linkToDeath 解决的是"客户端死了,服务端怎么知道 "的问题。但反过来还有一个问题:如果 HAL 服务端自己卡死了呢? 客户端光知道对方"还活着"(进程没死)是不够的------进程可能在跑,但主线程死锁了、消息队列堵了、传感器数据十分钟没更新了。

这就是 CarWatchdog 要解决的场景。它的核心思路是主动心跳 + 超时杀进程

arduino 复制代码
┌───────────────────────────────────────────────────┐
│              CarWatchdog 架构(简化)               │
│                                                   │
│  ┌─────────────┐    注册为 Client     ┌────────┐  │
│  │  你的 HAL    │ ──────────────────▶ │ CarWatch│  │
│  │  / Service   │                     │  dog    │  │
│  └──────┬──────┘                     │ Server  │  │
│         │                             └───┬────┘  │
│         │  每 3s 调 tellClientAlive()      │       │
│         │  "我还活着,这是我的心跳"         │       │
│         ▼─────────────────────────────────▶│       │
│                                           │       │
│         如果超过超时时间没收到心跳 ─────────▶│       │
│                                           ▼       │
│                                    ❌ 杀掉该 Client│
│                                    (carwatchdog  │
│                                     重启策略)     │
└───────────────────────────────────────────────────┘

对比一下两者的定位:

机制 解决什么问题 触发方式 后果
linkToDeath 对端进程没了 内核 fd 关闭 → 驱动推送 活着的一方收到回调,做清理
CarWatchdog 对端进程在但卡了 心跳超时 杀掉卡死的进程并重启

简单说:linkToDeath 是"死了通知我 ",CarWatchdog 是"没死但哑了,我帮你杀"。两者在 AAOS 里配合使用,构成车规级的高可用双保险。

CarWatchdog 的完整实现涉及 ICarWatchdog AIDL 接口、WatchdogPerServiceHandler、超时策略配置、以及和 init 的重启联动------内容比较多,这里先画个饼:

🍩 后续预告 :如果你对 AAOS 的高可用机制感兴趣,我会单独开一篇讲 CarWatchdog 的完整实现------包括如何让你的 HAL 注册为 Watchdog Client、心跳机制怎么写、超时后的重启策略怎么配。到时候我们会从 CarServiceHelperService 一路追到 native 层的 CarWatchdogDaemon

现在,让我们回到 Hello HAL 的演进。linkToDeath 已经能保住单客户端场景下的稳定性了,下一章解决多客户端的问题。


本章小结

第 4 章我们修掉了崩溃,并深入了底层原理:

  • 引入 ClientDeathRecipient 继承 hidl_death_recipient,在 subscribe() 时注册死亡通知
  • 客户端死亡 → serviceDied()onClientDied() → 清空引用 + 停止线程
  • unsubscribe() 时不忘 unlinkToDeath() 解绑
  • notificationLoop 的 null 检查作为兜底防线
  • 深入 linkToDeath 内核级原理:进程死 → fd 关 → 驱动知 → 回调至,几乎零延迟
  • 延伸了解 AAOS 的 CarWatchdog 心跳机制(进程在但卡了的场景)
  • 验证:App 退出后 HAL 进程不再崩溃

现在的 Hello 已经是稳定的单客户端版 了。但如果生产环境有多个进程想同时订阅呢?比如 system_server 要收传感器数据,同时一个诊断 App 也想看实时值------当前的"单 callback"会被第二次 subscribe 直接覆盖。

下一章解决这个问题。


第 5 章 进阶:多客户端订阅

本章目标 :从"单客户端"升级到"多客户端隔离",每个客户端独立拥有自己的回调 + 死亡通知,互不影响。最终得到生产级完整版(版本 D)

5.1 问题:第二个订阅者会干掉第一个

当前版本的 subscribe() 有这段逻辑:

cpp 复制代码
if (mCallback != nullptr) {
    return ERROR;  // "已经有人订了,你来晚了"
}

这意味着同一时刻只能有一个客户端订阅。但在真实系统里,多个进程往往需要同时监听同一个 HAL:

scss 复制代码
┌──────────────┐    subscribe()    ┌──────────────┐
│ system_server │ ───────────────▶  │              │
│ (策略决策)    │                  │  Hello HAL   │
└──────────────┘                  │  (单 callback │
                                  │   被覆盖!)    │
┌──────────────┐    subscribe()    │              │
│ DiagnosticApp│ ───────────────▶  │              │
│ (调试工具)    │                  └──────────────┘
└──────────────┘

第二个 subscribe 要么报错拒绝,要么覆盖第一个------两种都不合理。

正确做法:每个客户端进程维护自己独立的回调通道,互不干扰。

5.2 架构对比

ini 复制代码
┌─────────────────────────────────┐    ┌─────────────────────────────────┐
│       版本 C(单客户端)          │    │       版本 D(多客户端)          │
│                                 │    │                                 │
│   mCallback (单个 sp<>)         │    │   mSubscriptions (map)          │
│                                 │    │                                 │
│   mDeathRecipient (单个)        │    │   ├─ pid=1234 → {cb₁, death₁}   │
│                                 │    │   ├─ pid=5678 → {cb₂, death₂}   │
│   notificationLoop:             │    │   └─ pid=9012 → {cb₃, death₃}   │
│   → push 1 个回调               │    │                                 │
│                                 │    │   notificationLoop:             │
│   线程: 首次启/最后停            │    │   → 遍历 map,逐个推送           │
│                                 │    │                                 │
└─────────────────────────────────┘    │   线程: 首次启/最后停或死完停      │
                                      └─────────────────────────────────┘

5.3 设计要点

1️⃣ 用 PID 区分客户端

每个客户端进程有一个唯一的 PID(进程 ID)。我们用 PID 作为 key,把每个客户端的回调 + 死亡通知打包成一个 Subscription 结构体:

cpp 复制代码
struct Subscription {
    sp<V1_0::IHelloCallback> callback;
    sp<hidl_death_recipient> deathRecipient;
};

std::map<pid_t, Subscription> mSubscriptions;  // key = 客户端 PID
std::mutex mSubscriptionMutex;                 // 保护 map 的并发访问

PID 通过 IPCThreadState::self()->getCallingPid()subscribe() 调用时获取------这是 Binder 框架提供的标准 API,返回的是发起本次 IPC 调用的客户端进程 ID。

2️⃣ 每个客户端独立的死亡通知
cpp 复制代码
// subscribe() 时:
auto& sub = mSubscriptions[pid];
sub.callback = callback;
sub.deathRecipient = new ClientDeathRecipient(this, pid);
callback->linkToDeath(sub.deathRecipient, 0);

每个 Subscription 有自己的 deathRecipient,当某个客户端挂掉时,只移除它自己的条目,其他不受影响。

3️⃣ 线程启停规则不变
  • 第一个 subscribe → 启动 notificationLoop
  • 最后一个 unsubscribe 或死亡 → 停止 notificationLoop
  • 无人订阅时不浪费资源

5.4 完整实现(版本 D)

Hello.h
cpp 复制代码
#pragma once

#include <vendor/custom/hello_hidl/1.0/IHello.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>

#include <string>
#include <mutex>
#include <thread>
#include <atomic>
#include <map>

namespace vendor::custom::hello_hidl::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;

struct Hello : public V1_0::IHello {

    friend class ClientDeathRecipient;

    Hello();
    ~Hello();

    // 同步方法
    Return<uint32_t> calculate_plus(uint32_t a, uint32_t b) override;
    Return<uint32_t> write(const hidl_string& name) override;
    Return<void> read(read_cb _hidl_cb) override;

    // 订阅方法
    Return<V1_0::StatusCode> subscribe(
        const sp<V1_0::IHelloCallback>& callback) override;
    Return<V1_0::StatusCode> unsubscribe(
        const sp<V1_0::IHelloCallback>& callback) override;

private:
    std::string mStoredString;

    // 单个订阅信息
    struct Subscription {
        sp<V1_0::IHelloCallback> callback;
        sp<::android::hardware::hidl_death_recipient> deathRecipient;
    };

    // 所有活跃订阅,key = 客户端 PID
    std::map<pid_t, Subscription> mSubscriptions;
    std::mutex mSubscriptionMutex;    // 保护 map

    // 通知线程
    std::thread mNotifyThread;
    std::atomic<bool> mThreadRunning{false};

    uint64_t mTemperature{0};
    uint64_t mPressure{0};

    void notificationLoop();

    // 死亡通知触发:清理指定 PID 的订阅
    void onClientDied(pid_t pid);

    // 线程管理(需在锁外调用)
    void startNotifyThreadIfNeeded();
    void stopNotifyThreadIfEmpty();
};

}  // namespace vendor::custom::hello_hidl::implementation
Hello.cpp
cpp 复制代码
#include "Hello.h"

#define LOG_TAG "HelloHAL"
#include <log/log.h>
#include <chrono>
#include <unistd.h>
#include <hidl/HidlSupport.h>
#include <hwbinder/IPCThreadState.h>

namespace vendor::custom::hello_hidl::implementation {

using ::android::hardware::hidl_string;
using ::android::hardware::IPCThreadState;

// ============================================================
// 死亡通知 --- 单个客户端挂掉时,只移除它自己的订阅
// ============================================================
class ClientDeathRecipient : public ::android::hardware::hidl_death_recipient {
    Hello* mHello;
    pid_t mPid;
public:
    ClientDeathRecipient(Hello* hello, pid_t pid)
        : mHello(hello), mPid(pid) {}

    void serviceDied(uint64_t /*cookie*/,
                     const ::android::wp<::android::hidl::base::V1_0::IBase>& /*who*/)
                     override {
        ALOGI("ClientDeathRecipient: pid=%d died, auto-cleanup",
              static_cast<int>(mPid));
        if (mHello) {
            mHello->onClientDied(mPid);
        }
    }
};

// ============================================================
// 构构 / 析构
// ============================================================
Hello::Hello() { ALOGI("Hello HAL instance created"); }

Hello::~Hello() {
    mThreadRunning = false;
    if (mNotifyThread.joinable()) {
        mNotifyThread.join();
    }
    ALOGI("Hello HAL instance destroyed");
}

// ============================================================
// 同步方法(不变)
// ============================================================
Return<uint32_t> Hello::calculate_plus(uint32_t a, uint32_t b) {
    uint32_t total = a + b;
    ALOGI("calculate_plus: %u + %u = %u", a, b, total);
    return total;
}

Return<uint32_t> Hello::write(const hidl_string& name) {
    mStoredString = name.c_str();
    ALOGI("write: stored \"%s\"", mStoredString.c_str());
    return 0;
}

Return<void> Hello::read(read_cb _hidl_cb) {
    ALOGI("read: returning \"%s\"", mStoredString.c_str());
    _hidl_cb(mStoredString);
    return Void();
}

// ============================================================
// subscribe(多客户端)
// ============================================================
Return<V1_0::StatusCode> Hello::subscribe(
    const sp<V1_0::IHelloCallback>& callback) {

    if (callback == nullptr) {
        ALOGE("subscribe: callback is null");
        return V1_0::StatusCode::ERROR;
    }

    pid_t pid = IPCThreadState::self()->getCallingPid();

    // 阶段 1:持锁 --- 注册回调 + 死亡通知
    {
        std::lock_guard<std::mutex> lock(mSubscriptionMutex);

        if (mSubscriptions.count(pid) > 0) {
            ALOGE("subscribe: pid=%d already subscribed",
                  static_cast<int>(pid));
            return V1_0::StatusCode::ERROR;
        }

        Subscription sub;
        sub.callback = callback;
        sub.deathRecipient = new ClientDeathRecipient(this, pid);

        bool linked = callback->linkToDeath(sub.deathRecipient, 0);
        if (!linked) {
            ALOGE("subscribe: pid=%d linkToDeath failed, "
                  "client may already be dead", static_cast<int>(pid));
            return V1_0::StatusCode::ERROR;
        }

        mSubscriptions[pid] = sub;
        ALOGI("subscribe: pid=%d added (total subscribers=%zu)",
              static_cast<int>(pid), mSubscriptions.size());
    }  // ← 锁释放

    // 阶段 2:无锁 --- 首次订阅时启动线程
    startNotifyThreadIfNeeded();

    return V1_0::StatusCode::OK;
}

// ============================================================
// unsubscribe(多客户端)
// ============================================================
Return<V1_0::StatusCode> Hello::unsubscribe(
    const sp<V1_0::IHelloCallback>& /*callback*/) {

    pid_t pid = IPCThreadState::self()->getCallingPid();

    // 阶段 1:持锁 --- 解绑死亡通知 + 移除订阅
    {
        std::lock_guard<std::mutex> lock(mSubscriptionMutex);

        auto it = mSubscriptions.find(pid);
        if (it == mSubscriptions.end()) {
            ALOGE("unsubscribe: pid=%d not subscribed",
                  static_cast<int>(pid));
            return V1_0::StatusCode::ERROR;
        }

        if (it->second.deathRecipient != nullptr
            && it->second.callback != nullptr) {
            it->second.callback->unlinkToDeath(it->second.deathRecipient);
        }

        mSubscriptions.erase(it);
        ALOGI("unsubscribe: pid=%d removed (total subscribers=%zu)",
              static_cast<int>(pid), mSubscriptions.size());
    }  // ← 锁释放

    // 阶段 2:无锁 --- 无人订阅时停止线程
    stopNotifyThreadIfEmpty();

    return V1_0::StatusCode::OK;
}

// ============================================================
// onClientDied:死亡通知处理(只移除指定 PID)
// ============================================================
void Hello::onClientDied(pid_t pid) {
    {
        std::lock_guard<std::mutex> lock(mSubscriptionMutex);
        auto it = mSubscriptions.find(pid);
        if (it == mSubscriptions.end())
            return;  // 已经清理过了

        // deathRecipient 会自毁,不需要手动 unlink
        mSubscriptions.erase(it);
        ALOGI("onClientDied: pid=%d auto-removed "
              "(total subscribers=%zu)",
              static_cast<int>(pid), mSubscriptions.size());
    }  // ← 锁释放

    stopNotifyThreadIfEmpty();
}

// ============================================================
// 线程管理
// ============================================================
void Hello::startNotifyThreadIfNeeded() {
    if (!mThreadRunning) {
        if (mNotifyThread.joinable())
            mNotifyThread.join();
        mThreadRunning = true;
        mTemperature = 0;
        mPressure = 0;
        mNotifyThread = std::thread(&Hello::notificationLoop, this);
        ALOGI("subscribe: notification thread started");
    }
}

void Hello::stopNotifyThreadIfEmpty() {
    {
        std::lock_guard<std::mutex> lock(mSubscriptionMutex);
        if (!mSubscriptions.empty())
            return;  // 还有其他订阅者,不停线程
    }

    mThreadRunning = false;
    if (mNotifyThread.joinable()) {
        mNotifyThread.join();
        ALOGI("unsubscribe: notification thread stopped (no subscribers)");
    }
}

// ============================================================
// 推送循环(遍历所有订阅者)
// ============================================================
void Hello::notificationLoop() {
    ALOGI("notificationLoop: started, pushing every 1 second");

    while (mThreadRunning) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        if (!mThreadRunning) break;

        V1_0::TemperatureAndPressure value;
        {
            // 只持锁读取计数器(短时间锁)
            value.temperature = mTemperature;
            value.pressure = mPressure;
        }

        // 持锁遍历所有订阅,逐个推送
        std::lock_guard<std::mutex> lock(mSubscriptionMutex);
        for (auto& entry : mSubscriptions) {
            pid_t pid = entry.first;
            auto& sub = entry.second;

            if (sub.callback != nullptr && mThreadRunning) {
                ALOGI("notificationLoop: push to pid=%d: t=%llu p=%llu",
                      static_cast<int>(pid),
                      (unsigned long long)value.temperature,
                      (unsigned long long)value.pressure);
                // oneway ------ 任一客户端挂掉也不影响其他
                sub.callback->onValueSet(value);
            }
        }

        mTemperature++;
        mPressure++;
    }

    // 线程退出清理
    {
        std::lock_guard<std::mutex> lock(mSubscriptionMutex);
        mTemperature = 0;
        mPressure = 0;
    }

    ALOGI("notificationLoop: stopped");
}

}  // namespace vendor::custom::hello_hidl::implementation

5.5 关键机制总结

机制 说明
按 PID 区分 subscribe()getCallingPid()mSubscriptions[pid] = {...}
取消匹配 unsubscribe()getCallingPid()mSubscriptions.erase(pid)
死亡隔离 ClientDeathRecipient(pid).serviceDied()onClientDied(pid) → 只 erase 该 pid
oneway 安全 onValueSet()oneway,某个客户端挂掉不影响其他客户端的投递
线程启停 首次 subscribe 启动,最后一个 unsubscribe 或死亡时停止

5.6 多客户端验证场景

假设三个进程同时订阅:

ini 复制代码
时间轴:
  t=0s  system_server (pid=1001)  → subscribe → OK
  t=2s  DiagApp      (pid=5678)  → subscribe → OK
  t=5s  TestApp      (pid=9012)  → subscribe → OK
        ↓ 三方都收到推送 (t=0,p=0) (t=1,p=1) ...
  t=10s 用户杀掉 TestApp
        ↓ ClientDeathRecipient(pid=9012) 触发
        ↓ onClientDied(9012) → erase 9012
        ↓ 1001 和 5678 继续收推送 ✅
  t=15s DiagApp 调 unsubscribe
        ↓ erase 5678,只剩 1 个
  t=20s system_server 调 unsubscribe
        ↓ map 为空,notificationLoop 停止 ✅

本章小结

第 5 章完成了从单客户端到多客户端的演进:

  • std::map<pid_t, Subscription> 替代单个 mCallback
  • 每个 Subscription 有独立的 callback + deathRecipient
  • getCallingPid() 区分客户端,死亡通知只影响对应 PID
  • oneway 保证某客户端挂掉不影响其他
  • 线程启停规则:首个订阅启,最后离开停

经过"基础版 → 踩坑 → 修复 → 多客户端"四轮迭代,Hello 的实现已经比较完整了。下一章把最终完整代码汇总在一起,方便整体对照。


📝 下一章 :[第 6 章 完整版代码汇总](#第 6 章 完整版代码汇总 "#%E7%AC%AC-6-%E7%AB%A0-%E5%AE%8C%E6%95%B4%E7%89%88%E4%BB%A3%E7%A0%81%E6%B1%87%E6%80%BB")

由于文章太大了,所以拆分到下篇了。

9.5 参考资料


相关推荐
plainGeekDev2 小时前
广播接收器 → Flow + Lifecycle
android·java·kotlin
plainGeekDev2 小时前
EventBus → SharedFlow
android·java·kotlin
37手游移动客户端团队1 天前
招聘-高级安卓开发工程师
android·客户端
用户41659673693551 天前
WebView 请求异常排查操作手册
android·前端
Kapaseker1 天前
学不动了,入门 Compose Styles API
android·kotlin
墨狂之逸才2 天前
Android TV WebView 遥控器按键处理:从全透传到白名单
android
plainGeekDev2 天前
MVC 写法 → MVVM
android·java·kotlin
恋猫de小郭2 天前
Flutter Patchwork,不用 Fork 改依赖包源码的第三方工具
android·前端·flutter
三少爷的鞋2 天前
“结构化”这个词,本质上就是——把混乱的东西变成有组织、有规则、有边界的东西
android