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秒,丢棄的幀數更多。