Android fw BatteryService

目录

一、应用层BatteryManager

1、电池状态广播

2、电池服务接口

二、框架层BatteryService

1、BatteryService初始化

[1.1 battery服务能力](#1.1 battery服务能力)

[1.2 batteryproperties服务能力](#1.2 batteryproperties服务能力)

2、BatteryService与HAL层通信

[2.1 HealthServiceWrapper装饰类](#2.1 HealthServiceWrapper装饰类)

[2.2 BatteryService::update方法何去何从?](#2.2 BatteryService::update方法何去何从?)

3、processValuesLocked数据处理

[3.1 force参数到底有何意义?](#3.1 force参数到底有何意义?)

[3.2 核心流程解读](#3.2 核心流程解读)

[3.3 关机检测](#3.3 关机检测)

[3.4 ACTION_BATTERY_CHANGED广播事件](#3.4 ACTION_BATTERY_CHANGED广播事件)

三、Linux电源子系统

[1、power_supply 子系统](#1、power_supply 子系统)

[1.1 uevent是什么?](#1.1 uevent是什么?)

[1.2 power_supply 触发场景](#1.2 power_supply 触发场景)

2、Linux事件驱动机制

[2.1 linux epoll 多路复用机制](#2.1 linux epoll 多路复用机制)

[2.2 linux timerfd 定时器](#2.2 linux timerfd 定时器)

3、libhealthloop

[3.1 HealthLoop开启轮询](#3.1 HealthLoop开启轮询)

[3.2 InitInternal事件初始化](#3.2 InitInternal事件初始化)

[3.3 MainLoop事件监听和轮询](#3.3 MainLoop事件监听和轮询)

四、HAL层IHealth.hal

[1、Health AIDL](#1、Health AIDL)

[1.1 HalHealthLoop的主函数](#1.1 HalHealthLoop的主函数)

[1.2 HalHealthLoop的定义](#1.2 HalHealthLoop的定义)

[1.3 HealthInfo透传到上层](#1.3 HealthInfo透传到上层)

[2、Health HIDL](#2、Health HIDL)

3、libbatterymonitor

[3.1 HAL如何调到BattertyMonitor?](#3.1 HAL如何调到BattertyMonitor?)

[3.2 BatteryMonitor::updateValues读取节点](#3.2 BatteryMonitor::updateValues读取节点)


Android 设备上,一个应用在状态栏看到的电量百分比、充电状态、电池健康、电压、温度,最终都来自物理电池的电量计(Fuel Gauge / Gauge IC)。但应用并不直接访问硬件,电量数据自下而上要穿过四层软件:

  • Linux 内核 power_supply 子系统
  • 原生守护进程(healthd)或其演进形态 Health HAL
  • 框架层BatteryService
  • 应用层BatteryManager

本篇就是以BatteryService为切入点,来剖析整个电池电量相关数据和信息的调用链。

一、应用层BatteryManager

应用apk是如何获取电池电量相关信息呢?统一通过 android.os.BatteryManager进行获取,但实际上是有两者方式:粘性广播ACTION_BATTERY_CHANGED和 BatteryManage接口。

1、电池状态广播

这是最常用、最省电的方式。它在 Intent 里定义为 Intent.ACTION_BATTERY_CHANGED,由系统 BatteryService 发出,属于粘性系统广播,只能动态注册接收,不能在 Manifest 里静态注册。因为是粘性广播即可立即拿到最近一次电池快照;也可注册真实 receiver 监听后续变化。

java 复制代码
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
// null 直接取粘性广播
Intent batteryStatus = context.registerReceiver(null, ifilter); 
 
int level    = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale    = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float pct    = level * 100f / scale;  // 电量百分比
int status   = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean charging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
int plugged  = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
int health   = batteryStatus.getIntExtra(BatteryManager.EXTRA_HEALTH, -1);
boolean present = batteryStatus.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false);
int temp     = batteryStatus.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1); 
int voltage  = batteryStatus.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1);     
String tech  = batteryStatus.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);

ACTION_BATTERY_CHANGED广播通过intent的方式把电池相关数据传递给应用,其字段的定义BatteryManager早已经做好:

|---------------------------|-------------|------------------------------------------------|
| Extra 常量 | 含义 | 单位/取值 |
| EXTRA_LEVEL / EXTRA_SCALE | 当前电量 / 满格基准 | level/scale*100 当前电量百分比 |
| EXTRA_STATUS | 充电状态 | CHARGING DISCHARGING NOT_CHARGING FULL UNKNOWN |
| EXTRA_PLUGGED | 供电类型 | 0 不插电 1 AC 2 USB 4 WIRELESS |
| EXTRA_HEALTH | 电池健康 | GOOD OVERHEAT DEAD OVER_VOLTAGE COLD ... |
| EXTRA_PRESENT | 电池在位 | boolean |
| EXTRA_TEMPERATURE | 温度 | 0.1°C |
| EXTRA_VOLTAGE | 电压 | mV |
| EXTRA_TECHNOLOGY | 化学类型 | 如 Li-ion |
| EXTRA_ICON_SMALL | 状态栏图标资源 | int 资源 id |

2、电池服务 接口

BatteryManager 还提供面向硬件电量计原始量的同步查询接口,主要接口就是getIntProperty 和 getLongProperty。此种方式适合需要瞬时电流、平均电流、剩余电荷等数值的场景(如功耗分析、续航估算)

java 复制代码
BatteryManager bm = context.getSystemService(BatteryManager.class);
// 百分比
long cap   = bm.getLongProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);    
// 瞬时电流 uA    
long now   = bm.getLongProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW);   
// 平均电流 uA
long avg   = bm.getLongProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE);
// 剩余电荷 uAh
long charge= bm.getLongProperty(BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER); 
// 充电状态
long status= bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS);          
// 估算续航:剩余电荷 / 平均放电电流
long remainingUs = (charge * 3600L * 1000L) / Math.abs(avg);

此种方式为FW常规流程,通过电量管理BatteryManager对象调用BatteryService的接口获取一些定义好的属性数据。AOSP原生支持如下属性:

|----------------------------------|---------------|--------|
| 属性常量 | 含义 | 单位 |
| BATTERY_PROPERTY_CAPACITY | 剩余电量百分比 | % |
| BATTERY_PROPERTY_CURRENT_NOW | 瞬时电流(正负方向随平台) | µA |
| BATTERY_PROPERTY_CURRENT_AVERAGE | 平均电流 | µA |
| BATTERY_PROPERTY_CHARGE_COUNTER | 剩余电荷量 | µAh |
| BATTERY_PROPERTY_STATUS | 充电状态 | |
| BATTERY_PROPERTY_ENERGY_COUNTER | 剩余能量(部分平台) | nWh |

PS:支持的属性会返回Integer.MIN_VALUE或Long.MIN_VALUE,代码中务必先判空/判该哨兵值,不能直接拿来计算。该通道每次都是一次到 HAL 的同步调用,不要高频轮询。

二、框架层BatteryService

这里就不详细介绍android系统的systemserver进程和binder通信了,有需要可以参考Android Binder 系列专题。

BatteryService是电池数据进入java世界后的唯一汇聚点,对上向 BatteryManager 提供 Binder 接口,对下通过 HealthServiceConnection 绑定 vendor 的 Health HAL主要核心逻辑如下:

复制代码
Health HAL ──callback──▶ update(info) ──▶ processValuesLocked()  ← 核心状态机
                                              │
        ┌─────────────────────────────────────┼──────────────────────────┐
        ▼                    ▼                ▼                          ▼
  关机/警告判断        BatteryStats        LED(updateLightsLocked)   广播三件套
  shouldShutdown    setBatteryState                              BATTERY_CHANGED(粘性)
                                                                 POWER_CONNECTED/DISCONNECTED
                                                                 BATTERY_LOW/OKAY
                                                                 BATTERY_LEVEL_CHANGED(节流批处理)

如上流程图,BatteryService做的最重要的事情就是接收来自Health HAL层的回调,经过update传递HealthInfo电池信息数据,最终统一在processValuesLocked进行处理。处理数据的时候顺便做了关机逻辑、电池状态的设置、充电LED亮灭逻辑,当然最重要的还是把电池变化的数据通过广播的方式传递出去。

1、BatteryService初始化

BatteryService继承SystemService,运行在 system_server 进程,由 SystemServer 在启动阶段创建并加入服务注册表(battery)。

如上代码在onStart的时候像servicemanager进程注册了battery和batteryproperties两个服务,以供系统其他进程或应用提供能力。

1.1 battery服务能力

publishBinderService("battery", new BinderService())是对系统提供了battery相关能力,我们看看主要实现了哪些接口?参考BinderService类的定义:

1)onShellCommand

onShellCommand是接收来之adb shell的命令,实例化shell进行exec相关指令。主要的命令和参数如下,即支持通过get set的方式去动态修改当前电池健康相关的数据

java 复制代码
   int onShellCommand(Shell shell, String cmd) {
        if (cmd == null) {
            return shell.handleDefaultCommands(cmd);
        }
        PrintWriter pw = shell.getOutPrintWriter();
        switch (cmd) {
            case "unplug": {
                int opts = parseOptions(shell);
                getContext().enforceCallingOrSelfPermission(
                        android.Manifest.permission.DEVICE_POWER, null);
                unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw);
            } break;
            case "get": {
                final int opts = parseOptions(shell);
                final String key = shell.getNextArg();
                if (key == null) {
                    pw.println("No property specified");
                    return -1;
                }

                // Update the health info.
                if ((opts & OPTION_FORCE_UPDATE) != 0) {
                    mConditionVariable.close();
                    updateHealthInfo();
                    mConditionVariable.block(HEALTH_HAL_WAIT_MS);
                }

                switch (key) {
                    case "present":
                        pw.println(mHealthInfo.batteryPresent);
                        break;
                    case "ac":
                        pw.println(mHealthInfo.chargerAcOnline);
                        break;
                    case "usb":
                        pw.println(mHealthInfo.chargerUsbOnline);
                        break;
                    case "wireless":
                        pw.println(mHealthInfo.chargerWirelessOnline);
                        break;
                    case "dock":
                        pw.println(mHealthInfo.chargerDockOnline);
                        break;
                    case "status":
                        pw.println(mHealthInfo.batteryStatus);
                        break;
                    case "level":
                        pw.println(mHealthInfo.batteryLevel);
                        break;
                    case "counter":
                        pw.println(mHealthInfo.batteryChargeCounterUah);
                        break;
                    case "current_now":
                        pw.println(mHealthInfo.batteryCurrentMicroamps);
                        break;
                    case "current_average":
                        pw.println(mHealthInfo.batteryCurrentAverageMicroamps);
                        break;
                    case "temp":
                        pw.println(mHealthInfo.batteryTemperatureTenthsCelsius);
                        break;
                    case "invalid":
                        pw.println(mInvalidCharger);
                        break;
                    default:
                        pw.println("Unknown get option: " + key);
                        break;
                }
            } break;
            case "set": {
                int opts = parseOptions(shell);
                getContext().enforceCallingOrSelfPermission(
                        android.Manifest.permission.DEVICE_POWER, null);
                final String key = shell.getNextArg();
                if (key == null) {
                    pw.println("No property specified");
                    return -1;

                }
                final String value = shell.getNextArg();
                if (value == null) {
                    pw.println("No value specified");
                    return -1;

                }
                try {
                    if (!mUpdatesStopped) {
                        copyV1Battery(mLastHealthInfo, mHealthInfo);
                    }
                    boolean update = true;
                    switch (key) {
                        case "present":
                            mHealthInfo.batteryPresent = Integer.parseInt(value) != 0;
                            break;
                        case "ac":
                            mHealthInfo.chargerAcOnline = Integer.parseInt(value) != 0;
                            break;
                        case "usb":
                            mHealthInfo.chargerUsbOnline = Integer.parseInt(value) != 0;
                            break;
                        case "wireless":
                            mHealthInfo.chargerWirelessOnline = Integer.parseInt(value) != 0;
                            break;
                        case "dock":
                            mHealthInfo.chargerDockOnline = Integer.parseInt(value) != 0;
                            break;
                        case "status":
                            mHealthInfo.batteryStatus = Integer.parseInt(value);
                            break;
                        case "level":
                            mHealthInfo.batteryLevel = Integer.parseInt(value);
                            break;
                        case "counter":
                            mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value);
                            break;
                        case "current_now":
                            mHealthInfo.batteryCurrentMicroamps = Integer.parseInt(value);
                            break;
                        case "current_average":
                            mHealthInfo.batteryCurrentAverageMicroamps = Integer.parseInt(value);
                            break;
                        case "temp":
                            mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value);
                            break;
                        case "invalid":
                            mInvalidCharger = Integer.parseInt(value);
                            break;
                        default:
                            pw.println("Unknown set option: " + key);
                            update = false;
                            break;
                    }
                    if (update) {
                        final long ident = Binder.clearCallingIdentity();
                        try {
                            mUpdatesStopped = true;
                            processValuesLocked(
                                    /* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw);
                        } finally {
                            Binder.restoreCallingIdentity(ident);
                        }
                    }
                } catch (NumberFormatException ex) {
                    pw.println("Bad value: " + value);
                    return -1;
                }
            } break;
            case "reset": {
                int opts = parseOptions(shell);
                getContext().enforceCallingOrSelfPermission(
                        android.Manifest.permission.DEVICE_POWER, null);
                resetBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw);
            } break;
            case "suspend_input": {
                getContext().enforceCallingOrSelfPermission(
                        android.Manifest.permission.DEVICE_POWER, null);
                suspendBatteryInput();
            } break;
            default:
                return shell.handleDefaultCommands(cmd);
        }
        return 0;
    }
2)dumpInternal

dump就是我们经常需要用到的调试指令adb shell dumpsys battery:

  • 代码如下:
java 复制代码
    private void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) {
        synchronized (mLock) {
            if (args == null || args.length == 0 || "-a".equals(args[0])) {
                pw.println("Current Battery Service state:");
                if (mUpdatesStopped) {
                    pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
                }
                pw.println("  AC powered: " + mHealthInfo.chargerAcOnline);
                pw.println("  USB powered: " + mHealthInfo.chargerUsbOnline);
                pw.println("  Wireless powered: " + mHealthInfo.chargerWirelessOnline);
                pw.println("  Dock powered: " + mHealthInfo.chargerDockOnline);
                pw.println("  Max charging current: " + mHealthInfo.maxChargingCurrentMicroamps);
                pw.println(" Time when the latest updated value of the Max charging current was"
                        + " sent via battery changed broadcast: "
                        + TimeUtils.formatDuration(mLastBroadcastMaxChargingCurrentUpdateTime));
                pw.println("  Max charging voltage: " + mHealthInfo.maxChargingVoltageMicrovolts);
                pw.println("  Charge counter: " + mHealthInfo.batteryChargeCounterUah);
                pw.println("  status: " + mHealthInfo.batteryStatus);
                pw.println("  health: " + mHealthInfo.batteryHealth);
                pw.println("  present: " + mHealthInfo.batteryPresent);
                pw.println("  level: " + mHealthInfo.batteryLevel);
                pw.println("  scale: " + BATTERY_SCALE);
                pw.println("  voltage: " + mHealthInfo.batteryVoltageMillivolts);
                pw.println(" Time when the latest updated value of the voltage was sent via "
                        + "battery changed broadcast: "
                        + TimeUtils.formatDuration(mLastBroadcastVoltageUpdateTime));
                pw.println(" The last voltage value sent via the battery changed broadcast: "
                        + mLastBroadcastBatteryVoltage);
                pw.println("  temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
                pw.println("  technology: " + mHealthInfo.batteryTechnology);
                pw.println("  Charging state: " + mHealthInfo.chargingState);
                pw.println("  Charging policy: " + mHealthInfo.chargingPolicy);
                pw.println("  Capacity level: " + mHealthInfo.batteryCapacityLevel);
            } else {
                Shell shell = new Shell();
                shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
            }
        }
    }
  • dumpsys battery输出如下:
bash 复制代码
C:\Users\Administrator>adb shell dumpsys battery
Current Battery Service state:
  AC powered: false
  USB powered: true
  Wireless powered: false
  Dock powered: false
  Max charging current: 500000
  Max charging voltage: 5000000
  Charge counter: 2386260
  status: 2
  health: 2
  present: false
  level: 81
  scale: 100
  voltage: 4201
  temperature: 280
  technology: Li-ion

1.2 batteryproperties服务能力

publishBinderService("batteryproperties", new BatteryPropertiesRegistrar())是对应用提供了获取电池状态/电量信息的属性的能力,即前文介绍到的mBatteryManager.getIntProperty接口,实际上最终通过mHealthServiceWarapper装饰类从HAL层中获取数据,具体等后续详解。

2、BatteryService与HAL层通信

  • 前面已经介绍到了,无论是应用层持有的BatteryManager,还是systemserver进程持有的BatteryService,他们都不直接与驱动或者硬件直接进行通信。
  • 在Android 8(Treble化)之前BatteryService通过JNI的方式与system/core/healthd 守护进程进行沟通,由healthd守护进程从驱动节点获取数据。
  • 在Android 8(Treble化)之后BatteryService通过HIDL/AIDL方式与Health HAL进行沟通,取数逻辑被收口进vendor侧Health HAL,healthd 退化为 charger 模式(关机充电动画)与一套可复用库 libhealthloop/libhealthd。
  • 本章就看看treble化之后BatteryService是如何与Health HAL层进行交互的。

2.1 HealthServiceWrapper装饰类

  • Health HAL层存在的目的就是为了让硬件/平台/ODM产商更加友好的进行定制,但是第三方的定制势必对AOSP的升级造成影响,因此FW中XXXService.java的通用设计,就是对XXXHal层接口进行一个封装,最合理的设计模式就是装饰模式。
  • BatteryService的内部也是这样设计的,他有一个成员变量mHealthServiceWrapper,他就是对health hal进程的一个装饰封装,我们看看他是如何被构造的?
  • 如上代码通过静态方法HealthServiceWrapper.create进行构造,这里非常非常重要的就是传递的参数this::update。针对这个方法下一小节重点介绍。
1)AIDL和HIDL自动选择
  • 如上这段逻辑封装,大概意思进行如下几个步骤:
  • 先去实例化HealthServiceWrapperAidl类:实例化的时候会去连接AIDL接口,如果存在异常,表示当前service manager并没有health AIDL的服务接口注册
  • 再去实例化HealthServiceWrapperHidl类:实例化的时候会去连接HIDL接口。
  • 无论是Aidl还是Hidl装饰类在实例化的时候,都会把BatteryService::update方法作为回调接口注册进去。
2)封装了哪些接口?
  • 最后我们在看看BatteryService向下调用HealthServiceWrapper哪些接口?
  • 主要就是scheduleUpdate和getProperty两个接口。

2.2 BatteryService::update方法何去何从?

  • HealthServiceWrapper.create(BatteryService::update)这行代码非常重要,我们先看看update方法到底是个什么玩意?

如上代码,就一个普通函数, 但是他的参数却是一个android.hardware.health.HealthInfo对象,很明显这个对象是一个HIDL或者AIDL接口定义的类,而HAL层就是通过回到update方法,并把电池电量相关信息封装在HealthInfo接口对象中,从HAL层往FW层传递上来。

HealthInfo原本你是HIDL/AIDL接口语言定义的一个数据类型,通过binder通信并进行转换为一个java语言定义的HealthInfo类,这样就实现了HAL层与FW层之间的互通。

1)HealthServiceWrapperAidl封装
  • 我们继续看看BatteryService::update方法是如何被传递到AIDL里面的?
2)HealthServiceWrapperHidl封装
3)HealthInfo数据回调总结

综上所述,无论是AIDL还是HIDL,最终Hal层都会把从驱动节点读取到的电池电量相关数据封装,并通过相关回调机制,传递到BatteryService::update方法,最终移交到 processValuesLocked进行处理。

3、processValuesLocked数据处理

这里先提前总结一下此函数主要干了哪些事情:

  • 判断当前充电状态
  • 判断当前是否需要低电量关机
  • 判断当前数据是否存在变化,存在变化才发送广播事件

3.1 force参数到底有何意义?

我们可以看到主要的核心逻辑处理就是在如下代码,使用force与后续的数据进行或处理:

  • 如果force=true,那么下面的逻辑包括广播事件必须处理
  • 如果force=false,那么判断mHealthInfo数据是否存在变化,如果存在变化,那么条件也满足

回到这个函数调用的三个地方:

  • update:从HAL层回调,频率非常高
  • updateBatteryWarningLevelLocked:低电阈值变化调用,频率
  • 钩子函数:为测试提供的一些测试接口,我们暂时忽略

HAL层回调的地方为什么传递false呢?因为update回调频率非常高,所以这里才传递false,即如果数据存在变化才进行广播事件这样的重要逻辑处理。

低电检查为什么传递true呢?因为低电检查其实是非常重要的,涉及到很多逻辑处理,因此这里传递true,强制发送系统广播事件等重要逻辑处理。

3.2 核心流程解读

步骤1:派生状态计算(L776--790)

mBatteryLevelCritical =
mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
mPlugType = plugType(mHealthInfo);

private static int plugType(HealthInfo healthInfo) { // L742
if (healthInfo.chargerAcOnline) return BATTERY_PLUGGED_AC; // 1
else if (healthInfo.chargerUsbOnline) return BATTERY_PLUGGED_USB; // 2
else if (healthInfo.chargerWirelessOnline) return BATTERY_PLUGGED_WIRELESS; // 4
else if (healthInfo.chargerDockOnline) return BATTERY_PLUGGED_DOCK; // 8
else return BATTERY_PLUGGED_NONE; // 0
}

  • 它把 HAL 的四个布尔量归一化成一个枚举,优先级为 AC > USB > Wireless > Dock。
  • mPlugType 是成员变量,后续广播 extras、插拔事件、低电判断、LED 全部用它。
  • "插着电"(mPlugType != 0)不等于"正在充电"(batteryStatus == CHARGING):满电、温控、充电策略都可能导致插电但不充电。

步骤 2:同步 BatteryStats(L792--806)

mBatteryStats.setBatteryState(
status, // 充放电状态
health, // 健康度
plugType, // 电源类型
level, // 电量百分比
temperatureTenthsCelsius, // 温度(0.1 摄氏度)
voltageMillivolts, // 电压(mV)
chargeCounterUah, // 剩余电量(uAh)
fullChargeUah, // 满电容量(uAh)
chargeTimeToFullNowSeconds); // 预计充满剩余秒数

  • 作用:作为 dumpsys batterystats、Battery Historian 的数据基础,系统据此计算每个 UID、wakelock、硬件的耗电分摊。
  • 它在每次 HAL 上报时都会执行,不受变化检测影响------统计需要连续采样。
  • BatteryStatsService 与 BatteryService 同在 system_server,这个 Binder 接口实际是本地调用,catch (RemoteException) 属于历史遗留写法。

步骤 3:安全兜底(L808--816)

这里执行低电关机与高温关机判断。关键点:这两项在变化检测之前,每次 HAL 上报都会执行,安全逻辑不能因为"数据没变化"而漏判。

private boolean shouldShutdownLocked() { // L615
// HAL 2.1+ 提供 capacityLevel 时,优先采用厂商定义的临界值
if (mHealthInfo.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) {
return mHealthInfo.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL;
}
if (!mShutdownIfNoPower) return false; // 配置开关
if (mHealthInfo.batteryLevel > 0) return false; // 还有电
if (!mHealthInfo.batteryPresent) return false; // 无电池设备不关机
return mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_CHARGING;
}

  • 高温判断:batteryTemperatureTenthsCelsius > config_shutdownBatteryTemperature,默认 68.0 摄氏度。
  • 执行链:构建 ACTION_REQUEST_SHUTDOWN(EXTRA_KEY_CONFIRM=false,reason 为 SHUTDOWN_LOW_BATTERY 或 SHUTDOWN_BATTERY_THERMAL_STATE)-> post 到 Handler -> startShutdownActivity() 检查 AMS 是否就绪、找到持有 SHUTDOWN 权限的 Activity(通常为 SystemUI)-> startActivityAsUser()。

步骤 4:充电策略变更通知(L811--816)

translatedChargingPolicy = translateHalChargingPolicy(mHealthInfo.chargingPolicy);
if (force || translatedChargingPolicy != mLastChargingPolicy) {
mLastChargingPolicy = translatedChargingPolicy;
mHandler.post(this::notifyChargingPolicyChanged);
}

它和电池广播是两条独立通道:策略(自适应 / 长寿命 / 强制充满)变化时不走广播,而是本地监听器回调;BatteryManager.getIntProperty(CHARGING_POLICY) 读的也是这个缓存值。

步骤 5:变化检测闸门(L818--831)

把当前值与"上次真正广播出去的快照 mLastBroadcast*"逐字段比较(status、health、present、level、plug、voltage、temperature、maxCurrent、maxVoltage、invalidCharger、cycleCount、chargingState、capacityLevel),全部相同且 force=false 时直接结束。

这样设计的原因:

  • HAL 回调频率高,若每次回调都广播会形成广播风暴,必须做差异检测;
  • 上层触发(阈值修改、测试命令)时原始数据没有变化,差异检测会误判"无变化",因此需要 force 绕过;
  • force 也保证测试命令一定产生广播并递增 sequence,方便脚本对齐。

步骤 6:统计与事件日志(L833--906)

  • 未插电 -> 插电:记录充电起点,结算上一段放电(metrics ACTION_CHARGE、EventLog BATTERY_DISCHARGE),前提是放电期间电量有变化。
  • 插电 -> 未插电:结算充电时长。
  • status / health / present / plug 变化:写 EventLog BATTERY_STATUS 和 debug.tracing.battery_status、debug.tracing.plug_type 两个 sysprop。
  • level 变化:写 EventLog BATTERY_LEVEL(注释明确:不因电压 / 温度变化写,否则太吵)。
  • 刚进入临界低电且未插电:标记 logOutlier,稍后 logOutlierLocked() 把 batterystats dump 到 DropBox(BATTERY_DISCHARGE_INFO)。

这些数据面向 dumpsys、tracing、功耗分析工具,不面向普通应用。

步骤 7:低电状态机(L908--927)

if (!mBatteryLevelLow) {
// 进入低电:未插电、状态已知、电量 <= 警告阈值
if (mPlugType == BATTERY_PLUGGED_NONE
&& mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
&& mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
mBatteryLevelLow = true;
}
} else {
// 退出低电:插电即退出;或回到迟滞上界;或 force 模式下按新阈值判断
if (mPlugType != BATTERY_PLUGGED_NONE) {
mBatteryLevelLow = false;
} else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mBatteryLevelLow = false;
} else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
mBatteryLevelLow = false;
}
}

mLowBatteryWarningLevel 与 mLowBatteryCloseWarningLevel 之间留有默认 5% 的迟滞,避免阈值附近反复进 / 出低电模式(会影响 JobScheduler、省电策略)。注意 mBatteryLevelLow(供 PowerManager / JobScheduler 查询)与 mSentLowBatteryBroadcast(控制 LOW / OKAY 广播)是两个独立状态位。

步骤 8:事件广播(L929--979)

  • 每个"值得广播"的变化让 mSequence++,随三类广播一起下发,接收方据此判断新旧。
  • POWER_CONNECTED / POWER_DISCONNECTED:mPlugType 与快照跨过 0 时发送,flags 为 REGISTERED_ONLY_BEFORE_BOOT。
  • BATTERY_LOW / BATTERY_OKAY:由 shouldSendBatteryLowLocked() 与恢复条件控制。
  • 发送前先 removeMessages 同类消息,保证快速插拔 / 连续变化只投最后一条;所有广播都是异步投递。

步骤 9:主广播与限流(L985--993)

rateLimitBatteryChangedBroadcast(force) 返回 true 表示本次不发送。规则如下:

|------------------------------------------------------------------------------------------------------------------------|------------------------------------|
| 变化的量 | 放行(发送广播)的条件 |
| status / health / present / level / plugged / maxVoltage / invalidCharger / cycleCount / chargingState / capacityLevel | 任一变化即无条件放行 |
| 电压 | 相对上次发送值波动 >= 1%,且距上次电压广播 >= 20 秒 |
| 温度 | 变化 >= 1 摄氏度(10 个 0.1 摄氏度单位) |
| 最大充电电流 | 距上次该值广播 >= 5 秒 |

未被限流时才调用 sendBatteryChangedIntentLocked() 发送 BATTERY_CHANGED;若电量或插拔状态变化,再发送 ACTION_BATTERY_LEVEL_CHANGED(攒批 + 60 秒节流)。由于 level / plugged 属于"其他状态量",它们的变化永远不会被限流,两者逻辑保持一致。

步骤 10:收尾(L995--1022)

  • mLed.updateLightsLocked():低电红、充电橙 / 绿、满电绿、不充电且不低电则灭;即使 BATTERY_CHANGED 被限流,LED 也会刷新。
  • logOutlierLocked(dischargeDuration):异常放电时 dump batterystats 到 DropBox,放在广播之后,确保统计已记录最新状态。
  • 快照更新:只有在 BATTERY_CHANGED 真正发出时才更新 mLastBroadcast*;被限流的一轮不记账,所以电压 / 温度累计变化不会丢失,会在下一次有效变化时一起带出去。

3.3 关机检测

android系统在低电状态或者电量快要耗尽的是如何弹出关机界面的呢?这块机制就是保障系统在电量快要耗尽之前能够正常进入关机流程。入口如下:

如上两个函数一个是低电量检查,一个是温度检查。

1)低电关机 shutdownIfNoPowerLocked

这里的检查条件逻辑如下:

java 复制代码
    private boolean shouldShutdownLocked() {
        Slog.i(TAG, "shouldShutdownLocked: batteryCapacityLevel=" + mHealthInfo.batteryCapacityLevel);
        if (mHealthInfo.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) { // -1
            boolean result = (mHealthInfo.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL); // 1
            Slog.i(TAG, "shouldShutdownLocked: branch[batteryCapacityLevel CRITICAL], result=" + result);
            return result;
        }
        //配置开关:来自 config_shutdownIfNoPower(构造时读取,L463)。有些设备(如无电池的 TV / 车机 / 部分嵌入式设备)配置为不允许因没电关机。
        if (!mShutdownIfNoPower) {
            Slog.i(TAG, "shouldShutdownLocked: branch[!mShutdownIfNoPower], result=false");
            return false;
        }
        //电量必须为0:只有 level <= 0 才可能继续。哪怕还剩 1%,也不会关机------低电警告、省电模式、用户提示都交给上层处理,这里只管"最后一刻"。
        if (mHealthInfo.batteryLevel > 0) {
            Slog.i(TAG, "shouldShutdownLocked: branch[batteryLevel > 0], result=false");
            return false;
        }
        //电池必须存在:注释表示无电池设备(直接接电源供电)不该因为"电量 0"关机。
        // Battery-less devices should not shutdown.
        if (!mHealthInfo.batteryPresent) {
            Slog.i(TAG, "shouldShutdownLocked: branch[!batteryPresent], result=false");
            return false;
        }
        //没有在充电:为什么看 batteryStatus 而不是 plugged? 因为"插着充电器"不等于"在充电"。NOT_CHARGING 时电源在线,但电池仍在掉电,这时候不关机只会让设备直接断电(比优雅关机更危险)。所以只有真正 CHARGING 才放过。
        // If battery state is not CHARGING, shutdown.
        // - If battery present and state == unknown, this is an unexpected error state.
        // - If level <= 0 and state == full, this is also an unexpected state
        // - All other states (NOT_CHARGING, DISCHARGING) means it is not charging.
        boolean result = mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_CHARGING; // ? 2 
        Slog.i(TAG, "shouldShutdownLocked: branch[final check batteryStatus != CHARGING], result=" + result);
        return result;
    }

最后附上状态mHealthInfo.batteryStatus取值范围:

|--------------|-------|--------------------------------|
| 状态 | 值 | 为什么算"不能充" |
| UNKNOWN | 1 | 电池在位却状态未知,属于异常状态,不能指望它充上电 |
| DISCHARGING | 3 | 明确在放电 |
| NOT_CHARGING | 4 | 插着电但没在充(充电暂停、温控、充电策略、充电器功率不足等) |
| FULL | 5 | 电量为 0 却显示"满电",是矛盾状态,按异常处理 |

2)高温关机 shutdownIfOverTempLocked

这里的判断条件主要是如下:

mHealthInfo.batteryTemperatureTenthsCelsius > mShutdownBatteryTemperature

  • 其中batteryTemperatureTenthsCelsius就是Health HAL 上报的电池温度,单位 0.1℃(所以 680 表示 68.0℃)。注意是电池/电量计温度,不是 CPU/SoC 温度(后者归 ThermalService 管)。
  • 其中mShutdownBatteryTemperature是从资源配置文件中获取的阈值,默认680,即68°。
  • 总结:默认电池温度超哥68°,就会启动关机界面,进行优雅的关机流程。
3)优雅关机 startShutdownActivity

其本质就是通过ACTION_REQUEST_SHUTDOWN广播事件请求一个关机界面,在这个activity里面去调用PMS,详细流程参考Android Init 系列专题【篇五:reboot & shutdown】_reboot: restarting system with command 'zygote-fat-CSDN博客

3.4 ACTION_BATTERY_CHANGED广播事件

如下判断逻辑,如果rateLimitBatteryChangedBroadcast返回false,就发送广播:

1)电池状态广播发送的条件
java 复制代码
    private boolean rateLimitBatteryChangedBroadcast(boolean forceUpdate) {
        //开机后第一次更新:无条件发送
        if (mIsFirstBatteryChangedUpdate) {
            mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
            mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
            mIsFirstBatteryChangedUpdate = false;
            return false;
        }
        final boolean voltageUpdated =
                mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
        final boolean temperatureUpdated =
                mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
        final boolean maxChargingCurrentUpdated =
                mLastBroadcastMaxChargingCurrent != mHealthInfo.maxChargingCurrentMicroamps;
        //如果forceUpdate为true:直接放行
        //如果forceUpdate为false:判断数据状态变化直接放行
        final boolean otherStatesUpdated = forceUpdate
                || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
                || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
                || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
                || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
                || mPlugType != mLastBroadcastPlugType
                || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
                || mInvalidCharger != mLastBroadcastInvalidCharger
                || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
                || mHealthInfo.chargingState != mLastBroadcastChargingState
                || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel;
        // We only rate limit based on changes in the temp, voltage.
        if (otherStatesUpdated) {
            if (voltageUpdated)  mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
            if (maxChargingCurrentUpdated)  mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
            return false;
        }
        //电压条件:幅度 ≥1% 且 间隔 ≥20 秒(两个都满足才发)
        final float basePointDiff = (float) (mLastBroadcastBatteryVoltage - mHealthInfo.batteryVoltageMillivolts) / mLastBroadcastBatteryVoltage;
        //例子:上次发送 4000mV,现在 4045mV → 变化 1.125% ≥ 1%;若距上次电压发送已满 20 秒 → 发送;只过了 10 秒 → 继续压着(除非温度/电流/其他状态也满足)。
        if (voltageUpdated
                && abs(basePointDiff) >= BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE
                && SystemClock.elapsedRealtime() - mLastBroadcastVoltageUpdateTime >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
            mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
            if (maxChargingCurrentUpdated) mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
            return false;
        }
        //温度条件:变化 ≥1.0℃(没有时间门槛) Only send the broadcast if the temperature update is greater than 1 degree celsius.
        if (temperatureUpdated  && abs( mLastBroadcastBatteryTemperature - mHealthInfo.batteryTemperatureTenthsCelsius) >= ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE) {
            if (voltageUpdated)  mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
            if (maxChargingCurrentUpdated)   mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
            return false;
        }
        //最大充电电流条件:有变化 且 间隔 ≥5 秒(没有幅度门槛)
        if (maxChargingCurrentUpdated && SystemClock.elapsedRealtime() - mLastBroadcastMaxChargingCurrentUpdateTime >= TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS) {
            //只要电流相对上次发送值有变化,并且距上次至少 5 秒,就发送。由于快照只在发送后更新,"有变化"也是相对上次发送值
            mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
            if (voltageUpdated) mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
            return false;
        }
        //都不满足:return true本轮不发
        return true;
    }
2)电池状态广播发送的数据

|--------|----------------------------|--------------|--------------------------------------------------------------------------|---------------------------------|
| 编号 | Extra 常量 | 类型 / 单位 | 含义 | 数据来源 |
| 1 | EXTRA_SEQUENCE | int,递增序号 | 每次"值得广播的变化"加 1;同一批的 POWER/LOW/CHANGED 事件共享同一个值,接收方据此判断新旧 | mSequence |
| 2 | EXTRA_STATUS | int,枚举 | 充放电状态:UNKNOWN=1、CHARGING=2、DISCHARGING=3、NOT_CHARGING=4、FULL=5 | batteryStatus |
| 3 | EXTRA_HEALTH | int,枚举 | 健康度:GOOD / OVERHEAT / DEAD / OVER_VOLTAGE / UNSPECIFIED_FAILURE / COLD 等 | batteryHealth |
| 4 | EXTRA_PRESENT | boolean | 电池是否在位;无电池设备为 false | batteryPresent |
| 5 | EXTRA_LEVEL | int,百分比 | 当前电量(0--scale) | batteryLevel |
| 6 | EXTRA_SCALE | int,固定 100 | 满电刻度;百分比 = level × 100 / scale | BATTERY_SCALE 常量 |
| 7 | EXTRA_BATTERY_LOW | boolean | 是否处于"已发过低电事件"的状态(带迟滞,不是简单的 level ≤ 阈值) | mSentLowBatteryBroadcast |
| 8 | EXTRA_ICON_SMALL | int,资源 ID | 系统建议的小图标(充电 / 放电 / 未知),由状态和电量共同决定 | getIconLocked() |
| 9 | EXTRA_PLUGGED | int,位掩码 | 0=未插电,1=AC,2=USB,4=无线,8=底座(可组合) | mPlugType |
| 10 | EXTRA_VOLTAGE | int,mV | 电池瞬时电压,例如 4000 表示 4.0V | batteryVoltageMillivolts |
| 11 | EXTRA_TEMPERATURE | int,0.1℃ | 电池温度,例如 300 表示 30.0℃ | batteryTemperatureTenthsCelsius |
| 12 | EXTRA_TECHNOLOGY | String | 电池类型,如 "Li-ion" | batteryTechnology |
| 13 | EXTRA_INVALID_CHARGER | int,0/1 | 是否检测到无效充电器(来自 invalid_charger UEvent 节点,不是 HAL 数据) | mInvalidCharger |
| 14 | EXTRA_MAX_CHARGING_CURRENT | int,µA | 允许的最大充电电流 | maxChargingCurrentMicroamps |
| 15 | EXTRA_MAX_CHARGING_VOLTAGE | int,µV | 允许的最大充电电压(注意单位是微伏,和第 10 项的 mV 不同) | maxChargingVoltageMicrovolts |
| 16 | EXTRA_CHARGE_COUNTER | int,µAh | 电量计剩余容量 | batteryChargeCounterUah |
| 17 | EXTRA_CYCLE_COUNT | int,次 | 电池循环次数 | batteryCycleCount |
| 18 | EXTRA_CHARGING_STATUS | int,HAL 枚举 | HAL 上报的原始充电状态(与第 2 项的 BATTERY_STATUS 不一定相同) | chargingState |
| 19 | EXTRA_CAPACITY_LEVEL | int,HAL 枚举 | 容量等级:-1 不支持、0 未知、1 临界、2 低、3 正常、4 高、5 满 | batteryCapacityLevel |

三、Linux电源子系统

在介绍HAL之前,我需要先理清楚linux底层相关的一系列机制,不然作为非底软同事可能会对这块内容非常的懵逼。

1、power_supply 子系统

power_supply 子系统是什么?驱动同事肯定非常熟悉,然而框架或应用同事可能不知所以然。其本质就是linux内核的一种uevent模型。

uevent 是内核设备模型的标准通知,uevent_fd_ 订阅的是"全部设备"的广播,SUBSYSTEM=power_supply 是其中电源类设备(充电器、电池、无线充、PD)状态变化时发出的那一类。插拔充电器、充电状态/电量/温度/健康度变化、快充协商等都会触发它;而 USB 枚举本身属于 usb 子系统,会被 HealthLoop 直接忽略。

1.1 uevent是什么?

Linux 里每个设备/驱动对象(kobject/device)都可以发通知。当设备状态变化时,驱动或内核调用 kobject_uevent(),内核把一条消息通过 netlink 组播发给所有订阅者:

bash 复制代码
ACTION=change
DEVPATH=/devices/platform/soc/.../power_supply/battery
SUBSYSTEM=power_supply
POWER_SUPPLY_NAME=battery
POWER_SUPPLY_STATUS=Charging
POWER_SUPPLY_CAPACITY=55
POWER_SUPPLY_VOLTAGE_NOW=4123000
...

关键字段就是 SUBSYSTEM=,它标明这是哪一类设备。常见的:

|-------------------------|--------------------|
| SUBSYSTEM | 例子 |
| usb | USB 设备枚举/断开 |
| block | 块设备(eMMC、UFS 分区)增删 |
| net | 网卡注册/状态变化 |
| input | 输入设备 |
| thermal | 温控区状态 |
| power_supply | 电池、充电器、无线充电、USB PD |
| platform、pci、mmc、tty... | 各类总线/设备 |

所以Linux内核"收到所有设备的 uevent"指的是:内核里所有设备类的变更通知都会走到这个 socket,因为订阅的是内核的总广播组,没有按子系统过滤。

Android 里的 ueventd(负责创建设备节点、改权限)也订阅同一个组播组。组播的语义是每个订阅者都收到一份,所以 HealthLoop 和 ueventd 互不影响,各自处理自己关心的部分。

power_supply 是 Linux 的电源子系统(drivers/power/supply/)。每个电源设备(电池、AC 充电器、USB 充电器、无线充电器、USB PD 等)注册后,驱动在状态变化时调用:

cpp 复制代码
power_supply_changed(psy);   // 核心函数,触发 power_supply 的 uevent

1.2 power_supply 触发场景

充电器插拔

  • AC/USB/无线/Dock 的 online 从 0 变 1(或反之);
  • USB 枚举完成、BC1.2 识别出 SDP/DCP/CDP。

充电状态变化

  • STATUS 从 Discharging → Charging、Not charging、Full;
  • 快充协议(PD/QC/PPS)协商后电压、电流目标变化。

电池数据更新

  • 电量(CAPACITY)、电压(VOLTAGE_NOW)、电流(CURRENT_NOW)、温度(TEMP)、剩余容量(CHARGE_COUNTER)变化;
  • 这些通常由电量计(fuel gauge)驱动在中断或轮询周期里更新,由驱动决定是否发 uevent、发多频繁,很多驱动会做节流。

电池状态类事件

  • 电池在位/移除、健康度(HEALTH)变化、过温/过流告警。

充电策略/热控

  • 温控降低充电电流、暂停充电(STATUS=Not charging);
  • 充电器过压/欠压保护触发。

注意:一次插拔 USB 充电器,可能产生好几条 uevent:

|---------------------------------|--------------|------------------------------|
| 时间点 | 子系统 | HealthLoop 关心吗 |
| USB 控制器检测到设备接入、枚举 | usb | 不关心(被过滤掉) |
| 充电器驱动把 usb.online 置 1 | power_supply | ✅ 触发 ScheduleBatteryUpdate() |
| PD 协商完成,电压电流变化 | power_supply | ✅ 再触发一次 |
| 电量计把 battery.STATUS 改成 Charging | power_supply | ✅ 再触发一次 |

所以"USB 插拔触发 SUBSYSTEM=usb"这个说法本身没错,但供电相关的变化并不只发生在 usb 子系统 ------真正让电池状态变化的是 power_supply 子系统里的 charger/battery 设备。HealthLoop 只在后者上采取行动。

2、Linux事件驱动机制

linux内核除了uevent机制之外,这里还需要介绍其他两套机制。

2.1 linux epoll 多路复用机制

详情参考:Linux --- epoll (I/O就绪事件通知机制)_linux epoll-CSDN博客

1)epoll 基本函数

epoll_create1 ------ 建一个 epoll 实例

cpp 复制代码
int epoll_create1(int flags);
// flags: 0 或 EPOLL_CLOEXEC
// 返回: epoll 实例 fd(本质是个匿名 inode 的 fd,不是文件)
// EPOLL_CLOEXEC 必加:否则 exec() 后子进程会继承这个 fd 造成泄漏。等价于 epoll_create(1) + fcntl(FD_CLOEXEC)。
// 老的 epoll_create(int size) 里 size 从 2.6.8 起就只是历史遗留,内核不再用它做任何容量预留,但必须 > 0,否则 EINVAL。新代码一律用 epoll_create1。
// 内核里对应创建一个 struct eventpoll,含三样东西:红黑树 rbr(存所有被监听的 fd)、就绪链表 rdllist(存已就绪的)、等待队列 wq(存阻塞在 epoll_wait 上的进程)。

epoll_ctl ------ 增 / 改 / 删监听项

cpp 复制代码
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// op: EPOLL_CTL_ADD / EPOLL_CTL_MOD / EPOLL_CTL_DEL
// 返回: 0 成功,-1 出错(errno: EEXIST/ENOENT/EBADF/EINVAL/EPERM)
// 此函数的目的是把需要被统一管理的fd进行增删改查:
//      同一个 fd 重复 ADD 会 EEXIST;改关注事件必须 MOD
//      DEL 时 event 参数可以传 NULL(2.6.9 之后)
//      data 是你自己的字段,内核不解释。工程上通常 data.ptr 指向连接对象,比 data.fd 好用(能一次拿到缓冲区、状态机)
//      EPOLLERR/EPOLLHUP 不用注册也会报,所以必须在返回后检查,否则会漏掉异常连接导致空转 100% CPU

epoll_wait ------ 等就绪事件

cpp 复制代码
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// maxevents: 必须 > 0,本次最多返回多少个
// timeout: -1 永久阻塞;0 立即返回(纯轮询);>0 毫秒
// 返回: >0 就绪个数;0 超时;-1 出错(errno: EINTR 需重试)

close ------ 释放实例

cpp 复制代码
close(epfd);   // 释放实例
// 被监听的 fd 自己 close() 时会自动从 epoll 里摘除(因为内核在 file 释放时回调 ep_remove),但同一 fd 号被新 fd 复用时可能残留旧监听,稳妥做法是先 EPOLL_CTL_DEL 再 close。
2)epoll 如何理解?
bash 复制代码
epoll_create1  →  new eventpoll
                    ├── rbr     : 红黑树,key = (fd, file*),存所有 epitem
                    ├── rdllist : 就绪双向链表
                    └── wq      : 等待队列(阻塞的 epoll_wait 进程)

epoll_ctl(ADD) →  分配 epitem,插红黑树
                  poll_wait(file, &ep->pwq, ...) 把 ep_poll_callback 挂到该 fd 的等待队列
                  (关键:监听动作只做一次,不是每次 wait 都重做)

fd 就绪        →  内核在 poll 里回调 ep_poll_callback(epitem)
                  → 若 epitem 不在 rdllist,就链进去,并唤醒 eventpoll->wq 上的等待者

epoll_wait     →  若 rdllist 非空,直接摘链表返回 O(就绪数)
                  若为空,把自己挂到 wq 上睡,直到被回调唤醒或超时

如上的介绍可能比较玄乎,我这里简单理解一下,我附上我和AI的对话:

2.2 linux timerfd 定时器

3、libhealthloop

基于如上知识之后,我们需要来研究一下libhealthloop静态库,其代码路径在hardware/interfaces/health的工具目录中:

libhealthloop是一个静态库,注意不是动态库,他们的区别如下:

维度 静态库 .a 动态库 .so
链接时机 编译链接时 运行时由系统加载
代码去向 被拷贝进最终可执行文件/动态库 不拷贝进主程序,运行时加载
运行时依赖 不再需要 .a 文件 必须有对应 .so 文件
体积 可执行文件更大 可执行文件更小
内存 多进程使用时会重复占用 多进程可共享同一份
更新 库改动后,依赖方需重新编译链接 替换 .so 即可,接口不变时主程序不用重编
典型场景 小工具、嵌入式、需要独立部署 系统库、共享模块、需要灵活升级

3.1 HealthLoop开启轮询

libhealthloop是一个静态库,但是后面讲到HAL进程会静态链接这个库,并调用StartLoop函数进行事件轮询:

cpp 复制代码
int HealthLoop::StartLoop() {
    int ret;
    //开启日志到内核
    klog_set_level(KLOG_LEVEL);
    //事件初始化
    ret = InitInternal();
    if (ret) {
        KLOG_ERROR(LOG_TAG, "Initialization failed, exiting\n");
        return 2;
    }
    //事件轮询
    MainLoop();
    KLOG_ERROR(LOG_TAG, "Main loop terminated, exiting\n");
    return 3;
}

3.2 InitInternal事件初始化

HealthLoop轮询机制采用了前文介绍的epoll多路复用机制。即通过创建epollfd来同时监听了两个事件:定时器事件wakealarm_fd_和子系统监听事件uevent_fd_。

1)linux周期性事件
cpp 复制代码
void HealthLoop::WakeAlarmInit(void) {
    //创建一个定时器事件wakealarm_fd_,即使是系统休眠了,也能由内核驱动起来
    wakealarm_fd_.reset(timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK));
    if (wakealarm_fd_ == -1) {
        KLOG_ERROR(LOG_TAG, "wakealarm_init: timerfd_create failed\n");
        return;
    }
    //把wakealarm_fd_事件关联到前面创建的多路复用epollfd_节点上
    //RegisterEvent函数本质上是调用了epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &ev)
    //即wakealarm_fd_被添加绑定到epollfd_上面,后续只需要监听epollfd_即可
    //当监听到wakealarm_fd_触发就会自动回调HealthLoop::WakeAlarmEvent函数
    if (RegisterEvent(wakealarm_fd_, &HealthLoop::WakeAlarmEvent, EVENT_WAKEUP_FD))
        KLOG_ERROR(LOG_TAG, "Registration of wakealarm event failed\n");

    WakeAlarmSetInterval(healthd_config_.periodic_chores_interval_fast);
}
void HealthLoop::WakeAlarmEvent(uint32_t /*epevents*/) {
    // No need to lock because wakealarm_fd_ is guaranteed to be initialized.
    unsigned long long wakeups;
    if (read(wakealarm_fd_, &wakeups, sizeof(wakeups)) == -1) {
        KLOG_ERROR(LOG_TAG, "wakealarm_event: read wakealarm fd failed\n");
        return;
    }
    //当wakealarm_fd_内核被定时唤醒触发并调用该函数,实际上就是调用ScheduleBatteryUpdate函数
    PeriodicChores();
}
void HealthLoop::PeriodicChores() {
    ScheduleBatteryUpdate();
}
2)linux uevent事件
cpp 复制代码
void HealthLoop::UeventInit(void) {
    //创建uevent_fd_事件
    uevent_fd_.reset(uevent_open_socket(64 * 1024, true));
    if (uevent_fd_ < 0) {
        KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n");
        return;
    }
    fcntl(uevent_fd_, F_SETFL, O_NONBLOCK);
    //把uevent_fd_事件关联到前面创建的多路复用epollfd_节点上
    //RegisterEvent函数本质上是调用了epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &ev)
    //即uevent_fd_被添加绑定到epollfd_上面,后续只需要监听epollfd_即可
    //当监听到uevent_fd_触发就会自动回调HealthLoop::UeventEvent函数
    if (RegisterEvent(uevent_fd_, &HealthLoop::UeventEvent, EVENT_WAKEUP_FD))
        KLOG_ERROR(LOG_TAG, "register for uevent events failed\n");
}

// TODO(b/140330870): Use BPF instead.
#define UEVENT_MSG_LEN 2048
void HealthLoop::UeventEvent(uint32_t /*epevents*/) {
    // No need to lock because uevent_fd_ is guaranteed to be initialized.
    char msg[UEVENT_MSG_LEN + 2];
    char* cp;
    int n;
    n = uevent_kernel_multicast_recv(uevent_fd_, msg, UEVENT_MSG_LEN);
    if (n <= 0) return;
    if (n >= UEVENT_MSG_LEN) /* overflow -- discard */
        return;
    msg[n] = '\0';
    msg[n + 1] = '\0';
    cp = msg;
    while (*cp) {
        if (!strcmp(cp, "SUBSYSTEM=power_supply")) {
            //当linux子系统被触发后,此函数就被调用,过滤为电源子系统,就调用ScheduleBatteryUpdate函数
            ScheduleBatteryUpdate();
            break;
        }
        /* advance to after the next \0 */
        while (*cp++);
    }
}

3.3 MainLoop事件监听和轮询

如上最后通过C++函数指针的方式,都调用到了ScheduleBatteryUpdate函数,但此函数被定义为虚函数,通过这样实现了多态。

四、HAL层IHealth.hal

接下来我们需要再看看hardware/health的实现方式,其代码目录如下:

1、Health AIDL

如上代码为Health AIDL的实现机制。同之前看到的AIDL基本一致,但是这里有两条不得不说明一下:

FQA:二进制后面带上了example,难道这只是一个示例?

如上官方说明,翻译过来如果这个示例适配我们的平台,那么就可以安装启用Health AIDL,如果不适配,那么需要厂商或者ODM自定义Health AIDL的实现。

PS:后面经过多个平台项目的实践,我得出来的结论是,如果对这块定制比较少的厂商,通常不会来自定义Health AIDL,但是官方给到的example示例通常也不适配,所以基本上使用的Health HIDL HAL 2.1的比较多。

FQA:Health AIDL依赖两个重要的静态库,这两个静态库在后文启到至关重要的作用。

1.1 HalHealthLoop的主函数

1.2 HalHealthLoop的定义

这里非常的绕,需要多看几遍,我直接说出我的两个比较重要的结论哈:

  • service_:HAL通过此对象,可以调用到BatteryMonitor.cpp中的代码,并直接从驱动节点中获取电池电量相关数据
  • callback_:HAL通过此对象,可以直接回调到BatteryService中的代码,它就是BatteryService::update方法
  • HalHealthLoop继承HealthLoop:因此前文介绍的HealthLoop轮询机制提到的ScheduleBatteryUpdate函数就会被周期性触发和动态监听到修改触发

1.3 HealthInfo透传到上层

2、Health HIDL

如上是2.1版本的编译配置,但是有如下几个地方值得注意:

  • hal进程名字叫做android.hardware.health@2.1-service
  • hal接口实现库在设备中实际上叫做android.hardware.health@2.0-impl-2.1,最开始让我很奇怪,为什么不是2.1-impl,而是搞了一个别名,最后咨询ai回到如下:

如下代码,这里采用了2.0向后转换,是因为2.0已经冻结和稳定,通过强制转换,动态兼容2.0版本和2.1版本的直通式动态库。

如上Health 2.1的主函数也非常的奇怪,实例化了BinderHealth对象,这个对象的头文件路径却是在health2impl/BinderHealth.h,因此可以看到2.1的实现在libhealth2impl静态库中,Android.bp定义如下:

在此库中的逻辑其实和本章第一节介绍的内容差不多,这里写的其实非常的乱,就不具体讲解细节了,只贴大致流程,只需要知道和前面AIDL的机制基本上一致:

3、libbatterymonitor

前面两节的AIDL和HIDL都提到过,持有的service_对象才是真正的去读驱动节点的对象。那么本节就来看看他是如何去读驱动节点的。

无论是AIDL还是HIDL都静态引用了libbatterymonitor:

此静态库的定义其实就是之前的healthd守护进程,因为treble话,把healthd守护进程分离的支离破碎,但是保留了充电逻辑和驱动节点数据的读取的逻辑。

PS:经过研究发现HAL层调用的是system分区的静态库,实在无法理解这样设计的意义。

3.1 HAL如何调到BattertyMonitor?

  • AIDL的调用
  • HIDL的调用

如上逻辑都是通过BatteryMonitor.cpp 的updateValues函数和getHealthInfo函数实现。

3.2 BatteryMonitor::updateValues读取节点

此函数就是单纯的去更新各个字段:

这些函数最终都是通过readFromFile读取文件节点:

相关推荐
alexhilton1 小时前
提升 Android Compose 页面的流畅度
android·kotlin·android jetpack
撩得Android一次心动3 小时前
Android中Gradle网络下载超时、更改存放位置(解决方案)
android·gradle
HQL_seven4 小时前
【Activity】Android Activity详解
android
AI2中文网4 小时前
安卓 15/16 手机软键盘遮挡输入框、布局顶不上去?已解决:重新编译即可
android·智能手机
字节暗面4 小时前
Android APP 加固横评记录:爱加密、梆梆、网易易盾与腾讯 ACE
android·逆向工程·加固
落魄Android在线炒饭5 小时前
Android 实战问题反推 OpenGL 概念(上)
android·opengl
booboosui5 小时前
DeepSeek 的 Worker 调得动 GitHub,调不动微信:三类 Worker 的实现差别
android
MiyamuraMiyako6 小时前
Compose LazyRow 实现尾项露出与少量数据均分
android·android jetpack
booboosui6 小时前
输入法借 350 毫秒:给 agent 打字的两条路
android