一、背景
今天来总结一下,自己在项目中遇到的一个CPU占用过高的问题,详细的结束从发现到定位在到解决问题的过程。
原因是性能测试那边提出了一个bug,就是在投屏过程中,平板端也就是Sink端功耗非常高的问题,
二、排查问题
经过排查后发现:需要手机投平板后在手机侧拖动平板断开,平板端的一个进程CPU占用率会一直上升,且每次重复此操作后CPU占用率都会上升,高概率复现
可以输入 adb shell top -m 10 确定排名前10的进程
三、使用android Profile工具分析
接下来可以使用Android Studio打开android Profile工具,通过选中自己应用的进程,然后点击CPU可以查看CPU的使用情况。
我们通过复现在Source端断开,可以看到Sink端的cpu使用情况:在断开后,按理说是没有线程在跑的,但是从下图可以看到其中有个线程StreamSinker_Ou
一直在使用CPU,那么就可以确认是此线程出问题了。
四、使用simpleperf工具
4.1 simpleperf工具介绍
通过上面步骤后,我们可以使用simpleperf工具进行进一步分析:
simpleperf工具是在NDK的根目录下
当然Android设备都会有此工具,因此可以直接使用
4.2 simpleperf工具使用
4.2.1 抓取进程数据
simpleperf record -p 30382 --call-graph fp -o data/perf.data
执行完上面命令后,就会在data目录下生成perf.data文件
4.2.2 解析进程数据
simpleperf report -g -i data/perf.data > data/perf.log
执行完上面命令后,就可以将生成的perf.data文件解析出perf.log文件,此文件方便我们来判断具体是哪里占用CPU过高.
4.2.3 查看perf.log文件
打开perf.log文件我们可以看到占用CPU的具体的地方:是底层的android::StreamSinker::SinkerOutThread::threadLoop()
方法
五、代码分析
通过上面分析后可以知道是底层的StreamSinker
类的SinkerOutThread
内部类的threadLoop()
方法导致CPU占用过高。
此方法是一个wile
循环使用一个bool
变量进行控制,也就是说在断开后,此bool
值没有置为false
,控制此bool
值为false
也就是上图的stop
方法里。
通过代码的定位最终定位到了MultiscreenSink
类的doMessageReceived
方法的kWhatStop
条件中
而触发此kWhatStop
条件是在MultiscreenSink
类的stop
方法中
status_t MultiscreenSink::stop(void) {
LOGD(NEGOTIATE_DEBUG,"stop prepare send kWhatStop messg");
sp<AMessage> msg = new AMessage(kWhatStop, mWorkHandler);
msg->post();
return OK;
}
我们在通过复现时的日志可以发现:刚调用stop
方法,就开始走了MultiscreenSink
类的析构方法,导致stop方法里AMessage还未及时发送出去
六、问题解决
通过上面代码分析后,知道了具体的问题在哪里,那么接下来就可以更好的解决问题了,具体解决方法这里就不在描述了,只要知道具体问题点了,那么就会有很多方案去处理。
之后解决的效果如下,基本上投屏中CPU占用始终在70%左右,退出投屏此进程也就不在top 10中了。
参考:
解决CPU使用过高问题