天天看點

Android性能:通過Choreographer檢測UI丢幀和卡頓

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

繼續閱讀