Android性能相关--性能指标篇

性能指标

Android性能相关--性能指标篇_第1张图片

流畅性

FPS:SurfaceFlinger 合成次数
  1. FPS低可能是当前没有内容更新!
  2. 屏幕内若有多块显示区域(Surface),则我们的区域可能受影响
  3. 某些Surface合成不在SF中进行(Camera),则FPS无法衡量

FPS的计算,系统提供adb命令service call SurfaceFlinger 1013来获取从启动当前SF一共进行了多少次合成。
T1时间获取合成次数V1,T2时间获取合成次数V2
FPS = (V2-V1)/(T2-T1)

掉帧率
  1. SF:修改系统属性debug.choreographer.skipwarning=1,则丢帧时都会打印日志。(限制:adb需要root权限)
    setprop debug.choreographer.skipwarning 1
    setprop ctl.restart surfaceflinger; setprop ctl.restart zygote

  2. SM:代码注入,监控Choreographer.FrameCallback,每调用一次就是一次渲染,相邻两次之间时间超过16ms则丢帧(限制:需要嵌入代码)

long lastTime = 0;
    long thisTime = 0;
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    void startMonitor(){
        Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {//系统绘帧回调
            public void doFrame(long frameTimeNanos) {

                thisTime = System.currentTimeMillis();
                //累计流畅值
                plusSM(thisTime);//当前秒的SM+1,如果当前秒数已经到下一秒,则将此SM值写入文件
                //判别超时:
                if (thisTime - lastTime > 40 && lastTime!=0){
                    Log.e("DDDD","frame超时"+(thisTime - lastTime)+"ms: "+ lastTime +"-"+ thisTime);
                    //saveBlockInfo(lastTime, thisTime);//此处保存卡顿信息
                }
                //设置下一帧绘制的回调
                Choreographer.getInstance().postFrameCallback(this);//设置下次系统绘帧回调
                lastTime = thisTime;
            }
        };
        Choreographer.getInstance().postFrameCallback(frameCallback);
    }

    //保存当前SM值
    private long nowTime =1;//当前的时间(ms)
    private int sm = 1;
    private void plusSM(long t){
        if (nowTime ==1){
            nowTime = t;
        }
        if (nowTime/1000 == t/1000){
            sm++;
        }else if (t/1000 - nowTime/1000 >=1){
            //saveSMInfo(sm,t);//此处保存此时的流畅值SM
            Log.e("DDDD","sm:"+sm);
            sm=1;
            nowTime = t;
        }
    }
  1. Janky frames:

adb shell dumpsys gfxinfo < PACKAGE_NAME > 打印最近128帧信息

Graphics info for pid 31148 [com.android.settings]: 表明当前dump的为设置界面的帧信息,pid为31148
Total frames rendered: 105 本次dump搜集了105帧的信息
Janky frames: 2 (1.90%) 105帧中有2帧的耗时超过了16ms,卡顿概率为1.9%
Number Missed Vsync: 0 垂直同步失败的帧
Number High input latency: 0 处理input时间超时的帧数
Number Slow UI thread: 2 因UI线程上的工作导致超时的帧数
Number Slow bitmap uploads: 0 因bitmap的加载耗时的帧数
Number Slow issue draw commands: 1 因绘制导致耗时的帧数

Activity启动时长、首页启动时长

Hook点

	hook android.app.Instrumentation.execStartActivity函数    	:startActivity函数
	hook android.app.Instrumentation.callActivityOnCreate函数  	:onCreate函数
	hook android.app.Instrumentation.callActivityOnStart函数		:onStart函数
	hook android.app.Instrumentation.callActivityOnResume函数		:onResume函数
	hook android.app.Instrumentation.callActivityOnPause函数		:onPause函数
	hook android.app.Instrumentation.callActivityOnStop函数		:onStop函数
Fragment启动时长

Hook点

//android.app.Fragment包:
	hook android.app.Fragment.onAttach					:onAttach
	hook android.app.Fragment.performCreate				:onCreate
	hook android.app.Fragment.performCreateView			:onCreateView
	hook android.app.Fragment.performActivityCreated	:onActivityCreated
	hook android.app.Fragment.performStart				:onStart
	hook android.app.Fragment.performResume				:onResume
	hook android.app.Fragment.performPause				:onPause
	hook android.app.Fragment.performStop				:onStop
	hook android.app.Fragment.performDestroyView		:onDestoryView
	hook android.app.Fragment.performDestroy			:onDestory
	hook android.app.Fragment.performDetach				:onDetach
	hook android.app.Fragment.onHiddenChanged			:onHiddenChanged
	hook android.app.Fragment.setUserVisibleHint		:setUserVisibleHint


