记一次简单的耗电发热问题排查过程

  • A+
所属分类:安全开发

最近我司开发了一个应用,我发现这个小小的应用在使用数分钟之后手机就会发烫,于是我觉得探查一下究竟,虽然好久没有做性能优化了。

我首先排查的是手机的耗电,我不清楚现在是否有更先进的工具可以分析 Android 系统的耗电情况;不过以前用过 battery-historian[1],效果也还行,于是打算使用这个看一看。翻阅了一下官方文档,发现这个工具用起来比以前方便多了,以前要装一个类似 eclipse 的应用;现在用 docker 一句话就搞定了:

docker -- run -p 9999:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999

执行这个命令后,docker 容器内部会启动一个 web 服务,我们上传手机的耗电统计情况,就可以可视化的分析了。

接下来我们通过 adb 命令来告诉 Android 系统我们需要重置耗电情况,这样可以避免之前的数据影响我们接下来的分析:

adb shell dumpsys batterystats --reset

执行完毕之后,我们打开目标应用,然后操作一段时间,操作结束之后,我们通过另外一条 adb 命令让 Android 系统导出系统的当前状态(包括耗电情况):

adb bugreport bugreport.zip

这条命令需要一定的时间,等命令结束之后,我们在本地浏览器打开 localhost:9999 会显示一个页面,在页面中有一个上传按钮,我们上传 bugreport.zip 这个文件然后提交,battery-historian 立刻给我们呈现了分析结果:

记一次简单的耗电发热问题排查过程

图中可以看出,耗电大头有两块:屏幕和 GPS;屏幕耗电没什么办法,GPS 长时间使用就有点不正常了:我们不应该长时间地使用 GPS 定位,可以把结果缓存起来,并且使用之后要关闭监听。battery-historian 的使用方法可以参考 Google 开发者教程[2] 和 README[3]

关闭 GPS 模块之后,发热情况没有明显的改观,于是我们继续查看;很显然手机发热一般直接原因是 CPU 使用过度,于是我打算查看一下 CPU 的使用率;为了方便,最好用悬浮窗展示,最后我发现一个名叫 CPU Float 的应用满足我的需求。

在安装好 CPU Float 之后,我们使用应用观察一段时间,发现 CPU 的核心频率一直居高不下,看起来好像是在做什么繁重的工作;「线程是CPU调度的最小单位」,我记得《操作系统》教材如是说。接下来我打算看一下这个应用里面的 CPU 都在干什么;于是我使用 Android Studio 的 profiler 查看了一下应用的线程运行情况:

记一次简单的耗电发热问题排查过程

经过观察,主要是四个线程在占用 CPU,其中有一个线程几乎没有 sleep 是一直在运行的;感觉不太科学,于是我拿着线程名去搜索代码,于是发现:


while (true) { Object obj = mConcurrentLinkedQueue.poll(); if (obj != null) { // do something }}

这里明显是不对的,ConcurrentLinkedQueue 是通过 CAS 机制实现的非阻塞队列,这里的 poll 方法,如果在队列为空的时候,会立即返回 null;因此,当队列在一段时间内是空的时候,上述代码基本上等于死循环。改法也很简单,把 ConcurrentLinkedQueue 换成某种阻塞队列 BlockingQueue,使得队列为空的时候,通过阻塞队列的阻塞接口(take/put)使线程进入阻塞状态然后让出时间片就可以了。这两个队列的区别可以参考文章LinkedBlockingQueue vs ConcurrentLinkedQueue[4]

这个阻塞队列让我回想起了之前在支付宝做性能优化的时候遇到的一个问题,当时我发现我们的日志模块在启动过程中竟然会耗费不少时间;日志模块写日志是一个异步的过程,打印日志的语句实际上是把日志扔到一个队列里面去,然后另外一个线程读队列并写入文件,这个队列当时使用的是 ArrayBlockingQueue,这个阻塞队列实际上是通过锁实现的,因此如果有大量日志打印,可能会让输出日志的线程进入 waiting 状态;于是我把它换成了一个无锁的数据结构 ConcurrentLinkedQueue,尽量让主线程一路狂奔,启动时间立马就快了几十毫秒。

在把 GPS 问题和队列问题改掉之后,耗电和发热状态终于恢复了正常。虽然是个小问题,排查起来也并不困难,不过还是记录一下给需要的人吧~

References

[1] battery-historian: https://github.com/google/battery-historian
[2] Google 开发者教程: https://developer.android.google.cn/topic/performance/power/battery-historian
[3] README: https://github.com/google/battery-historian#getting-started
[4] LinkedBlockingQueue vs ConcurrentLinkedQueue: https://www.baeldung.com/java-queue-linkedblocking-concurrentlinked


本文始发于微信公众号(虚拟框架):记一次简单的耗电发热问题排查过程

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: