【Android 性能优化】布局渲染优化 ( CPU 渲染优化 | 减少布局的嵌套 | 测量布局绘制时间 | OnFrameMetricsAvailableListener | 布局渲染优化总结 )

2023-03-27 21:45:11 浏览数 (1)

文章目录

  • 一、 减少布局嵌套
  • 二、 布局渲染时间测量
    • 1. FrameMetrics 使用流程
    • 2. FrameMetrics 参数解析
    • 3. FrameMetrics 代码示例
  • 三、 布局渲染优化总结

一、 减少布局嵌套


在 【Android 性能优化】布局渲染优化 ( GPU 过度绘制优化总结 | CPU 渲染过程 | Layout Inspector 工具 | View Tree 分析 | 布局组件层级分析 ) 博客中引入了 CPU 渲染优化 , CPU 渲染优化的核心就是减少布局嵌套 , 布局嵌套使用 Android Studio 中的 Layout Inspector 工具进行查看 ;

CPU 渲染的优化的核心就是减少布局的嵌套 , 推荐使用约束布局进行开发 , 只有一层嵌套的布局 ;

减少布局的嵌套 , 能极大减少 UI 组件测量 , 摆放 , 生成 UI 组件的时间 , 这样就可以减少 CPU 渲染时间 , 使整个渲染过程时间降低 , 尽可能的压缩在 16ms 以内 , 保证 Vsync 信号到来时 , 渲染已经完毕 , 可以在屏幕中绘制这些布局 ;

能够被优化的布局 : 假如父布局中只有一个子布局 , 子布局中有若干组件 , 那么可以直接将子布局的组件放在父布局中 , 将子布局这个层级干掉 , 或者将父布局层级删除 ;

一个父布局没有分支的布局 , 只有一个子布局 , 那么大概率可以优化删除父布局或子布局中的一个 , 两者保留一个 ;

强烈推荐使用 ConstraintLayout 约束布局 , 没有以上布局嵌套问题 ;

二、 布局渲染时间测量


如果使用 可以直接在该工具中查看布局渲染时间 , 但是该工具停止维护 , 使用老版本的 Android Studio 可以使用该工具 ;

Google 官方推荐使用 OnFrameMetricsAvailableListener 测量布局渲染时间 ;

1. FrameMetrics 使用流程

FrameMetrics 使用流程 :

① 创建测量线程 : 测量的过程肯定是要放在线程中执行 , 这里创建 HandlerThread 线程 ; 该线程创建后直接启动即可 ;

代码语言:javascript复制
HandlerThread handlerThread = new HandlerThread("FrameMetrics");
handlerThread.start();

② 创建 Handler : 测量时 , 需要将 Handler 对象传递到 addOnFrameMetricsAvailableListener 方法中 , 与 Window.OnFrameMetricsAvailableListener 监听器一起设置 ;

代码语言:javascript复制
Handler handler = new Handler(handlerThread.getLooper());

③ 注册监听器 : 向 Activity 界面的 Window 窗口对象 , 添加监听器 , 同时设置 Handler , 渲染测量过程在该 Handler 所在线程执行 ;

代码语言:javascript复制
getWindow().addOnFrameMetricsAvailableListener(
                    new Window.OnFrameMetricsAvailableListener(){
	// 省略实现内容 ... 
}, handler);

④ 回调方法中获取渲染时间 : FrameMetrics frameMetrics 参数中封装了

12

个渲染性能参数属性 , 可以自行

2. FrameMetrics 参数解析

FrameMetrics 参数解析 :

FrameMetrics 参数常量

代表的含义

ANIMATION_DURATION

动画执行回调时间, 单位纳秒

COMMAND_ISSUE_DURATION

向 GPU 发送绘制命令花费的时间, 单位纳秒

DRAW_DURATION

将组件树 ( View Hierarchy ) 转为显示列表 ( DisplayLists ) , 计算过程所花费的时间, 单位纳秒

FIRST_DRAW_FRAME

绘制的该帧是否是第一帧, 0 是, 1 不是 ; 第一帧渲染会慢一些 , 第一帧不会引发动画中的跳帧问题, 这些问题都会被窗口动画隐藏 , 不必进行显示过程中的 jank 计算

INPUT_HANDLING_DURATION

处理输入事件花费的时间, 单位纳秒

INTENDED_VSYNC_TIMESTAMP

