文章目录
- 一、 减少布局嵌套
- 二、 布局渲染时间测量
- 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 参数中封装了
个渲染性能参数属性 , 可以自行
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 绘制重叠时 , 使用裁剪后的画布绘制 ;