//android.support.v4.app.Fragment包:
	hook android.support.v4.app.Fragment.onAttach					:onAttach
	hook android.support.v4.app.Fragment.performCreate				:onCreate
	hook android.support.v4.app.Fragment.performCreateView			:onCreateView
	hook android.support.v4.app.Fragment.performActivityCreated		:onActivityCreated
	hook android.support.v4.app.Fragment.performStart				:onStart
	hook android.support.v4.app.Fragment.performResume				:onResume
	hook android.support.v4.app.Fragment.performPause				:onPause
	hook android.support.v4.app.Fragment.performStop				:onStop
	hook android.support.v4.app.Fragment.performDestroyView			:onDestoryView
	hook android.support.v4.app.Fragment.performDestroy				:onDestory
	hook android.support.v4.app.Fragment.performDetach				:onDetach
	hook android.support.v4.app.Fragment.onHiddenChanged			:onHiddenChanged
	hook android.support.v4.app.Fragment.setUserVisibleHint			:setUserVisibleHint

CPU数据

/proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为内核与进程提供通信的接口。用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某些参数。

/proc/cpuinfo 获取CPU硬件信息(型号、频率、缓存大小等)
/proc/stat 获取CPU总体活动信息
/proc/< pid >/stat 获取某进程的CPU活动信息
/proc/< pid >/task/< tid >/stat 获取某线程的CPU活动信息

eg:stat文件获取到的信息的单位是jiffies(时间片,记录系统启动以来的节拍数,不同内核一个节拍的时间不同,通常1~10ms)

/proc/stat

fjzag@fjzag-desktop:~$ cat /proc/stat
cpu  38082 627 27594 893908 12256 581 895 0 0
cpu0 22880 472 16855 430287 10617 576 661 0 0
cpu1 15202 154 10739 463620 1639 4 234 0 0
intr 120053 222 2686 0 1 1 0 5 0 3 0 0 0 47302 0 0 34194 29775 0 5019 845 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
ctxt 1434984
btime 1252028243
processes 8113
procs_running 1
procs_blocked 0
  • user (38082) 从系统启动开始累计到当前时刻,处于用户态的运行时间,不包含 nice值为负进程。
  • nice (627) 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间
  • system (27594) 从系统启动开始累计到当前时刻,处于核心态的运行时间
  • idle (893908) 从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间iowait (12256) 从系统启动开始累计到当前时刻,IO等待时间(since 2.5.41)
  • irq (581) 从系统启动开始累计到当前时刻,硬中断时间(since 2.6.0-test4)
  • softirq (895) 从系统启动开始累计到当前时刻,软中断时间
  • guest_nice 从系统启动开始累计到当前时刻,在Linux内核控制下的操作系统虚拟cpu花费在nice进程上的时间

/proc/< pid >/stat 和 /proc/< pid >/task/< tid >/stat

[zhengangen@buick ~]# cat /proc/6873/stat
1 (linuxrc) S 0 0 0 0 -1 8388864 50 633 20 4 2 357 72 342 16 0 1 0 22 2252800 70 4294967295 32768 1879936 3199270704 3199269552 1113432 0 0 0 674311 3221479524 0 0 0 0 0 0

看下 2 357 72 342 这段数据

  • utime=2 该任务在用户态运行的时间
  • stime=357 该任务在核心态运行的时间
  • cutime=72 累计的该任务的所有的waited-for进程曾经在用户态运行的时间
  • cstime=342 累计的该任务的所有的waited-for进程曾经在核心态运行的时间

计算方法:

  • 某时间段内系统CPU使用率:
    1.将T2和T1的总CPU时间片(user、nice、system、idle、iowait、irq、softirq、stealstolen、guest相加)相减,得到这段时间的总CPU时间片s1
    2.将T2.idle和T1.idle相减得到这段时间的总CPU空闲时间s2
    3.该时间系统CPU使用率为 100 * ( s1 - s2 )/ s1
  • 某时间段内某进程CPU使用率:
    1.将T2和T1的总CPU时间片(user、nice、system、idle、iowait、irq、softirq、stealstolen、guest相加)相减,得到这段时间的总CPU时间片s1
    2.将T2和T1的该进程总CPU时间片(utime、stime、cutime、cstime相加)相减,得到这段时间内的该进程CPU时间片s2
    3.该时间段内该进程CPU使用率为:100 * s2 / s1

