性能优化-卡顿优化

1,058 阅读3分钟

为什么会卡顿

1.界面绘制的层级过高

2.UI线程存在耗时操作

3.GC频繁导致线程频繁挂起

怎么检测

1、接入BlockCanary框架

框架的检测原理:利用主线程Looper日志打点检测

众所周知在应用启动时,在ActivityThread的main方法中会在主线程启动一个MainHandler

Handler源码每次执行MessageQueue之前会打印日志,执行完之后会打印日志。BlockCanary利用这个原理 Looper.getMainLooper().setMessageLogging(mainLooperPrinter); 并在mainLooperPrinter中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值(如2000毫秒,该阀值是可以自定义)为主线程卡慢发生,并dump出各种信息,提供开发者分析性能瓶颈。

`public static void loop() {
    ...
    for (;;) {
        ...
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        ...
    }
}`

框架架构图:

根据BlockCanary的原理,相信大家都能看出问题,其实这样检测卡顿的方式并不严谨。

BlockCanary是通过检测MainHandler处理每一个任务所耗费的时间,如果超过了阀价那么就提示卡顿。但是在根据Android的UI渲染原理,大家可以知道,屏幕是以60HZ的频率发出刷新信号,但是如果当当前界面没有发生任何的UI变动时,ViewRootImpl是收不到刷新的信号的。

那么在界面没有发生UI变化时(也就是不需要执行刷新界面的操作)主线程执行了耗时的操作而且超过了设定的时间阀值,那么这种情况其实并没有发生卡顿的现象,但是BlockCanary认为界面已经卡顿了。

那么要怎么解决BlockCanary检测不严谨问题?

在控制UI渲染流程中提供了一个监听屏幕刷新类Choreographer,在这个类中可以通过 Choreographer.getInstance().postFrameCallback(BlockDetectByChoregrapher.getInstance()); 在屏幕绘制每一帧之前会回调 那么通过回调的时间可以判断界面的掉帧情况。如下:

public class BlockDetectByChoregrapher implements Choreographer.FrameCallback {

    public static BlockDetectByChoregrapher sInstance;

    private String TAG = "BlockDetectByChoregrapher";

    public static final float deviceRefreshRateMs = 16.6f;

    public static long lastFrameTimeNanos = 0;//纳秒为单位

    public static long currentFrameTimeNanos = 0;

    public void start() {
        Choreographer.getInstance().postFrameCallback(BlockDetectByChoregrapher.getInstance());
    }

    public static BlockDetectByChoregrapher getInstance() {
        if (sInstance == null) {
            sInstance = new BlockDetectByChoregrapher();
        }
        return sInstance;
    }

    @Override
    public void doFrame(long frameTimeNanos) {
        if (lastFrameTimeNanos == 0) {
            lastFrameTimeNanos = frameTimeNanos;
            Choreographer.getInstance().postFrameCallback(this);
            return;
        }
        currentFrameTimeNanos = frameTimeNanos;
        float value = (currentFrameTimeNanos - lastFrameTimeNanos) / 1000000.0f;

        final int skipFrameCount = skipFrameCount(lastFrameTimeNanos, currentFrameTimeNanos, deviceRefreshRateMs);
        if (skipFrameCount > 1) {
            Log.e(TAG, "两次绘制时间间隔value=" + value
                    + "  frameTimeNanos=" + frameTimeNanos + "  currentFrameTimeNanos=" + currentFrameTimeNanos + "  skipFrameCount=" + skipFrameCount + "");
        }
        lastFrameTimeNanos = currentFrameTimeNanos;
        Choreographer.getInstance().postFrameCallback(this);
    }


    /**
     * 计算跳过多少帧
     *
     * @param start
     * @param end
     * @param devicefreshRate
     * @return
     */
    private int skipFrameCount(long start, long end, float devicefreshRate) {
        int count = 0;
        long diffNs = end - start;
        long diffMs = Math.round(diffNs / 1000000.0f);
        long dev = Math.round(devicefreshRate);
        if (diffMs > dev) {
            long skipCount = diffMs / dev;
            count = (int) skipCount;
        }
        return count;
    }
}

那么是不是不用BlockCanary了

利用Choreographer虽然可以更准确的判断出是否发生卡顿,但是不能够看出发生卡顿的地方。所以可以通过和BlockCanary配合使用,这样能够精准的找到卡顿发生的位置,而且主线程做过多的耗时的操作,不管是否造成卡顿,总归是不好的。