Android流畅度分析

背景

最近一个项目用的低性能android开发板开发,经常会在logcat中看到以下log,故查了一下,在此记录。

I/Choreographer: Skipped 60 frames!  The application may be doing too much work on its main thread.

分析

首先说明一下android渲染机制。

Android系统每隔16ms就发出VSYNC (Vertical Synchronization垂直同步)信号,触发渲染,重新绘制一次Activity,也就是说,我们的应用必须在16ms内完成屏幕刷新的全部逻辑操作,即每一帧只能停留16ms。

为什么是16ms呢? 16ms相当于60fps。这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。12fps大概类似手动快速翻动书籍的帧率, 这明显是可以感知到不够顺滑的。24fps使得人眼感知的是连续线性的运动,这其实是归功于运动模糊的效果。 24fps是电影胶圈通常使用的帧率,因为这个帧率已经足够支撑大部分电影画面需要表达的内容,同时能够最大的减少费用支出。 但是低于30fps是 无法顺畅表现绚丽的画面内容的,此时就需要用到60fps来达到想要的效果,超过60fps就没有必要了。如果我们的应用没有在16ms内完成屏幕刷新的全部逻辑操作,页面就会发生卡顿。

渲染操作取决于两个核心组件:CPU和GPU。CPU负责包括Measure,Layout,Record,Execute的计算操作,GPU 负责Rasterization(栅格化)操作。当他俩因为各种原因处理时间大于16ms时,用户就会感知到页面卡顿。

grid

上图为栅格化操作,就是将View拆分为不同的像素点以显示到屏幕上。也就是说要将xml转化为用户能看到的图像,首先要通过CPU转化为多边形和纹理,然后传递给GPU进行栅格化。栅格化和OpenGL有关,且是一个十分耗时的操作。所以这16ms主要被两件事所占用:1.xml转为多边形和纹理。2.CPU上传数据给GPU以进行栅格化。

cgpu

要想缩短这两件事的耗时,就需要从CPU和GPU两方面考虑,即减少CPU转化的次数和上传给GPU的次数。

CPU:减少不必要的布局

GPU:避免过度绘制

优化的具体方式不再赘述。

Choreographer

(英[ˌkɒrɪ’ɒɡrəfə(r)] 美[ˌkɒrɪ’ɒɡrəfə(r)])

该log出自Choreographer的doFrame方法

private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
            "debug.choreographer.skipwarning", 30);
//绘制一帧
void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            //省略部分代码
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                            + "which is more than the frame interval of "
                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                            + "Skipping " + skippedFrames + " frames and setting frame "
                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                }
                frameTimeNanos = startNanos - lastFrameOffset;
            }
        }
        //省略部分代码
    }

SKIPPED_FRAME_WARNING_LIMIT默认30帧,所以当丢失超过30帧则会打印此log。

那么利用反射将此变量改为1,则可以实现只要一丢帧就打印出log。

也可实现Choreographer.FrameCallback接口实现更多功能。(两次绘制时间间隔等)

大体流程

vsync

主要参考: https://www.jianshu.com/p/d126640eccb1