内存

系统总内存的获取

读取/proc/meminfo文件的第一个字段MemTotal即可

系统空闲内存
//通过ActivityManager获取
public static long getSysFreeMemory(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
        am.getMemoryInfo(mi);
        return mi.availMem;
 }
进程内存
//进程内存上限
//进程内存上限
public static int getMemoryMax() {
	return (int) (Runtime.getRuntime().maxMemory()/1024);
}
//进程总内存
public static int getPidMemorySize(int pid, Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    int[] myMempid = new int[] { pid };
    Debug.MemoryInfo[] memoryInfo = am.getProcessMemoryInfo(myMempid);
    int memSize = memoryInfo[0].getTotalPss();
    return memSize;
}
系统内存快照

adb shell dumpsys meminfo < packageName >

流量

TrafficStats类是由Android提供的一个从你的手机开机开始,累计到现在使用的流量总量,或者统计某个或多个进程或应用所使用的流量,当然这个流量包括的Wifi和移动数据网Gprs。

//系统流量统计:
TrafficStats.getTotalRxBytes() ——获取从此次开机起总接受流量(流量是分为上传与下载两类的,当然其实这里还有本地文件之间数据交换的流量,这个暂且不说,等下说明一下我遇到的问题);
TrafficStats.getTotalTxBytes()——获取从此次开机起总发送流量;
TrafficStats.getMobileRxBytes()——获取从此次开机起不包括Wifi的接受流量,即只统计数据网Gprs接受的流量;
TrafficStats.getMobileTxBytes()——获取从此次开机起不包括Wifi的发送流量,即只统计数据网Gprs发送的流量;
//进程流量统计:
TrafficStats.getUidRxBytes(mUid)//上行流量
TrafficStats.getUidTxBytes(mUid)//下行流量

布局绘制

绘制时长

View在使用之前需要进行Infalte操作,此操作在主线程执行且耗时严重,通常是造成卡顿的直接原因

//hook的函数:
	hook android.view.LayoutInflater.inflate		:inflate
	hook android.app.Activity.setContentView		:setContentView

从setContentView到inflate结束,这段时间用时可看作绘制时长,一般超过30ms则认为构建超时。

绘制深度

https://github.com/Tencent/GT/blob/master/android/GT-布局检测原理及规则.md

GT神器威力加强版

GT提供的能力

GT这部分可以通过下载其APK来使用,其提供的sdk只提供了数据收集能力,数据处理在GT APP端,可以进行整合、扩展,这是下一部分的内容了。
各主要数据获取途径上面已经描述,除了上述的重要数据还有其他很多比如

  1. Thread.getStackTrace() 获取各线程的栈信息采集
  2. Hook android.database.sqlite.SQLiteDatabase的关键函数来监控SQLite数据库的操作
  3. 通过cmd命令记录log信息
    等等各方面的采集。

我们通过GT能获得的性能数据如下:

CPU指标、内存指标、流量指标
Android性能相关--性能指标篇_第2张图片

流畅度 – 帧率丢失、Activity/Fragment的冷热启动时长
Android性能相关--性能指标篇_第3张图片

Android性能相关--性能指标篇_第4张图片
Android性能相关--性能指标篇_第5张图片

流畅度 – 压力测试
Monkey 和 Monkey Demons
电量指标
GT有【耗电数据采集】插件,但只支持个别机型。

扩展

我们将GT SDK集成进来,并对其进行扩展,提供更多数据的收集功能,比如内存泄漏相关、自定义函数数据观测等。再将GT APP的数据整理 展示部分集成过来,最终将这些结果用同样的方式展示在网页上,形成一个完整的性能收集方案

扩展内容:(目前只有这两个)

  1. 借助LeakCanary等工具收集内存泄漏信息
  2. 提供注解@observeMethod,可以观测函数运行时间、CPU变化、内存变化、流量变化等

数据整理 展示部分整合
Android性能相关--性能指标篇_第6张图片
PS:左边App进程部分处于sdk中的内容,右边数据接收数据并整理部分处于GT APP中,将两部分进行整合优化,形成一套SDK。
最终将data.js放到GT_Report中查看数据

PS:
android平台局域网服务器搭建
https://github.com/koush/AndroidAsync
https://github.com/koush/AndroidAsync

微信APM开源项目Matrix
https://github.com/tencent/matrix#matrix_cn

你可能感兴趣的:(Android质量管理)