目录
[1.1 battery服务能力](#1.1 battery服务能力)
[1.2 batteryproperties服务能力](#1.2 batteryproperties服务能力)
[2.1 HealthServiceWrapper装饰类](#2.1 HealthServiceWrapper装饰类)
[2.2 BatteryService::update方法何去何从?](#2.2 BatteryService::update方法何去何从?)
[3.1 force参数到底有何意义?](#3.1 force参数到底有何意义?)
[3.2 核心流程解读](#3.2 核心流程解读)
[3.3 关机检测](#3.3 关机检测)
[3.4 ACTION_BATTERY_CHANGED广播事件](#3.4 ACTION_BATTERY_CHANGED广播事件)
[1、power_supply 子系统](#1、power_supply 子系统)
[1.1 uevent是什么?](#1.1 uevent是什么?)
[1.2 power_supply 触发场景](#1.2 power_supply 触发场景)
[2.1 linux epoll 多路复用机制](#2.1 linux epoll 多路复用机制)
[2.2 linux timerfd 定时器](#2.2 linux timerfd 定时器)
[3.1 HealthLoop开启轮询](#3.1 HealthLoop开启轮询)
[3.2 InitInternal事件初始化](#3.2 InitInternal事件初始化)
[3.3 MainLoop事件监听和轮询](#3.3 MainLoop事件监听和轮询)
[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.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读取文件节点:


