Android性能:通过Choreographer检测UI丢帧和卡顿
Android系统每隔16ms重绘UI界面,16ms是因为Android系统规定UI绘图的刷新频率60FPS。Android系统每隔16ms,发送一个系统级别信号VSYNC唤起重绘操作。1秒内绘制UI界面60次。每16ms为一个UI界面绘制周期。
平常所说的“丢帧”情况,并不是真的把绘图的帧给“丢失”了,也而是UI绘图的操作没有和系统16ms的绘图更新频率步调一致,开发者代码在绘图中绘制操作太多,导致操作的时间超过16ms,在Android系统需要在16ms时需要重绘的时刻由于UI线程被阻塞而绘制失败。如果丢的帧数量是一两帧,用户在视觉上没有明显感觉,但是如果超过3帧,用户就有视觉上的感知。丢帧数如果再持续增多,在视觉上就是所谓的“卡顿”。
丢帧是引起卡顿的重要原因。在Android中可以通过Choreographer检测Android系统的丢帧情况,以作为进一步分析卡顿的基础:
package zhangphil.test; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.Choreographer; import android.view.View; public class ANRActivity extends AppCompatActivity { private MyFrameCallback mFrameCallback = new MyFrameCallback(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Choreographer.getInstance().postFrameCallback(mFrameCallback); setContentView(R.layout.activity_anr); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { uiLongTimeWork(); } }); } public class MyFrameCallback implements Choreographer.FrameCallback { private String TAG = "性能检测"; private long lastTime = 0; @Override public void doFrame(long frameTimeNanos) { if (lastTime == 0) { //代码第一次初始化。不做检测统计。 lastTime = frameTimeNanos; } else { long times = (frameTimeNanos - lastTime) / 1000000; int frames = (int) (times / 16); if (times > 16) { Log.w(TAG, "UI线程超时(超过16ms):" + times + "ms" + " , 丢帧:" + frames); } lastTime = frameTimeNanos; } Choreographer.getInstance().postFrameCallback(mFrameCallback); } } private void uiLongTimeWork() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
Choreographer周期性的在UI重绘时候触发,在代码中记录上一次和下一次绘制的时间间隔,如果超过16ms,就意味着一次UI线程重绘的“丢帧”。丢帧的数量为间隔时间除以16,如果超过3,就开始有卡顿的感知。
代码运行输出:
07-20 11:23:40.082 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):17ms , 丢帧:1
07-20 11:23:40.099 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):17ms , 丢帧:1
07-20 11:23:40.145 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):28ms , 丢帧:1
07-20 11:23:40.165 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):20ms , 丢帧:1
07-20 11:23:40.190 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):24ms , 丢帧:1
07-20 11:23:40.208 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):17ms , 丢帧:1
07-20 11:23:40.224 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):17ms , 丢帧:1
07-20 11:23:40.257 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):33ms , 丢帧:2
07-20 11:23:40.306 5654-5654/zhangphil.test W/性能检测: UI线程超时(超过16ms):24ms , 丢帧:1
如果手动点击按钮故意阻塞1秒,丢弃的帧数更多。