该值是个时间戳, 表示该帧的 vsync 信号发出时间 ; 这个时间是当前帧的预期开始时间 , 如果该时间与 VSYNC_TIMESTAMP 时间戳不同 , 那么说明 UI 线程被阻塞了, 没有及时响应 vsync 信号

LAYOUT_MEASURE_DURATION

组件树 ( view hierarchy ) 测量 ( measure ) 和摆放 ( layout ) 花费的时间 , 单位纳秒

SWAP_BUFFERS_DURATION

CPU 传递多维向量图形数据给 GPU 花费的时间, 单位纳秒

SYNC_DURATION

显示列表 ( DisplayLists ) 与显示线程同步花费的时间, 单位纳秒

TOTAL_DURATION

CPU 渲染到传递到 GPU 所用的总时间, 上述所花费的有意义的时间之和 , 单位纳秒

UNKNOWN_DELAY_DURATION

UI 线程响应并开始处理渲染的等待时间, 一般是 0, 如果大于 0 说明出问题了

VSYNC_TIMESTAMP

vsync 信号发出的时间戳, 该时刻 GPU 应该进行绘制, 间隔 16ms , 同时 CPU 开始渲染

3. FrameMetrics 代码示例

代码示例 :

代码语言:javascript复制
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 渲染性能测量
        renderingPerformanceMeasurement();
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    /**
     * 渲染性能测量
     */
    public void renderingPerformanceMeasurement(){
        HandlerThread handlerThread = new HandlerThread("FrameMetrics");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper());

        // 24 版本以后的 API 才能支持该选项
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // 整个渲染过程测量都在 HandlerThread 线程中执行
            getWindow().addOnFrameMetricsAvailableListener(
                    new Window.OnFrameMetricsAvailableListener(){

                        @Override
                        public void onFrameMetricsAvailable(
                                Window window,
                                FrameMetrics frameMetrics,
                                int dropCountSinceLastInvocation) {

                            // 帧渲染测量完毕后, 会回调该方法
                            // 渲染性能参数都封装在 FrameMetrics frameMetrics 参数中

                            // 先拷贝一份, 之后从拷贝的数据中获取对应渲染时间参数
                            FrameMetrics fm = new FrameMetrics(frameMetrics);

                            // 1. 动画执行回调时间, 单位纳秒
                            Log.i("FrameMetrics", "ANIMATION_DURATION : "  
                                    fm.getMetric(FrameMetrics.ANIMATION_DURATION));

                            // 2. 向 GPU 发送绘制命令花费的时间, 单位纳秒
                            Log.i("FrameMetrics", "COMMAND_ISSUE_DURATION : "  
                                    fm.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION));

                            // 3. 将组件树 ( View Hierarchy ) 转为显示列表 ( DisplayLists )
                            // 计算过程所花费的时间, 单位纳秒
                            Log.i("FrameMetrics", "DRAW_DURATION : "  
                                    fm.getMetric(FrameMetrics.DRAW_DURATION));

                            // 4. 绘制的该帧是否是第一帧, 0 是, 1 不是
                            // 第一帧渲染会慢一些
                            // 第一帧不会引发动画中的跳帧问题, 这些问题都会被窗口动画隐藏
                            // 不必进行显示过程中的 jank 计算
                            Log.i("FrameMetrics", "FIRST_DRAW_FRAME : "  
                                    fm.getMetric(FrameMetrics.FIRST_DRAW_FRAME));

                            // 5. 处理输入事件花费的时间, 单位纳秒
                            Log.i("FrameMetrics", "INPUT_HANDLING_DURATION : "  
                                    fm.getMetric(FrameMetrics.INPUT_HANDLING_DURATION));

                            // 6. 该值是个时间戳, 表示该帧的 vsync 信号发出时间
                            // 这个时间是当前帧的预期开始时间
                            // 如果该时间与 VSYNC_TIMESTAMP 时间戳不同
                            // 那么说明 UI 线程被阻塞了, 没有及时响应 vsync 信号
                            Log.i("FrameMetrics", "INTENDED_VSYNC_TIMESTAMP : "  
                                    fm.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP));

                            // 7. 组件树 ( view hierarchy ) 测量 ( measure ) 和摆放 ( layout ) 花费的时间
                            // 单位 纳秒
                            Log.i("FrameMetrics", "LAYOUT_MEASURE_DURATION : "  
                                    fm.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION));

                            // 8. CPU 传递多维向量图形数据给 GPU 花费的时间, 单位纳秒
                            Log.i("FrameMetrics", "SWAP_BUFFERS_DURATION : "  
                                    fm.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION));

                            // 9. 显示列表 ( DisplayLists ) 与显示线程同步花费的时间, 单位纳秒
                            Log.i("FrameMetrics", "SYNC_DURATION : "  
                                    fm.getMetric(FrameMetrics.SYNC_DURATION));

                            // 10. CPU 渲染到传递到 GPU 所用的总时间, 上述所花费的有意义的时间之和
                            // 单位纳秒
                            Log.i("FrameMetrics", "TOTAL_DURATION : "  
                                    fm.getMetric(FrameMetrics.TOTAL_DURATION));

                            // 11. UI 线程响应并开始处理渲染的等待时间, 一般是 0, 如果大于 0 说明出问题了
                            Log.i("FrameMetrics", "UNKNOWN_DELAY_DURATION : "  
                                    fm.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION));

                            // 12. vsync 信号发出的时间戳, 该时刻 GPU 应该进行绘制, 间隔 16ms
                            // 同时 CPU 开始渲染
                            Log.i("FrameMetrics", "VSYNC_TIMESTAMP : "  
                                    fm.getMetric(FrameMetrics.VSYNC_TIMESTAMP));
                        }
                    }, handler);
        }
    }
}

