简介: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:password@tcp(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 文件过大(超过 100MB),Go 进程默认内存限制不够。 解决:拆分 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_output=True, text=True) text = result.stdout # 提取唤醒锁次数和总时长 wake_lock_pattern = re.compile(r"WakeLock\s+(\S+):\s+(\d+)\s+times,\s+(\d+)\s+ms") 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(r"Job\s+(\S+):\s+(\d+)\s+times") 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 还是传感器,然后再去代码里搜对应的注册逻辑。这套流程跑顺之后,基本不用再靠猜。希望帮到你。
本文还有配套的精品资源,点击获取