第五板块:Android 系统服务与电源管理 | 第十八篇:Battery Service 与 电量统计(Fuel Gauge)算法
所属板块:第五板块 --- Android 系统服务与电源管理
前置知识:第十七篇中的 Power Manager Service、WakeLock 机制、Linux 内核驱动、Sysfs 接口
本篇定位 :这是 Android 设备能量视角的终极审判 。如果说 PMS 是控制能量消耗的阀门,那么 Battery Service 就是计量能量流逝的仪表盘 。本篇将彻底拆解 Linux 电源供应子系统(Power Supply Class) 、Healthd 守护进程的轮询机制 、电池容量(Capacity)的百分比算法 、电流积分法(Coulomb Counting) 、电池曲线校准(Gas Gauging) 。我们将深入 Kernel Driver 、Native Daemon 与 Framework Service,揭示 Android 如何从毫安时(mAh)的微观层面计算剩余电量。全程无省电技巧、无电池保养建议,仅保留 Android 电量统计的底层定义与算法规范。
1. 核心结论先行(Thesis Statement)
Android 的电量统计是一个基于物理测量的估算系统。
- Battery Service 的本质 :一个数据聚合器 。它运行在 System Server 中,通过 Binder 从 Healthd 守护进程获取电池数据,并广播给所有应用。
- Healthd 的本质 :内核接口的翻译官 。它是一个 Native 守护进程,通过轮询
/sys/class/power_supply/下的 sysfs 节点,读取电压、电流、温度等原始物理数据。 - 电量百分比的本质 :开路电压(OCV)与库仑计数的混合估算 。系统通过查找电池曲线(Battery Profile),将当前的电压和电流映射到剩余容量百分比。
- 电量统计(Battery Stats)的本质 :差分累加器。系统记录每个 UID(应用)在单位时间内持有的 WakeLock 时间和 CPU 使用率,以此计算耗电占比。
2. 电池硬件与内核层(Kernel Layer)
2.1 电源供应子系统(Power Supply Class)
Linux 内核提供了一套标准的电源管理接口。
学术定义:
- Power Supply Class: 内核中的一个子系统,用于管理各种电源设备(电池、USB、AC 适配器)。
- Sysfs 接口 : 内核通过
/sys/class/power_supply/<supply_name>/目录下的文件,向用户空间暴露电源属性。
2.2 关键 Sysfs 节点
| 节点路径 | 含义 | 单位 | 学术定义 |
|---|---|---|---|
/sys/class/power_supply/battery/capacity |
剩余容量百分比 | % | 系统估算的剩余电量。 |
/sys/class/power_supply/battery/voltage_now |
当前电压 | uV (微伏) | 电池两端的电势差。 |
/sys/class/power_supply/battery/current_now |
当前电流 | uA (微安) | 正值为充电,负值为放电。 |
/sys/class/power_supply/battery/temp |
电池温度 | 0.1 °C | 电池的热力学温度。 |
/sys/class/power_supply/battery/charge_counter |
累计电荷量 | uAh (微安时) | 库仑计数的原始值。 |
/sys/class/power_supply/battery/status |
充电状态 | 文本 | Charging, Discharging, Full, Not charging。 |
2.3 Fuel Gauge(电量计)IC
硬件上,电池通常通过 I2C 或 SPI 连接一个专门的 Fuel Gauge IC(如 TI 的 BQ27541)。
学术定义:
- OCV (Open Circuit Voltage): 电池开路电压。电池在不充不放时的电压,与剩余电量有固定的化学对应关系。
- Coulomb Counter (库仑计): 一个积分器。测量流过电池的电流,对时间积分,得到流过的电荷量(Q = I × t)。
3. Healthd 守护进程(Native Layer)
3.1 Healthd 的架构
Healthd 是 Android 系统中负责监控电池状态的 Native 守护进程。
#mermaid-svg-4gpEFdISsTViPh8Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4gpEFdISsTViPh8Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4gpEFdISsTViPh8Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4gpEFdISsTViPh8Q .error-icon{fill:#552222;}#mermaid-svg-4gpEFdISsTViPh8Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4gpEFdISsTViPh8Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4gpEFdISsTViPh8Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4gpEFdISsTViPh8Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4gpEFdISsTViPh8Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4gpEFdISsTViPh8Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4gpEFdISsTViPh8Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4gpEFdISsTViPh8Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4gpEFdISsTViPh8Q .marker.cross{stroke:#333333;}#mermaid-svg-4gpEFdISsTViPh8Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4gpEFdISsTViPh8Q p{margin:0;}#mermaid-svg-4gpEFdISsTViPh8Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4gpEFdISsTViPh8Q .cluster-label text{fill:#333;}#mermaid-svg-4gpEFdISsTViPh8Q .cluster-label span{color:#333;}#mermaid-svg-4gpEFdISsTViPh8Q .cluster-label span p{background-color:transparent;}#mermaid-svg-4gpEFdISsTViPh8Q .label text,#mermaid-svg-4gpEFdISsTViPh8Q span{fill:#333;color:#333;}#mermaid-svg-4gpEFdISsTViPh8Q .node rect,#mermaid-svg-4gpEFdISsTViPh8Q .node circle,#mermaid-svg-4gpEFdISsTViPh8Q .node ellipse,#mermaid-svg-4gpEFdISsTViPh8Q .node polygon,#mermaid-svg-4gpEFdISsTViPh8Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4gpEFdISsTViPh8Q .rough-node .label text,#mermaid-svg-4gpEFdISsTViPh8Q .node .label text,#mermaid-svg-4gpEFdISsTViPh8Q .image-shape .label,#mermaid-svg-4gpEFdISsTViPh8Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-4gpEFdISsTViPh8Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4gpEFdISsTViPh8Q .rough-node .label,#mermaid-svg-4gpEFdISsTViPh8Q .node .label,#mermaid-svg-4gpEFdISsTViPh8Q .image-shape .label,#mermaid-svg-4gpEFdISsTViPh8Q .icon-shape .label{text-align:center;}#mermaid-svg-4gpEFdISsTViPh8Q .node.clickable{cursor:pointer;}#mermaid-svg-4gpEFdISsTViPh8Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4gpEFdISsTViPh8Q .arrowheadPath{fill:#333333;}#mermaid-svg-4gpEFdISsTViPh8Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4gpEFdISsTViPh8Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4gpEFdISsTViPh8Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4gpEFdISsTViPh8Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4gpEFdISsTViPh8Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4gpEFdISsTViPh8Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4gpEFdISsTViPh8Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4gpEFdISsTViPh8Q .cluster text{fill:#333;}#mermaid-svg-4gpEFdISsTViPh8Q .cluster span{color:#333;}#mermaid-svg-4gpEFdISsTViPh8Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4gpEFdISsTViPh8Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4gpEFdISsTViPh8Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-4gpEFdISsTViPh8Q .icon-shape,#mermaid-svg-4gpEFdISsTViPh8Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4gpEFdISsTViPh8Q .icon-shape p,#mermaid-svg-4gpEFdISsTViPh8Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4gpEFdISsTViPh8Q .icon-shape .label rect,#mermaid-svg-4gpEFdISsTViPh8Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4gpEFdISsTViPh8Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4gpEFdISsTViPh8Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4gpEFdISsTViPh8Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} System Server
healthd 进程
Linux 内核
更新数据
读取
解析
计算百分比
推送更新
广播
电池驱动
/sys/class/power_supply/battery/
轮询线程 (ueventd)
解析器
电量计算算法
Binder 服务 (IBatteryPropertiesRegistrar)
BatteryService
BatteryStatsService
3.2 Healthd 的轮询机制
Healthd 通过 uevent 机制监听内核事件,并定期轮询 sysfs。
源码解析:
cpp
// system/core/healthd/healthd.cpp
void healthd_mainloop(void) {
while (1) {
// 1. 轮询 uevent 和 sysfs
poll(fds, nfds, -1);
// 2. 处理电池事件
healthd_battery_update();
}
}
void healthd_battery_update(void) {
// 读取 sysfs
battery_capacity = read_sysfs("/sys/class/power_supply/battery/capacity");
battery_voltage = read_sysfs("/sys/class/power_supply/battery/voltage_now");
// 通过 Binder 通知 Framework
battery_properties.prop = battery_capacity;
battery_properties.prop |= BATTERY_PROP_CAPACITY;
gBatteryPropertiesRegistrar->notifyListeners(battery_properties);
}
4. Battery Service 的估算算法
4.1 电量百分比的计算
系统不直接相信硬件上报的百分比,而是结合电压和电流进行修正。
学术定义:
- 电池曲线(Battery Profile): 厂商预置的一个查找表(Look-up Table)。横轴是电压,纵轴是电量百分比。不同温度下有不同的曲线。
- 算法逻辑 :
- 读取
voltage_now(V) 和current_now(I)。 - 根据当前温度,选择对应的电池曲线。
- 查找电压 V 对应的百分比 P1。
- 结合电流 I(放电越快,电压降越大),对 P1 进行补偿,得到最终百分比 P。
- 读取
4.2 库仑计数法(Coulomb Counting)
为了更精确,系统使用库仑计数法。
公式 :
Qremaining=Qlast+∫tlasttnowI(t) dtQ_{remaining} = Q_{last} + \int_{t_{last}}^{t_{now}} I(t) \, dtQremaining=Qlast+∫tlasttnowI(t)dt
学术定义:
- QremainingQ_{remaining}Qremaining: 剩余电荷量。
- I(t)I(t)I(t): 随时间变化的电流。
- 校准 : 每次充满电(QfullQ_{full}Qfull)时,重置库仑计数器的基准值。
4.3 电池老化与补偿
电池会随着循环次数增加而老化(容量衰减)。
学术定义:
- Design Capacity: 设计容量(如 4000 mAh)。
- Full Charge Capacity: 当前充满电的实际容量(如 3800 mAh)。
- Cycle Count: 充放电循环次数。
- 算法 : 当
Full Charge Capacity低于Design Capacity的 80% 时,系统认为电池已老化,并基于新的满容量进行百分比计算。
5. BatteryStatsService 的耗电统计
5.1 基于 UID 的耗电追踪
BatteryStatsService 负责统计每个应用的耗电量。
学术定义:
- UID (User ID): 每个应用分配的唯一 Linux UID。
- Power Profile : 设备的功率配置文件。定义在
framework/base/core/res/res/xml/power_profile.xml中,包含 CPU、WiFi、GPS 等硬件的功耗参数。
5.2 耗电计算公式
应用的总耗电量 = CPU 耗电 + 硬件耗电 + WakeLock 耗电。
| 组件 | 统计方式 | 算法 |
|---|---|---|
| CPU | 时间片统计 | ∑(CPU_Freq_Time×Power_Per_Freq)\sum (CPU\_Freq\_Time \times Power\_Per\_Freq)∑(CPU_Freq_Time×Power_Per_Freq) |
| WiFi | 状态统计 | Scan_Time×Scan_Power+Active_Time×Active_PowerScan\_Time \times Scan\_Power + Active\_Time \times Active\_PowerScan_Time×Scan_Power+Active_Time×Active_Power |
| GPS | 时间统计 | Gps_Time×Gps_PowerGps\_Time \times Gps\_PowerGps_Time×Gps_Power |
| WakeLock | 持有时间 | WakeLock_Time×System_PowerWakeLock\_Time \times System\_PowerWakeLock_Time×System_Power |
源码解析:
java
// BatteryStatsImpl.java
void updateTimeBasesLocked(boolean onBattery, long uptime, long realtime) {
// 更新 CPU 使用时间
mCpuStats.update(onBattery, uptime, realtime);
// 更新 WakeLock 持有时间
mWakeLockStats.update(onBattery, uptime, realtime);
}
6. 充电状态机
6.1 充电阶段的物理过程
#mermaid-svg-T6W7daTA3SdnW4yx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-T6W7daTA3SdnW4yx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-T6W7daTA3SdnW4yx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-T6W7daTA3SdnW4yx .error-icon{fill:#552222;}#mermaid-svg-T6W7daTA3SdnW4yx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-T6W7daTA3SdnW4yx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-T6W7daTA3SdnW4yx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-T6W7daTA3SdnW4yx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-T6W7daTA3SdnW4yx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-T6W7daTA3SdnW4yx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-T6W7daTA3SdnW4yx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-T6W7daTA3SdnW4yx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-T6W7daTA3SdnW4yx .marker.cross{stroke:#333333;}#mermaid-svg-T6W7daTA3SdnW4yx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-T6W7daTA3SdnW4yx p{margin:0;}#mermaid-svg-T6W7daTA3SdnW4yx defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-T6W7daTA3SdnW4yx g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-T6W7daTA3SdnW4yx g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-T6W7daTA3SdnW4yx g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-T6W7daTA3SdnW4yx g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-T6W7daTA3SdnW4yx g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-T6W7daTA3SdnW4yx .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-T6W7daTA3SdnW4yx .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-T6W7daTA3SdnW4yx .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-T6W7daTA3SdnW4yx .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-T6W7daTA3SdnW4yx .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-T6W7daTA3SdnW4yx .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-T6W7daTA3SdnW4yx .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-T6W7daTA3SdnW4yx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-T6W7daTA3SdnW4yx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-T6W7daTA3SdnW4yx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-T6W7daTA3SdnW4yx .edgeLabel .label text{fill:#333;}#mermaid-svg-T6W7daTA3SdnW4yx .label div .edgeLabel{color:#333;}#mermaid-svg-T6W7daTA3SdnW4yx .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-T6W7daTA3SdnW4yx .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-T6W7daTA3SdnW4yx .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-T6W7daTA3SdnW4yx .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-T6W7daTA3SdnW4yx .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-T6W7daTA3SdnW4yx .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-T6W7daTA3SdnW4yx .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-T6W7daTA3SdnW4yx #statediagram-barbEnd{fill:#333333;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-T6W7daTA3SdnW4yx .cluster-label,#mermaid-svg-T6W7daTA3SdnW4yx .nodeLabel{color:#131300;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-T6W7daTA3SdnW4yx .note-edge{stroke-dasharray:5;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-note text{fill:black;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram-note .nodeLabel{color:black;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagram .edgeLabel{color:red;}#mermaid-svg-T6W7daTA3SdnW4yx #dependencyStart,#mermaid-svg-T6W7daTA3SdnW4yx #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-T6W7daTA3SdnW4yx .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-T6W7daTA3SdnW4yx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 初始状态
插入充电器
低压预充
恒流快充
恒压涓流
充满
拔掉充电器
电量低 (15%)
电量极低 (5%)
关机
Unknown
Charging
PreCharging
FastCharging
TrickleCharging
Full
Discharging
Low
Critical
Shutdown
6.2 热管理(Thermal Throttling)
充电过程中,电池温度是关键。
| 温度范围 | 状态 | 学术定义 |
|---|---|---|
| < 0°C | Cold | 禁止充电,防止锂析出。 |
| 0°C - 10°C | Cool | 限制充电电流。 |
| 10°C - 45°C | Normal | 正常快充。 |
| > 45°C | Hot | 停止充电,防止热失控。 |
7. 关键源码深度解析
7.1 BatteryService 的广播机制
java
// BatteryService.java
private void updateBatteryLevelLocked() {
// 1. 从 Healthd 获取原始数据
int level = mBatteryProperties.level;
int voltage = mBatteryProperties.voltage;
// 2. 计算百分比(如果需要)
level = calculateLevel(level, voltage);
// 3. 发送广播
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
intent.putExtra(BatteryManager.EXTRA_LEVEL, level);
intent.putExtra(BatteryManager.EXTRA_VOLTAGE, voltage);
mContext.sendBroadcast(intent);
}
7.2 Power Profile 的定义
xml
<!-- power_profile.xml -->
<device name="generic">
<item name="cpu.speeds">125000 250000 500000 1000000</item>
<item name="cpu.active">200</item> <!-- mA -->
<item name="wifi.active">150</item> <!-- mA -->
<item name="gps.on">50</item> <!-- mA -->
</device>
8. 电量统计的常见误区
8.1 百分比跳变
| 现象 | 学术原因 |
|---|---|
| 电量从 30% 突然跳到 1% | 电池内阻增大,在大电流放电时电压骤降,导致 OCV 估算错误。 |
| 充电到 100% 后很快掉到 90% | 电池曲线校准不准,或者电池老化。 |
8.2 应用耗电统计不准
| 现象 | 学术原因 |
|---|---|
| 某应用显示耗电 50%,但实际没用多久 | 该应用持有 WakeLock 但 CPU 并未全速运行,统计的是"持有时间"而非"实际功耗"。 |
| 系统进程耗电高 | 系统进程(如 System UI)代理了其他应用的操作,导致统计归属错误。 |
9. 本篇总结(Knowledge Closure)
| 关键点 | 纯学术定义 |
|---|---|
| Battery Service 的本质 | 数据聚合器,从 Healthd 获取原始物理数据。 |
| Healthd 的本质 | 内核接口翻译官,轮询 sysfs 节点。 |
| 电量百分比算法 | OCV 电压查表与库仑计数积分的混合估算。 |
| BatteryStats 的本质 | 基于 UID 的差分累加器,利用 Power Profile 计算硬件功耗。 |
| 充电管理 | 恒流、恒压、涓流的三段式物理过程,受温度严格限制。 |
10. 第五板块结语
至此,第五板块:Android 系统服务与电源管理 已全部完结。
我们从 Power Manager Service 的 WakeLock 仲裁 出发,深入 Linux 内核的休眠机制 ,探索 Doze 模式的时空置换 ,最终抵达 Battery Service 的库仑计数与耗电统计。
我们揭示了 Android 电源管理的终极哲学:用最严苛的手段限制能量消耗,用最精确的算法度量能量流逝。
下一篇预告 :第六板块:Android 安全与权限体系 | 第十九篇:SELinux 强制访问控制与沙箱机制