简介battery-historian.zip 是一款面向 Android 开发者与系统调试人员的电量分析工具包用于解析 bugreport 或 adb 日志中的电池状态数据生成电量曲线、唤醒锁详情与应用耗电排行等可视化报告帮助定位过度唤醒、后台活动频繁等耗电问题适合具备一定 Android 调试基础、需要优化应用性能或排查续航异常的读者。压缩包共约 2000 个文件整体 26.66MB以 js、html 前端资源为主辅以 go、py 源码及 css、gif、png 等界面素材另含 proto、sh、bat 等脚本与配置结构完整。目前已有 546 人学习下载。包内提供已编译版本解压后运行 run.bat 即可启动可绕开 submit 不显示等常见异常借助其可视化图表读者能快速定位高耗电应用与系统服务为优化电源管理、延长电池寿命提供数据依据。1. 拿到 battery-historian.zip 之后Android 耗电排查到底在查什么很多人第一次拿到battery-historian.zip以为解压完就能看到一个“一键分析耗电”的桌面软件结果打开目录发现是一堆 Go 源码、前端静态资源和 Docker 配置瞬间懵了。它其实是一套面向 Android 电量诊断的可视化分析工具核心工作是把设备导出的bugreport或batterystats文本解析成时间轴、唤醒锁、JobScheduler、传感器、网络等多维图表。它解决的不是“哪个 App 最耗电”这种粗粒度问题而是“谁在后台反复唤醒 CPU、谁持锁不放、谁在息屏后偷偷跑”这类需要时间线证据的疑难杂症。适合两类人一类是做 ROM 或系统优化的工程师另一类是 App 开发中遇到“用户反馈待机掉电快”却复现不出来的同学。如果你只会看系统设置里的电量排行那这套东西能把你从“猜”拉到“有证据”。2. 从 zip 到可访问页面本地跑通 battery-historian 的最小路径2.1 先搞清楚 zip 里有什么别急着 docker run解压后通常能看到几个关键部分Go 后端源码、前端静态资源目录、Dockerfile、以及一个用于初始化数据库的脚本。常见做法是直接用 Docker 跑因为依赖 Go 环境和数据库初始化比较繁琐。但如果你所在环境不能拉镜像也可以本地编译。先确认目录结构重点找Dockerfile和README里提到的端口一般是9999。不要一上来就改代码先按默认配置跑通再谈定制。# 解压后进入目录查看关键文件 unzip battery-historian.zip -d battery-historian cd battery-historian ls -la # 常见会看到 Dockerfile、main.go、static/、scripts/ 等逻辑说明这一步只是确认 zip 内容完整避免解压到一半文件缺失。参数上没什么可调的但要注意如果 zip 是在 Windows 下打包的Linux 解压后可能权限不对需要给脚本加执行权限。2.2 用 Docker 启动端口映射和挂载目录的坑最省事的启动方式就是构建镜像并运行。注意端口映射要写对很多教程只写-p 9999:9999但如果你本机 9999 被占用就换成-p 19999:9999。另外如果你想让容器直接读取宿主机上的 bugreport 文件可以挂载一个目录进去省得反复拷贝。# 构建镜像注意最后有个点 docker build -t battery-historian-local . # 运行容器把本机 19999 映射到容器 9999 docker run -d --name bh \ -p 19999:9999 \ -v /home/user/bugreports:/data \ battery-historian-local逻辑说明-d后台运行--name给容器起名方便后续查看日志。-v挂载目录后你可以在容器内直接访问/data下的文件。参数上如果构建时网络慢可以换基础镜像源但不要改Dockerfile里的端口暴露除非你同步改前端请求地址。2.3 不用 Docker 的本地编译方式Go 环境与数据库初始化有些公司内网不能跑 Docker那就本地编译。需要 Go 1.16 以上以及一个可用的数据库通常是 SQLite 或 MySQL。看main.go里的数据库连接字符串如果是 SQLite直接编译运行即可如果是 MySQL要先建库并导入scripts/下的 SQL。# 安装依赖并编译 go mod tidy go build -o battery-historian . # 如果使用 SQLite直接运行 ./battery-historian --port 9999 # 如果使用 MySQL先初始化数据库 mysql -u root -p -e CREATE DATABASE bh; mysql -u root -p bh scripts/init.sql ./battery-historian --port 9999 --db root:passwordtcp(127.0.0.1:3306)/bh逻辑说明go mod tidy拉取依赖go build生成二进制。参数--port指定监听端口--db指定数据库连接串。如果编译报错找不到包检查 Go 版本和代理设置。数据库初始化脚本里通常包含表结构不要跳过。2.4 生成一份能用的 bugreport抓取命令与关键开关工具跑起来了但没有数据可分析。你需要从 Android 设备上抓一份包含电量统计的 bugreport。注意普通adb bugreport可能不包含batterystats详细数据需要先启用相关服务。常见做法是# 重置电量统计让数据从当前开始 adb shell dumpsys batterystats --reset # 断开设备模拟待机一段时间比如 30 分钟 # 然后重新连接抓取完整 bugreport adb bugreport bugreport.zip # 如果只想抓 batterystats 部分可以单独导出 adb shell dumpsys batterystats batterystats.txt逻辑说明--reset会清空历史统计让后续数据更干净。bugreport生成的是 zip 包里面包含多个文本文件。单独导出batterystats.txt体积更小但缺少系统全局信息。参数上待机时间越长后台唤醒问题越容易暴露但文件也越大。建议至少 30 分钟且期间不要充电。3. 把 bugreport 丢进去之后时间轴、唤醒锁与 JobScheduler 怎么读3.1 上传与解析为什么你的文件一直转圈打开页面后上传bugreport.zip或batterystats.txt如果一直转圈多半是文件格式不对或版本不匹配。工具对 Android 版本有要求太老的版本比如 Android 5 以下解析可能失败。另外如果 bugreport 是在设备刚重启后抓的数据量太少也会解析异常。先看容器日志通常会有报错提示。# 查看容器日志定位解析失败原因 docker logs bh --tail 100逻辑说明日志里会显示解析到哪一步失败比如“unexpected token”或“missing section”。如果是版本问题换一台新设备抓取。参数上不要用第三方修改过的 bugreport尽量用原生adb bugreport。3.2 时间轴图横轴上的每个凸起都对应一次唤醒解析成功后最先看的是时间轴。横轴是时间纵轴是电量或 CPU 状态。如果看到息屏后电量曲线依然陡峭下降同时下方有密集的唤醒条说明有东西在阻止系统进入深度睡眠。点击唤醒条会显示对应的进程和唤醒锁名称。常见的有WakeLock、AlarmManager、JobScheduler。这一步的关键是找到“谁在什么时候唤醒了多久”。3.3 唤醒锁与 JobScheduler 的关联排查唤醒锁是应用直接持有的而 JobScheduler 是系统调度的。有时候你看到某个 Job 频繁执行但它本身不持锁而是触发了另一个服务去持锁。这时候要在时间轴上对齐看Job 执行的时间点是否紧接着出现了唤醒锁。常见做法是先在 JobScheduler 面板按执行次数排序找到异常频繁的 Job记下它的包名和时间再回到时间轴过滤该包名。# 从 batterystats.txt 中直接搜索 Job 和 WakeLock 关键字 grep -n JobScheduler batterystats.txt | head -50 grep -n WakeLock batterystats.txt | head -50逻辑说明命令行 grep 可以快速定位文本中的关键段落适合在网页加载慢时应急。参数上-n显示行号head限制输出条数。注意 batterystats 文本很大不要一次性全看先过滤包名。3.4 传感器与网络息屏后的隐形耗电大户除了唤醒锁传感器和网络也是待机耗电的常客。在工具里看“Sensor”面板如果某个传感器在息屏后仍有大量激活记录比如加速度计或气压计说明有应用在后台持续监听。网络方面看“Mobile Network”或“WiFi”的活跃时间如果息屏后仍有频繁的小数据包传输可能是推送心跳或同步任务。这些在时间轴上都能和电量下降对应起来。4. 避坑与排查battery-historian 落地时最容易翻车的 5 个点4.1 现象上传后页面空白日志显示“no data”原因bugreport 里没有包含batterystats服务数据常见于未启用batterystats或抓取命令不对。 解决先执行adb shell dumpsys batterystats --enable full-wake-history再重置并重新抓取。注意有些定制系统默认关闭详细统计。4.2 现象时间轴上的时间戳全是 1970 年原因设备时间未同步或者 bugreport 中的时区信息丢失。 解决抓取前确保设备联网自动同步时间或者手动设置正确时区。解析时工具会按文本里的时间戳转换时区错会导致整个时间轴偏移。4.3 现象Docker 容器启动后立刻退出原因数据库连接失败或者端口被占用。 解决看docker logs如果是数据库问题检查--db参数或 SQLite 文件路径权限。如果是端口换映射端口。不要盲目重启先看日志。4.4 现象解析大文件时内存溢出原因bugreport 文件过大超过 100MBGo 进程默认内存限制不够。 解决拆分 bugreport或者只上传batterystats.txt。也可以在Dockerfile里调大GOGC或直接给容器更多内存。4.5 现象唤醒锁名称显示为“unknown”原因部分系统权限限制应用持有的锁没有记录到 batterystats 中。 解决需要 root 权限或系统签名权限才能看到完整锁信息。普通设备上只能看到系统级锁第三方应用的锁可能被隐藏。这时候要结合dumpsys power和dumpsys alarm交叉验证。5. 进阶用脚本批量提取关键指标把 battery-historian 变成日常巡检工具跑通单次分析后你可能会想能不能每天自动抓取、自动解析、自动报警可以但不要指望 battery-historian 本身提供 API它的强项是可视化。我的做法是写一个 Python 脚本调用adb抓取 batterystats然后用正则提取关键指标最后把结果推送到内部看板。这样即使不打开网页也能知道待机耗电是否异常。import subprocess import re import datetime # 抓取 batterystats subprocess.run([adb, shell, dumpsys, batterystats, --reset]) # 模拟待机实际使用时可以 sleep 一段时间 # subprocess.run([sleep, 1800]) result subprocess.run([adb, shell, dumpsys, batterystats], capture_outputTrue, textTrue) text result.stdout # 提取唤醒锁次数和总时长 wake_lock_pattern re.compile(rWakeLock\s(\S):\s(\d)\stimes,\s(\d)\sms) locks wake_lock_pattern.findall(text) for name, count, duration in locks: if int(count) 100: print(f[{datetime.datetime.now()}] 异常唤醒锁: {name}, 次数: {count}, 总时长: {duration}ms) # 提取 JobScheduler 执行次数 job_pattern re.compile(rJob\s(\S):\s(\d)\stimes) jobs job_pattern.findall(text) for name, count in jobs: if int(count) 50: print(f[{datetime.datetime.now()}] 频繁 Job: {name}, 次数: {count})逻辑说明脚本先重置统计待机一段时间后抓取文本用正则匹配唤醒锁和 Job 的执行次数。参数上阈值100和50需要根据你的业务调整不是固定值。注意dumpsys batterystats输出格式可能随 Android 版本变化正则要定期维护。这个脚本适合放在 CI 里每天跑一次对比历史数据。另外如果你想把 battery-historian 的图表也自动化可以研究它的前端请求接口用 HTTP 请求直接上传文件并获取 JSON 结果。但我不建议一开始就这么做因为接口没有官方文档版本升级容易翻车。先手动分析几轮搞清楚哪些指标真正影响你的场景再考虑自动化。我自己的习惯是每次拿到新的待机耗电反馈先抓 30 分钟 bugreport丢进 battery-historian 看时间轴确认是唤醒锁还是 Job 还是传感器然后再去代码里搜对应的注册逻辑。这套流程跑顺之后基本不用再靠猜。希望帮到你。本文还有配套的精品资源点击获取