卡顿是什么
从表面现象来说,卡顿就是直观的感受反应变慢,画面停顿,触摸响应不及时,从深层次说,卡顿由一系列的原因造成,包括CPU、内存、磁盘 I/O都可能导致卡顿。
还记得上次帧率的公式吗?显卡的处理能力= 分辨率×帧率 如果处理能力不行,分辨率不变就会导致帧率变低,帧率变低就会导致卡顿现象。
卡顿排查工具
官方已经给我们提供了分析工具Traceview,它利用 Android Runtime 函数调用的 event 事件,将函数运行的耗时和调用关系写入 trace 文件中,它可以用来查看整个过程有哪些函数调用,但是工具本身带来的性能开销过大,无法反映真实的情况。在 Android 5.0 之后,新增了startMethodTracingSampling方法,可以使用基于样本的方式进行分析,以减少分析对运行时的性能影响。systrace Android 4.1 新增的性能分析工具,我们通常使用它跟踪系统的 I/O 操作、CPU 负载、Surface 渲染、GC 等事件,系统有很多地方都调用该工具的方法Trace.beginSection和Trace.endSection来做监控,如果想监控我们自己的应用也可以给应用的方法添加这个代码,方法可以通过插桩的方式,这样就实现了在 systrace 基础上增加应用程序耗时的监控,但想想,为什么Matrix可以自动应用程序的耗时分析呢?其实原理上和帧率监控相呼应,在Looper分发消息时,计算分发耗时,如果大于预定好的阀值,就开始收集信息上报,信息内容包括(进程信息、cpu信息、方法栈信息等)
造成卡顿的常见因素
- 界面绘制层级过高
- UI线程耗时操作
- GC频繁
这几个常见的因素,在我们日常开发中经常会关注的问题,也有了很好的解决办法,这里就不详细描述,接下来直接看Matrix的源码,看看它都做了哪些优化。
Matrix卡顿分析源码
直接看卡顿分析的Tracer类,我们还看onAlive方法,它是在plugin调用start方法时被调用的函数,可以理解为start函数,它将自己注册为观察者。观察的谁呢?
其实就是观察下面几个方法,等待的就是主线程的Looper消息回调。
那么现在可以看EvilMethodTracer的dispatchBegin函数,看它做了什么?
public void dispatchBegin(long beginNs, long cpuBeginMs, long token) {super.dispatchBegin(beginNs, cpuBeginMs, token);//插入方法结点,如果出现了方法执行过慢的问题,就从该结点开始收集方法执行记录indexRecord = AppMethodBeat.getInstance().maskIndex("EvilMethodTracer#dispatchBegin");}
再看doFrame函数,好像没什么用
@Overridepublic void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {queueTypeCosts[0] = inputCostNs;queueTypeCosts[1] = animationCostNs;queueTypeCosts[2] = traversalCostNs;}
再看dispatchEnd函数
@Overridepublic void dispatchEnd(long beginNs, long cpuBeginMs, long endNs, long cpuEndMs, long token, boolean isVsyncFrame) {super.dispatchEnd(beginNs, cpuBeginMs, endNs, cpuEndMs, token, isVsyncFrame);//判断是否是dev环境,如果是dev就使用当前时间,如果不是就返回0long start = config.isDevEnv() ? System.currentTimeMillis() : 0;//计算消息分发耗时long dispatchCost = (endNs - beginNs) / Constants.TIME_MILLIS_TO_NANO;try {//如果大于阀值,默认是700if (dispatchCost >= evilThresholdMs) {//取出该方法long[] data = AppMethodBeat.getInstance().copyData(indexRecord);long[] queueCosts = new long[3];//深拷贝数组,将doFrame中记录的信息,copy到queueCosts数组中System.arraycopy(queueTypeCosts, 0, queueCosts, 0, 3);//获取activity的nameString scene = AppMethodBeat.getVisibleScene();//添加任务,开始收集其他数据并上报MatrixHandlerThread.getDefaultHandler().post(new AnalyseTask(isForeground(), scene, data, queueCosts, cpuEndMs - cpuBeginMs, dispatchCost, endNs / Constants.TIME_MILLIS_TO_NANO));}} finally {indexRecord.release();if (config.isDevEnv()) {//如果dev环境,计算cpu使用率,并输出String usage = Utils.calculateCpuUsage(cpuEndMs - cpuBeginMs, dispatchCost);MatrixLog.v(TAG, "[dispatchEnd] token:%s cost:%sms cpu:%sms usage:%s innerCost:%s",token, dispatchCost, cpuEndMs - cpuBeginMs, usage, System.currentTimeMillis() - start);}}}
继续看AnalyseTask任务,直接看run方法调用的函数analyse
void analyse() {// 进程信息int[] processStat = Utils.getProcessPriority(Process.myPid());//继承cup使用String usage = Utils.calculateCpuUsage(cpuCost, cost);LinkedList<MethodItem> stack = new LinkedList();//方法栈数据收集if (data.length > 0) {//从插入结点开始收集并整理方法执行记录TraceDataUtils.structuredDataToStack(data, stack, true, endMs);TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() {@Overridepublic boolean isFilter(long during, int filterCount) {return during < filterCount * Constants.TIME_UPDATE_CYCLE_MS;}@Overridepublic int getFilterMaxCount() {return Constants.FILTER_STACK_MAX_COUNT;}@Overridepublic void fallback(List<MethodItem> stack, int size) {MatrixLog.w(TAG, "[fallback] size:%s targetSize:%s stack:%s", size, Constants.TARGET_EVIL_METHOD_STACK, stack);Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK));while (iterator.hasNext()) {iterator.next();iterator.remove();}}});}StringBuilder reportBuilder = new StringBuilder();StringBuilder logcatBuilder = new StringBuilder();long stackCost = Math.max(cost, TraceDataUtils.stackToString(stack, reportBuilder, logcatBuilder));String stackKey = TraceDataUtils.getTreeKey(stack, stackCost);MatrixLog.w(TAG, "%s", printEvil(scene, processStat, isForeground, logcatBuilder, stack.size(), stackKey, usage, queueCost[0], queueCost[1], queueCost[2], cost)); // for logcat//上报问题try {TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);if (null == plugin) {return;}JSONObject jsonObject = new JSONObject();jsonObject = DeviceUtil.getDeviceInfo(jsonObject, Matrix.with().getApplication());jsonObject.put(SharePluginInfo.ISSUE_STACK_TYPE, Constants.Type.NORMAL);jsonObject.put(SharePluginInfo.ISSUE_COST, stackCost);jsonObject.put(SharePluginInfo.ISSUE_CPU_USAGE, usage);jsonObject.put(SharePluginInfo.ISSUE_SCENE, scene);jsonObject.put(SharePluginInfo.ISSUE_TRACE_STACK, reportBuilder.toString());jsonObject.put(SharePluginInfo.ISSUE_STACK_KEY, stackKey);Issue issue = new Issue();issue.setTag(SharePluginInfo.TAG_PLUGIN_EVIL_METHOD);issue.setContent(jsonObject);plugin.onDetectIssue(issue);} catch (JSONException e) {MatrixLog.e(TAG, "[JSONException error: %s", e);}}
整体分析下来,经过帧率和启动耗时的分析后,卡顿现象的分析是不是就游刃有余,很容易就理解了呢。好了本次就先分析到这里。
