Android系统卡顿数据收集

系统卡顿其实是一个比较宽泛的概念,平时的感觉像是一个很主观的指标,但是从技术层面去看,倒是可以被量化。

# 两个层面

从人的感知角度去看卡顿的问题,有两个可以被分割的层面,其一是响应卡顿,其二是界面卡顿。

响应卡顿

响应卡顿在电视端的Android平台上是很好理解的,具体地说就是从用户按下遥控器键值到用户看到画面开始改变这个过程的时间差,如果是移动端触控设备,可以理解成从用户滑动屏幕到看到画面开始滑动这个过程的时间差。当这个时间差大于人感知的心理阈值时,则可认为是响应卡顿。当然这个心理阈值是需要通过大量的用户体验加上科学的统计分析才能得出的。

界面卡顿

界面卡顿其实就是我们大部分时间所认为的“卡”,从技术角度上看,就是从一帧画面切换到另一帧画面时产生的时间差。当这个时间差大于人感知的物理+心理的组合阈值时,则可认为是界面卡顿。

这里只先对界面卡顿做记录。

# 界面卡顿原理

Android显示系统通过一个类似软中断的方式来有序地绘制画面,也就是我们所说VSYNC机制。一般情况下,Android系统会保证60HZ的刷新率,换算后是16ms刷新一次画面,系统使用时钟中断来保证时间间隔的精确性,保证每隔16ms即发出一个VSYNC信号。如图所示,当在16ms内不能处理完成当前帧时,会错过一个VSYNC,也就造成了丢帧。

Android4.0+ VSYNC 机制示意

在画面绘制时,CPU负责解析Layout,draw等操作,GPU负责栅格化元素并将画面更新到屏幕。根据前端的布局或者实现方式的不同,CPU和GPU都会出现不能在16ms内处理完当前帧的现象。当布局复杂到一定程度,嵌套层级多的情况下,会导致CPU瓶颈的丢帧,当界面存在overdraw严重的情况时,会导致GPU瓶颈的丢帧

# 界面卡顿检测和数据收集

Android4.0以后,加入了Choreographer机制,Choreographer接收显示系统VSYNC信号,在下一帧渲染时有可被应用调用的回调给出。

1
2
3
4
5
// Called when a new display frame is being rendered.
public interface FrameCallback
{
public void doFrame(long frameTimeNanos);
}

具体的模块实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class FrameSurveyor implements Choreographer.FrameCallback{
protected static final String TAG = "FrameSurveyor";
private static final long ERRORFRAMETAKETIME = 16666666*1;
private long mlastFrameTimeNanos = 0;
private static FrameSurveyor mframeSurveyor;
public static FrameSurveyor getInstance(){
if(mframeSurveyor == null){
mframeSurveyor = new FrameSurveyor();
}
return mframeSurveyor;
}
public void startSurveyor(){
Log.d(TAG, "Surveyor Start");
Choreographer.getInstance().postFrameCallback(FrameSurveyor.getInstance());
}
public void reportSurveyor(){
Log.d(TAG, "Surveyor Report");
Choreographer.getInstance().removeFrameCallback(this);
}
@Override
public void doFrame(long frameTimeNanos) {
long takeTime = 0;
if (mlastFrameTimeNanos != 0){
takeTime = frameTimeNanos - mlastFrameTimeNanos;
}
mlastFrameTimeNanos = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
if (takeTime > ERRORFRAMETAKETIME){
Log.d(TAG, "frame warning: " + String.valueOf(takeTime));
}
}
}