前语

关于卡顿这件事已经是老生常谈了,卡顿关于用户来说是灵敏的,简单被用户直接感受到的。那么究其原因,卡顿该怎么定义,关于卡顿的产生该怎么排查问题,当线上用户卡登时,在线下无法复现时,又怎么获取信息来定位问题?

什么是卡顿

一般来说, 12FPS (每秒显现帧数)为最低标准,低于 12fps 画面基本上就不是接连的,而当大于 60FPS 时,人眼很难区分出来显着的改变,所以 60FPS 是衡量一个界面流畅程度的重要目标。但 FPS 低并不意味着卡顿产生,而卡顿产生 FPS 必定不高,而是以一段时刻内的掉帧程度来衡量的。
假定按照 60FPS 来说, 一帧的时刻为 1000ms/60 = 16.6667ms,假如某次掉帧严峻,导致 1000ms 内 FPS 极低,那么就会造成画面卡住的现象。

卡顿排查东西

排查卡顿的东西有许多,大部分分为两种类型:
instrument:获取一段时刻内一切函数的调用进程,能够经过剖析这段时刻内的函数调用流程,再进一步剖析待优化的点。
sample:有选择性或许采用抽样的办法观察某些函数调用进程,能够经过这些有限的信息推测出流程中的可疑点,然后再持续细化剖析。

1. CPU Profiler

CPU Profiler 是 Android Studio 自带的东西,能够检查 CPU、内存、网络和电池资源的运用情况。它能够以图形的办法展现履行时刻、调用栈,而且信息全面包含一切线程,咱们能够经过 Debug 类的办法来检测区间的代码:

Debug.startMethodTracing("xxx");
...
Debug.stopMethodTracing();

终究会在 sd 卡的 Android/data/packagename/files 文件夹下生成一个 xxx.trace 文件。经过 AS 解析咱们能够看到这段时刻内函数的调用以及耗时,帧率情况,CPU 的情况以及其他线程的情况,咱们能够剖析到详细的方位来调整,例如:

Android-关于页面卡顿的排查工具与监测方案

可是东西自身会带来很大的功能开支,所以有时无法反映真实的情况。

2. Systrace

systrace 是 Android 4.1 新增的功能剖析东西, systrace 使用了 Linux 的ftrace调试东西,相当于在体系各个要害方位都添加了一些功能探针,也就是在代码里加了一些功能监控的埋点。Android 在 ftrace 的基础上封装了atrace,并增加了更多特有的探针,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。 systrace 东西只能监控特定体系调用的耗时情况,它是归于 sample 类型,而且功能开支十分低。可是它不支撑使用程序代码的耗时剖析,所以在运用时有一些局限性。
运用办法:
systrace.py -t 10 sched gfx view wm am app webview -a com.example.android_kt_wandroid

它也能够运用代码的办法插桩到办法前后进行剖析:

Trace.begainSection(name);
...
Trace.endSection(name);

运用 systrace 东西会生成一份 .systrace 文件,咱们能够在 Chrome 浏览器输入 chrome://tracing Load文件,它相同能够看 cpu,frame,thread 的一些情况:

Android-关于页面卡顿的排查工具与监测方案
经过右侧的 Alerts 能够提示一些掉帧的主张。
Android-关于页面卡顿的排查工具与监测方案

小结一下:无论运用哪种卡顿监控东西,最后咱们都能够得到卡登时的仓库和当时 CPU 运转的一些信息。大部分的卡顿问题都比较好定位,例如主线程履行一个耗时使命、读一个十分大的文件或许是履行网络请求等。

卡顿监测计划

有些时候咱们底子使不上上述几个东西,比如某用户反应 xxx 页面 xxx 时卡的一批,等我实际去按用户所述的步骤操作时,整个流程十分流畅,所以咱们是需求用户卡登时的一些信息,比如用户的体系版别、CPU 负载、网络环境、使用数据等,脱离这个现场,咱们本地难以复现,也就很难去处理问题。

1.音讯行列

这种办法依赖主线程 Looper,监控每次 dispatchMessage 的履行耗时,BlockCanary 就是这样完成的

public static void loop() {
    ...
    for (;;) {
        ...
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        ...
    }
}

主线程一切的使命都是在 dispatchMessage(msg) 办法中履行完成,那咱们经过自定义 Printer,在 dispatchMessage(msg) 前后分别记载时刻,而且在此之前经过另外一个线程经过 Thread#getStackTrace 接口,一向获取主线程履行仓库信息并记载起来,当 dispatchMessage(msg) 超过一个阈值时,咱们就告诉另一个线程获取仓库信息来做剖析。
详细完成,可供参考

2.Choreographer

这种办法依赖 Choreographer 模块,咱们知道, Android 体系每隔 16ms 发出 VSYNC 信号,来告诉界面进行重绘、渲染,每一次同步的周期为 16.6ms,代表一帧的改写频率。SDK 中包含了一个相关类,以及相关回调。理论上来说两次回调的时刻周期应该在 16ms,假如超过了 16ms 咱们则认为产生了卡顿,使用两次回调间的时刻周期来判别是否产生卡顿。
咱们能够在 Choreographer 模块注册一个 FrameCallback 监听目标,同时经过另外一条线程循环记载主线程仓库信息,并在每次 Vsync 事情 doFrame 告诉回来时,循环注册该监听目标,直接计算两次 Vsync 事情的时刻距离,当超出阈值时,取出记载的仓库进行剖析。
详细完成,可供参考

3.插桩

上述的两种办法都要单开一个线程记载办法仓库会占用体系资源,其次有时会出问题,因为正在运转的函数有可能并不是真实耗时的函数。
例如:假定一次音讯循环履行了三个函数,整个音讯履行为 3000ms,由于函数 A (1500ms) 和函数 B (1000ms) 已经履行结束,咱们得到的函数 C (500ms) 并不耗时,因而咱们需求拿到每个函数的耗时。

Android-关于页面卡顿的排查工具与监测方案

咱们能够经过自定义 transform 在编译期拿到一切的 class 文件(关于Gradle-Transform的文章),经过 ASM 东西在一切的办法前后进行插桩,在运转时检测到卡顿后取出办法信息。

//插桩前
void func(){
    dosomething();
}
//插桩后
void func(){
    Trace.i(x);
    dosomething();
    Trace.o(x);
}

可是也会有许多细节和优化的点:

  1. 防止办法数暴增。在函数的进口和出口应该插入相同的函数,在编译时提前给代码中每个办法分配一个独立的 ID 作为参数。
  2. 过滤简单的函数。过滤一些类似直接 return、i++ 这样的简单函数,而且支撑黑名单装备。对一些调用十分频频的函数,需求添加到黑名单中来下降整个计划对功能的损耗。
  3. 优化编译期的耗时。能够经过线程池以异步的办法插桩,结合 Future。
  4. 运转时存储办法信息的办法。将办法信息以 int 值保存,类似于 MeasureSpec 的设计。

详细完成Transform部分
详细完成打点代码部分

微信的开源项目 matrix 中 Trace Canary 模块已经处理上述问题,感兴趣的能够看下源码。

总结

到此咱们了解了几种卡顿监控的办法,已经能够处理咱们的部分问题,其实许多时候卡顿问题并不难处理,相较处理来说,更困难的是怎么快速发现这些卡顿点,以及经过更多的辅佐信息找到真实的卡顿原因。

参考链接:
Matrix Android TraceCanary
Android开发高手课 05 | 卡顿优化(上):你要掌握的卡顿剖析办法
Android开发高手课 06 | 卡顿优化(下):怎么监控使用卡顿?