自由跑和课程页当年都CPU出现高占用率,对于课程页还好CPU大概在20%~30%之间,但是在自由跑界面CPU占用率在40%~50%,导致我们的自由跑会出卡顿本文将带复盘当年的案发现场,一步步排查CPU占用高的原因,和一些有用的命令帮助我们更直观的感受CPU占用的

CPU占用率组成成分

我们Luancher的CPU的占用率组成的主要成分可分为

课程页

  1. 视频的渲染
  2. 机器控制中间层轮询读取底层数据

自由跑页

  1. 自由跑赛道渲染轮询线程
  2. 机器控制中间层轮询读取底层数据

从进程开始得到pid

1
top -m 10 -n 1 -s cpu

会得到如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
130|root@rk3188:/ # top -m 10 -n 1 -s cpu



User 14%, System 6%, IOW 0%, IRQ 0%
User 173 + Nice 3 + Sys 83 + Idle 942 + IOW 0 + IRQ 0 + SIRQ 0 = 1201

PID PR CPU% S #THR VSS RSS PCY UID Name
12806 2 15% S 50 911760K 85716K fg u0_a60 com.onespax.launcher
118 0 2% S 6 5640K 244K root /sbin/adbd
12783 1 1% S 1 904K 420K root logcat
105 0 0% S 15 55792K 2344K fg system /system/bin/surfaceflinger
12831 0 0% S 17 717672K 25128K fg u0_a60 com.onespax.launcher:service_manager
12785 0 0% S 1 0K 0K root kworker/0:1
12927 0 0% R 1 1280K 484K root top
405 1 0% S 62 760432K 37124K fg system system_server
978 1 0% S 1 0K 0K root kworker/1:2
8991 2 0% S 1 0K 0K root kworker/2:2

使用该条命名即可知道哪个进程使用占用CPU过多

从每个线程开始查找

接下从每个线程分析,到底哪个线程占用的CPU过多,对于Android5.0以下的设备,每个线程的状态可以通过Android Device Monitor查看(note:此时JDK版本最高为1.8_141,否则运行不起来),Android5.0及以上都可使用Android Studio自带的profiler查询,但是这些常看基本都是基于时间片来看的,也就是不能直观的看到XXX线程占用占用CPU%,如果有的话,就可以将范围缩小的某个范围,更容易排查。

线程重命名

如果发现有很多未命名的线程,先对自己创建的线程命名,对于单个线程的命名直接线程构造函数传入即可,对于线程池来算,可以参考Executors#DefaultThreadFactory来对线程池中线程重命名,方便后定位高CPU占用率定位

Linux看某个进程的线程

参考
Linux看某个进程的线程
例如查看pid=12806进程下的线程占用资源情况

1
top -t | grep "12806"

可得到如下log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
130|root@rk3188:/ # top -t | grep "12806"
12806 12905 1 6% R 914988K 84860K fg u0_a60 Render com.onespax.launcher
12806 12806 1 4% S 914988K 84860K fg u0_a60 nespax.launcher com.onespax.launcher
12806 12858 3 3% R 914988K 84860K fg u0_a60 YoumeiTreadmill com.onespax.launcher
12806 12808 1 0% S 914988K 84860K fg u0_a60 GC com.onespax.launcher
12806 12813 1 0% S 914988K 84860K fg u0_a60 Compiler com.onespax.launcher
12806 12827 3 0% S 914988K 84860K fg u0_a60 nespax.launcher com.onespax.launcher
12806 12811 2 0% S 914988K 84860K fg u0_a60 Signal Catcher com.onespax.launcher
12806 12812 2 0% S 914988K 84860K fg u0_a60 JDWP com.onespax.launcher
12806 12814 2 0% S 914988K 84860K fg u0_a60 ReferenceQueueD com.onespax.launcher
12806 12815 0 0% S 914988K 84860K fg u0_a60 FinalizerDaemon com.onespax.launcher
12806 12816 2 0% S 914988K 84860K fg u0_a60 FinalizerWatchd com.onespax.launcher
12806 12817 2 0% S 914988K 84860K fg u0_a60 Binder_1 com.onespax.launcher
12806 12818 2 0% S 914988K 84860K fg u0_a60 Binder_2 com.onespax.launcher
12806 12821 1 0% S 914988K 84860K fg u0_a60 Thread-140 com.onespax.launcher
12806 12822 2 0% S 914988K 84860K fg u0_a60 LeakCanary-Heap com.onespax.launcher
12806 12823 2 0% S 914988K 84860K fg u0_a60 StethoListener- com.onespax.launcher
12806 12828 1 0% S 914988K 84860K fg u0_a60 nespax.launcher com.onespax.launcher
12806 12829 3 0% S 914988K 84860K fg u0_a60 nespax.launcher com.onespax.launcher
12806 12830 2 0% S 914988K 84860K fg u0_a60 Binder_3 com.onespax.launcher
12806 12845 2 0% S 914988K 84860K fg u0_a60 RxSchedulerPurg com.onespax.launcher
12806 12846 3 0% S 914988K 84860K fg u0_a60 RxCachedWorkerP com.onespax.launcher
12806 12849 0 0% S 914988K 84860K fg u0_a60 RxCachedThreadS com.onespax.launcher
12806 12850 2 0% S 914988K 84860K fg u0_a60 RxComputationTh com.onespax.launcher
12806 12851 2 0% S 914988K 84860K fg u0_a60 pool-3-thread-1 com.onespax.launcher
12806 12859 1 0% S 914988K 84860K fg u0_a60 record-timer com.onespax.launcher
12806 12862 2 0% S 914988K 84860K fg u0_a60 pool-7-thread-1 com.onespax.launcher

可以看到top命令可以查看进程下的线程并显示出了CPU占用率。

发现问题及解决

接下来就可以定位到是循环读取底层指令的线程

1
2
3
4
5
6
7
8
9
while(true){
if(条件A){
Thread.sleep(300);
countinue;
}
// 读取底层数据
Uart485.getInstance().uReadBuf();
// 分发到上层回调
}

通过ddms分析可以知道是Uart485.getInstance().uReadBuf(cmd)在一段时间内执行的次数比较多,为什么执行的比价多,因为在我们指定的线程“TreadmillManager“的run方法中调用最多,回溯前面的代码可以看到,我们大多数条件下条件A是不满足的,我们最初想要的效果是无论什么条件,我们都需要sleep(300);所以上述代码会一直轮询,不会让出CPU时间,且这种高密度的调用对对我们没有任何意义,所以把Thread.sleep(300)拿到条件外面是最好的选择。