[关闭]
@TryLoveCatch 2021-04-09T10:28:14.000000Z 字数 5774 阅读 1141

Android性能优化之测试

android 性能优化 测试


Android基础性能评估原理主要参考腾讯GT (https://github.com/Tencent/GT)和滴滴DoraemonKit(https://github.com/didi/DoraemonKit)下面详细看一下每种数据的获取原理:

CPU

AndroidO以下

/proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为内核与进程提供通信的接口。用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取/proc目录中的文件时,proc文件系统是动态从系统内核读出所需信息并提交的。 从proc文件中可以获取系统、进程、线程的cpu时间片使用情况,所以两次采集时间片的数据就可以获取进程CPU占用率, CPU占用率 = (进程T2-进程T1)/(系统T2-系统T1) 的时间片比值。由于Android8.0以后 /proc/stat权限收到管控,此方法不再适用

获取系统CPU时间片

获取系统CPU时间片使用情况:读取proc/stat,文件的内容如下:

  1. cpu 2032004 102648 238344 167130733 758440 15159 17878 0
  2. cpu0 1022597 63462 141826 83528451 366530 9362 15386 0
  3. cpu1 1009407 39185 96518 83602282 391909 5796 2492 0
  4. intr 303194010 212852371 3 0 0 11 0 0 2 1 1 0 0 3 0 11097365 0 72615114 6628960 0 179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  5. ctxt 236095529
  6. btime 1195210746
  7. processes 401389
  8. procs_running 1
  9. procs_blocked 0

第一行各个字段的含义:

  1. user (14624) 从系统启动开始累计到当前时刻,处于用户态的运行时间,不包含 nice值为负进程。
  2. nice (771) 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间
  3. system (8484) 从系统启动开始累计到当前时刻,处于核心态的运行时间
  4. idle (283052) 从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间
  5. iowait (0) 从系统启动开始累计到当前时刻,IO等待时间(since 2.5.41)
  6. irq (0) 从系统启动开始累计到当前时刻,硬中断时间(since 2.6.0-test4)
  7. softirq (62) 从系统启动开始累计到当前时刻,软中断时间(since 2.6.0-test4)

总的cpu时间totalCpuTime = user + nice + system + idle + iowait + irq + softirq

获取进程和线程的CPU时间片

获取进程CPU时间片使用情况:读取proc/pid/stat

获取线程CPU时间片使用情况:读取proc/pid/task/tid/stat,这两个文件的内容相同,如下

  1. 6873 (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0

各个字段的含义:

  1. pid=6873 进程(包括轻量级进程,即线程)号
  2. comm=a.out 应用程序或命令的名字
  3. task_state=R 任务的状态,R:runnign, S:sleeping (TASK_INTERRUPTIBLE), D:disk sleep (TASK_UNINTERRUPTIBLE), T: stopped, T:tracing stop,Z:zombie, X:dead
  4. ppid=6723 父进程ID
  5. pgid=6873 线程组号
  6. sid=6723 c该任务所在的会话组ID
  7. tty_nr=34819(pts/3) 该任务的tty终端的设备号,INT34817/256)=主设备号,(34817-主设备号)=次设备号
  8. tty_pgrp=6873 终端的进程组号,当前运行在该任务所在终端的前台任务(包括shell 应用程序)的PID
  9. task->flags=8388608 进程标志位,查看该任务的特性
  10. min_flt=77 该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数
  11. cmin_flt=0 累计的该任务的所有的waited-for进程曾经发生的次缺页的次数目
  12. maj_flt=0 该任务需要从硬盘拷数据而发生的缺页(主缺页)的次数
  13. cmaj_flt=0 累计的该任务的所有的waited-for进程曾经发生的主缺页的次数目
  14. utime=1587 该任务在用户态运行的时间,单位为jiffies
  15. stime=1 该任务在核心态运行的时间,单位为jiffies
  16. cutime=0 累计的该任务的所有的waited-for进程曾经在用户态运行的时间,单位为jiffies
  17. cstime=0 累计的该任务的所有的waited-for进程曾经在核心态运行的时间,单位为jiffies
  18. priority=25 任务的动态优先级
  19. nice=0 任务的静态优先级
  20. num_threads=3 该任务所在的线程组里线程的个数
  21. it_real_value=0 由于计时间隔导致的下一个 SIGALRM 发送进程的时延,以 jiffy 为单位.
  22. start_time=5882654 该任务启动的时间,单位为jiffies
  23. vsize=1409024page 该任务的虚拟地址空间大小
  24. rss=56(page) 该任务当前驻留物理地址空间的大小
  25. rlim=4294967295bytes 该任务能驻留物理地址空间的最大值
  26. start_code=134512640 该任务在虚拟地址空间的代码段的起始地址
  27. end_code=134513720 该任务在虚拟地址空间的代码段的结束地址
  28. start_stack=3215579040 该任务在虚拟地址空间的栈的结束地址
  29. kstkesp=0 esp(32 位堆栈指针) 的当前值, 与在进程的内核堆栈页得到的一致.
  30. kstkeip=2097798 指向将要执行的指令的指针, EIP(32 位指令指针)的当前值.
  31. pendingsig=0 待处理信号的位图,记录发送给进程的普通信号
  32. block_sig=0 阻塞信号的位图
  33. sigign=0 忽略的信号的位图
  34. sigcatch=082985 被俘获的信号的位图
  35. wchan=0 如果该进程是睡眠状态,该值给出调度的调用点
  36. nswap swapped的页数,当前没用
  37. cnswap 所有子进程被swapped的页数的和,当前没用
  38. exit_signal=17 该进程结束时,向父进程所发送的信号
  39. task_cpu(task)=0 运行在哪个CPU
  40. task_rt_priority=0 实时进程的相对优先级别
  41. task_policy=0 进程的调度策略,0=非实时进程,1=FIFO实时进程;2=RR实时进程

进程的总cpu时间processCpuTime = utime + stime + cutime + cstime
线程的总cpu时间threadCpuTime = utime + stime + cutime + cstime
我们统计的cpu使用,需要将采集引入线程的损耗在总体的CPU使用中排除。
计算后的cpu总时间processCpuTimeFinal = processCpuTime - collectThreadTime

cpu使用率:( processCpuTime - collectThreadTime)/CpuTotal

Android O及以上

Android O及以上无法在读取/proc/stat文件,计算CPU可以依赖一个命令行工具:

  1. top -n 1

意思是获取1次,当前所有进程的状态,其具体输出形式如下图,我们可以根据当前进程的PID找到相应的一行,然后读取CPU这一列。

注意:
CPU这一列的意思是相对于单核的使用率,也就是说如果这一列显示50%的使用率,实际上只是单核跑到50%,如果手机有8核,那么实际使用率应该是 50%/8 = 6.25%

内存

系统内存

  1. // 只需要通过ActivityManager即可获取
  2. ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
  3. ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
  4. am.getMemoryInfo(mi);
  5. mi.availMem; // 空闲内存
  6. mi.totalMem; // 总内存

进程内存

进程内存上限

  1. //进程内存上限
  2. public static int getMemoryMax() {
  3. return (int) (Runtime.getRuntime().maxMemory()/1024);
  4. }

进程总内存

  1. //进程总内存
  2. public static int getPidMemorySize(int pid, Context context) {
  3. ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
  4. int[] myMempid = new int[] { pid };
  5. Debug.MemoryInfo[] memoryInfo = am.getProcessMemoryInfo(myMempid);
  6. int memSize = memoryInfo[0].getTotalPss();
  7. // dalvikPrivateDirty: The private dirty pages used by dalvik。
  8. // dalvikPss :The proportional set size for dalvik.
  9. // dalvikSharedDirty :The shared dirty pages used by dalvik.
  10. // nativePrivateDirty :The private dirty pages used by the native heap.
  11. // nativePss :The proportional set size for the native heap.
  12. // nativeSharedDirty :The shared dirty pages used by the native heap.
  13. // otherPrivateDirty :The private dirty pages used by everything else.
  14. // otherPss :The proportional set size for everything else.
  15. // otherSharedDirty :The shared dirty pages used by everything else.
  16. return memSize;
  17. }

FPS以及页面丢帧

所在页面的FPS:这个值主要受 当前VIewTree中 所有view onDraw()方法耗时的影响,有现成的方法可以抓取。详细方法如下介绍。

页面FPS

首先Android的帧绘制流程是:CPU主线程图像处理->GPU进行光栅化->显示帧。APP产生掉帧的情况大多是由“CPU主线程图像处理”这一步超负载引起的,所以我们思考如何去监控主线程绘制情况。要检测CPU绘制帧的时间,就必须找到那个调用View.dispatchDraw的类,Choreographer类就是那个接受系统垂直同步信号,主线程在每次接受时同步信号触发View的Input、Animation、Draw等操作。

所以我们可以向Choreographer类中加入自己的Callback来冒充View的Callback,通过此Callback我们可以获得View绘制的时间、可以统计一秒内帧绘制的能力。

注意:Choreographer是线程级别的对象,每个线程都会有一个Choreographer对象,vsync由native回调上来以后,会分发给所有的Choreographer对象,如果当前线程不卡顿,很有可能 检测到的帧率一直是60,对于页面的FPS,只有使用主线程的对象才有意义。

一段时间内的fps = frame数量/时间差

代码实现:

  1. long lastTime = 0;
  2. long thisTime = 0;
  3. int frame =0;
  4. @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
  5. void startMonitor(){
  6. Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {//系统绘帧回调
  7. public void doFrame(long frameTimeNanos) {
  8. thisTime = frameTimeNanos/1000000
  9. //判别超时:
  10. if (thisTime - lastTime > 40 && lastTime!=0){
  11. Log.e("TAG","frame超时"+(thisTime - lastTime)+"ms: "+ lastTime +"-"+ thisTime);
  12. }
  13. frame++
  14. //设置下一帧绘制的回调
  15. Choreographer.getInstance().postFrameCallback(this);//设置下次系统绘帧回调
  16. lastTime = thisTime;
  17. }
  18. };
  19. Choreographer.getInstance().postFrameCallback(frameCallback);
  20. }

页面丢帧

知道了页面的FPS,页面丢帧也非常好计算:

丢帧= 时间差*60-总帧数

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注