卡顿是什么
从表面现象来说,卡顿就是直观的感受反应变慢,画面停顿,触摸响应不及时,从深层次说,卡顿由一系列的原因造成,包括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函数,好像没什么用
@Override
public 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函数
@Override
public void dispatchEnd(long beginNs, long cpuBeginMs, long endNs, long cpuEndMs, long token, boolean isVsyncFrame) {
super.dispatchEnd(beginNs, cpuBeginMs, endNs, cpuEndMs, token, isVsyncFrame);
//判断是否是dev环境,如果是dev就使用当前时间,如果不是就返回0
long start = config.isDevEnv() ? System.currentTimeMillis() : 0;
//计算消息分发耗时
long dispatchCost = (endNs - beginNs) / Constants.TIME_MILLIS_TO_NANO;
try {
//如果大于阀值,默认是700
if (dispatchCost >= evilThresholdMs) {
//取出该方法
long[] data = AppMethodBeat.getInstance().copyData(indexRecord);
long[] queueCosts = new long[3];
//深拷贝数组,将doFrame中记录的信息,copy到queueCosts数组中
System.arraycopy(queueTypeCosts, 0, queueCosts, 0, 3);
//获取activity的name
String 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() {
@Override
public boolean isFilter(long during, int filterCount) {
return during < filterCount * Constants.TIME_UPDATE_CYCLE_MS;
}
@Override
public int getFilterMaxCount() {
return Constants.FILTER_STACK_MAX_COUNT;
}
@Override
public 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);
}
}
整体分析下来,经过帧率和启动耗时的分析后,卡顿现象的分析是不是就游刃有余,很容易就理解了呢。好了本次就先分析到这里。