打印结果 :

代码语言:javascript复制
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: ANIMATION_DURATION : 3854
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: COMMAND_ISSUE_DURATION : 101644281
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: DRAW_DURATION : 9981616
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: FIRST_DRAW_FRAME : 1
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: INPUT_HANDLING_DURATION : 50625
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: INTENDED_VSYNC_TIMESTAMP : 164845526994092
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: LAYOUT_MEASURE_DURATION : 154012359
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: SWAP_BUFFERS_DURATION : 1569062
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: SYNC_DURATION : 405729
2020-06-25 13:19:05.340 14179-14229/kim.hsl.rtmp I/FrameMetrics: TOTAL_DURATION : 268872818
2020-06-25 13:19:05.340 14179-14229/kim.hsl.rtmp I/FrameMetrics: UNKNOWN_DELAY_DURATION : 639354
2020-06-25 13:19:05.340 14179-14229/kim.hsl.rtmp I/FrameMetrics: VSYNC_TIMESTAMP : 164845526994092
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: ANIMATION_DURATION : 281042
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: COMMAND_ISSUE_DURATION : 1590885
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: DRAW_DURATION : 520157
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: FIRST_DRAW_FRAME : 1
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: INPUT_HANDLING_DURATION : 38646
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: INTENDED_VSYNC_TIMESTAMP : 164845543729270
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: LAYOUT_MEASURE_DURATION : 6619844
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: SWAP_BUFFERS_DURATION : 908230
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: SYNC_DURATION : 51302
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: TOTAL_DURATION : 284281446
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: UNKNOWN_DELAY_DURATION : 274133736
2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: VSYNC_TIMESTAMP : 164845810395926
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: ANIMATION_DURATION : 206980
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: COMMAND_ISSUE_DURATION : 2708386
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: DRAW_DURATION : 86823
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: FIRST_DRAW_FRAME : 0
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: INPUT_HANDLING_DURATION : 51302
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: INTENDED_VSYNC_TIMESTAMP : 164845828222860
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: LAYOUT_MEASURE_DURATION : 685260
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: SWAP_BUFFERS_DURATION : 859895
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: SYNC_DURATION : 537292
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: TOTAL_DURATION : 5902960
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: UNKNOWN_DELAY_DURATION : 545772
2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: VSYNC_TIMESTAMP : 164845828222860

三、 布局渲染优化总结

1 . 背景问题 : 主题的背景 , 布局的背景 , 组件的背景 , 都需要特别主题 , 每次添加背景 , 都会增加一次绘制 ;

2 . 布局嵌套 : 推荐使用约束布局 ; 如果某个容器布局只有一个子容器 , 那么可以删除一层嵌套 ;

3 . merger 使用 : 如果是 FrameLayout 布局 , 使用 merger 可以减少一层嵌套 ;

4 . 布局包含 : 布局文件中尽量使用 include 包含其它布局 , 如标题栏 Toolbar 组件 , 这样 GPU 中缓存一次之后 , 之后的界面再加载该 Toolbar 组件时 , 直接复用 GPU 中缓存的纹理数据 ;

5 . 自定义组件裁剪 : 在 Canvas 绘制重叠时 , 使用裁剪后的画布绘制 ;

0 人点赞