第二十一章:硬件抽象层(HAL)探秘:Android传感器HAL接口、如何查看设备传感器HAL实现

说到Android的传感器框架,很多人会盯着上层的应用开发,或者研究Framework层的代码流转。但我个人觉得,真正有意思的地方,其实是硬件抽象层——也就是HAL。为什么?因为它是连接Android系统和底层硬件的那座桥。桥修得好不好,直接决定了你的距离传感器灵不灵、省不省电。

这一章,我们就来扒一扒传感器HAL的底裤。我会带你看看它的接口定义,再手把手教你如何查看自己手机上的HAL实现。

HAL在传感器架构中的位置

先看一张图,你就明白HAL到底站在哪了。

应用层 (App) Framework层 (SensorManager) 硬件抽象层 (HAL) — 本章核心 内核驱动层 (Kernel Driver) 物理传感器硬件 数据流方向

你看,HAL层夹在Framework和内核驱动之间。Framework通过JNI调用到HAL,HAL再通过ioctl或者sysfs跟内核驱动打交道。说白了,HAL就是Android系统给硬件厂商留的一个“标准化接口”。

核心观点:HAL的存在,让Google不用关心你用的是哪家的传感器芯片。只要你的HAL实现了规定的接口,Android系统就能正常使用你的传感器。

传感器HAL接口长什么样?

Android的传感器HAL接口定义在 hardware/libhardware/include/hardware/sensors.h 这个头文件里。我当年第一次打开这个文件的时候,说实话有点懵——结构体套结构体,回调函数满天飞。但别怕,我们拆开来看。

核心结构体:sensors_module_t

每个HAL模块都要暴露一个 sensors_module_t 结构体。它长这样:

typedef struct sensors_module_t {
    struct hw_module_t common;
    
    /* 获取传感器列表 */
    int (*get_sensors_list)(struct sensors_module_t* module,
                            struct sensor_t const** list);
} sensors_module_t;

嗯,代码不多,但信息量很大。这个结构体里最重要的就是 get_sensors_list 函数指针。它返回一个 sensor_t 数组,里面描述了设备上所有可用的传感器信息。

我的经验:我在调试一款国产手机的距离传感器时,发现get_sensors_list返回的列表里居然没有距离传感器。查了半天,原来是HAL实现里把距离传感器类型写成了“PROXIMITY_RAW”,而不是标准的“SENSOR_TYPE_PROXIMITY”。这种坑,你不看HAL代码根本发现不了。

核心结构体:sensor_t

每个传感器的描述信息,都放在 sensor_t 里:

typedef struct sensor_t {
    const char*     name;       // 传感器名称
    const char*     vendor;     // 供应商
    int             version;    // 版本
    int             handle;     // 句柄,用于标识传感器
    int             type;       // 类型,比如距离传感器是8
    float           maxRange;   // 最大量程
    float           resolution; // 分辨率
    float           power;      // 功耗,单位mA
    int32_t         minDelay;   // 最小采样周期,单位微秒
    uint32_t        fifoReservedEventCount; // FIFO保留事件数
    uint32_t        fifoMaxEventCount;       // FIFO最大事件数
    const char*     stringType; // 字符串类型
    const char*     requiredPermission; // 所需权限
    int32_t         maxDelay;   // 最大采样周期
    uint32_t        flags;      // 标志位
    void*           reserved[2];// 保留字段
} sensor_t;

这里面的字段,每一个都对应着上层 Sensor 类的属性。比如 maxRange 对应距离传感器的最大检测距离,minDelay 对应最快能多快上报数据。

核心结构体:sensors_poll_device_t

有了传感器列表,接下来就是怎么操作传感器了。这个操作接口定义在 sensors_poll_device_t 里:

typedef struct sensors_poll_device_t {
    struct hw_device_t common;
    
    /* 激活/去激活传感器 */
    int (*activate)(struct sensors_poll_device_t* dev,
                    int handle, int enabled);
    
    /* 设置传感器延迟 */
    int (*setDelay)(struct sensors_poll_device_t* dev,
                    int handle, int64_t period_ns);
    
    /* 轮询传感器数据 */
    int (*poll)(struct sensors_poll_device_t* dev,
                sensors_event_t* data, int count);
} sensors_poll_device_t;

这三个函数,就是HAL的灵魂:

  • activate:打开或关闭某个传感器。传入handle和enable标志。
  • setDelay:设置采样频率。注意单位是纳秒。
  • poll:阻塞式读取传感器数据。Framework会开一个线程不断调用它。

注意:poll函数是阻塞的。如果传感器没有新数据,它会一直等在那里。我曾经见过一个实现,poll函数里直接死循环了,导致整个传感器服务卡死。嗯,这种bug排查起来特别痛苦。

如何查看自己设备的传感器HAL实现?

理论说完了,我们来点实操。怎么看到你手机上的传感器HAL代码?

首先,你要明白一件事:HAL是编译成动态库的。在Android系统里,传感器HAL的so文件通常叫 sensors.<设备名>.so。比如:

  • sensors.msm8998.so — 高通平台
  • sensors.exynos9810.so — 三星平台
  • sensors.mt6765.so — 联发科平台

这些so文件放在 /vendor/lib/hw/ 或者 /system/lib/hw/ 目录下。你可以用adb连上手机,直接查看:

adb shell
ls -l /vendor/lib/hw/sensors.*.so

如果能看到文件,恭喜你,你的设备有传感器HAL实现。如果看不到……嗯,可能是厂商把传感器做进了别的模块里,或者你的手机太老了。

反编译HAL库

光看文件名不够,我们得看看里面到底实现了什么。用 readelf 可以查看导出的符号:

adb shell
readelf -s /vendor/lib/hw/sensors.xxx.so | grep -E "sensors_module|activate|poll|setDelay"

你会看到类似这样的输出:

    12: 0000a3e0   132 FUNC    GLOBAL DEFAULT    8 sensors_module
    45: 0000b210    56 FUNC    GLOBAL DEFAULT    8 activate
    67: 0000b2a0    88 FUNC    GLOBAL DEFAULT    8 poll
    89: 0000b1c0    44 FUNC    GLOBAL DEFAULT    8 setDelay

看到这些符号,说明HAL实现了标准的接口。如果缺少某个符号,那就有问题了。

我的习惯:拿到一台新设备,我第一件事就是跑这个命令。如果发现poll函数没有导出,那这个设备要么是用了旧的HAL版本,要么就是厂商自己改了接口。这两种情况,上层应用都可能遇到奇怪的问题。

查看HAL源码

如果你有设备的源码(比如AOSP或者厂商发布的kernel源码),那就可以直接看源代码了。传感器HAL的源码通常放在:

hardware/<厂商>/sensors/
或者
device/<厂商>/<设备名>/sensors/

举个例子,Pixel设备的传感器HAL源码在:

hardware/google/pixel/sensors/

里面一般会有 SensorBase.cppSensorDevice.cpp 这样的文件。打开看看,你会发现它们实现了我们上面说的那些接口。

一个典型的HAL实现流程

我画个流程图,帮你理清HAL内部的工作逻辑:

Framework调用HAL activate? 打开传感器设备 关闭传感器设备 poll() 等待数据 返回sensors_event_t

流程其实不复杂:Framework先调用activate打开传感器,然后不断调用poll获取数据。数据以 sensors_event_t 结构体返回,里面包含了距离值、时间戳等信息。

距离传感器的HAL实现细节

具体到距离传感器,HAL实现里有个关键点:近程/远程状态的判断。距离传感器通常返回两个值:距离(厘米)和最大量程。但上层应用更关心的是“靠近”还是“远离”。

这个判断逻辑,其实是在HAL里做的。看一段伪代码:

// 距离传感器HAL实现片段
int proximity_poll(sensors_event_t* data) {
    // 从驱动读取原始值
    int raw_value = read_from_driver();
    
    // 判断近程/远程
    if (raw_value < PROXIMITY_THRESHOLD) {
        data->distance = 0.0f;  // 靠近
    } else {
        data->distance = maxRange;  // 远离
    }
    
    data->type = SENSOR_TYPE_PROXIMITY;
    data->timestamp = get_current_time();
    
    return 1;  // 返回1个事件
}

这里有个坑:PROXIMITY_THRESHOLD 这个阈值怎么定?不同厂商的传感器硬件不一样,有的用5cm,有的用3cm。我见过一个设备,阈值设得太低,导致用户打电话时屏幕关不掉——因为传感器永远认为“远离”。

避坑指南:我曾经调试过一个项目,距离传感器在通话时偶尔闪屏。查了三天,最后发现是HAL里的阈值判断用了整数比较,而驱动返回的是浮点数。精度丢失导致状态翻转。嗯,从那以后我写HAL代码,所有比较都加上了epsilon容差。

总结一下

传感器HAL其实没那么神秘。它就是一组标准接口,让Android系统能统一操作不同厂商的传感器硬件。核心就三个函数:get_sensors_listactivatepoll。再加上一个 setDelay 控制采样频率。

想查看自己设备的HAL实现?用adb连上去,找 /vendor/lib/hw/sensors.*.so,然后用 readelf 看看导出的符号。如果有源码,直接翻 hardware/ 目录下的代码。

搞懂了HAL,你就掌握了传感器调试的主动权。下次遇到距离传感器不灵的问题,别光盯着上层代码看——去HAL里翻一翻,说不定问题就在那里等着你。


公众号:蓝海资料掘金营,微信deep3321