文章目录
一、 背景设置产生的过度绘制
二、 Android 系统的渲染优化
1. 透明组件数据传递
2. GPU 存储机制
3. Android 7.0 之后的优化机制
三、 自定义布局渲染优化
一、 背景设置产生的过度绘制
1. 背景设置产生的过度绘制 :
① 组件背景 : 每个组件每设置一次背景 , 该组件的区域就会增加一层绘制 , 如 LinearLayout 线性布局设置背景颜色 , TextView 设置背景颜色 , 都会增加该组件区域内的过渡绘制 ;
② 布局背景 : 布局文件总的背景 , 会增加一次 GPU 绘制 ;
③ 主题背景 : Activity 界面的主题背景 , 会增加一次 GPU 绘制 ;
2. 组件背景设置策略 : 不要随便为组件添加背景 , 添加一次背景 , 就增加一次 GPU 绘制 ;
不要随意给布局中的 UI 组件设置背景 , 能不设置背景的就不设置背景 , 如 ImageView 组件 , 设置一张图片 , 会增加一次绘制 , 如果再给该 ImageView 组件设置背景颜色 , 那么又会增加一次绘制 , 那么该 ImageView 组件肯定过渡绘制了 ;
二、 Android 系统的渲染优化
在 【Android 性能优化】布局渲染优化 ( CPU 与 GPU 架构分析 | 安卓布局显示流程 | 视觉与帧率分析 | 渲染超时卡顿分析 | 渲染过程与优化 ) 博客中分析了图像渲染的 16 毫秒过程中
CPU 渲染
CPU 传递数据到 GPU
GPU 渲染
是三大耗时操作 , 上述分析的背景过渡绘制 , 是从减少 GPU 渲染时间角度出发 , 降低图像渲染时间 ;
CPU 传递数据给 GPU 非常耗时 ;
下面分析是从 降低 CPU 传递数据到 GPU 时间 角度出发 , 进行的优化 , 这部分优化是由 Android 系统完成的 ;
1. 透明组件数据传递
Android 系统做了如下自动优化操作 , 当组件的背景是透明的 , 那么 CPU 将该组件转为多维向量图片 ( 多边形和纹理组成 ) 时发现该组件是透明的 , 该组件的图像信息就不会传递给 GPU 进行渲染 , 从而减少了 CPU 向 GPU 传递的数据大小 ; 之前讲到过 , CPU 向 GPU 传递数据也是一个非常耗时的操作 , 因此该优化 , 也降低了组件渲染的时间 ;
透明组件摆放处理 : CPU 不传递这些组件到 GPU 中 , 但是在布局中仍然正常摆放 ;
2. GPU 存储机制
1. GPU 存储纹理机制 : GPU 中的显存可以存储纹理资源 , 即多维向量图形资源 , 在渲染时 , 可以直接使用该存储的资源 , 不用每次都让 CPU 传递数据过来 ;
2. CPU 传递主题资源给 GPU 机制 : 传递主题资源是一次性传递 , 主题中的 背景 , 颜色 , 图片 ( Bitmap , Drawable ) 等资源都打包存储在了多维向量图形 ( 多边形 和 纹理 ) 中 , 传递给 GPU 进行渲染 , GPU 每次进行渲染时直接从存储区域取出这些资源 , 进行渲染 , 不再依赖 CPU 实时传递 ;
这种变化较少的资源 , 适合一次性加载 , 应用或界面的主题资源基本不会改变 ;
3. 普通的 UI 组件资源 : 如果是普通的 UI 组件 , 那么就不能只加载一次了 , 需要每次渲染时 , CPU 都要将组件加载到内存 , 并转成的多维向量图形 , 最后传递给 GPU ;
3. Android 7.0 之后的优化机制
Android 7.0 之后的优化机制 :
① 7.0 系统优化前 : Android 7.0 之前调用 UI 组件的 invalidate 方法 , 组件会回调 onLayout , onMeasure 和 onDraw 方法 ;
② 7.0 系统优化后 : Android 7.0 之后调用 UI 组件的 invalidate 方法 , 组件不会回调 onLayout 和 onMeasure 方法 , 只会调用 onDraw 方法 ;
③ 7.0 系统优化后工作机制 : 在 GPU 中缓存 UI 组件对应的多维向量图形 ( 纹理 ) , 当该组件位置或颜色等外观发生变化时 , 就会通知 CPU , 重新进行加载 , 如 onLayout 摆放 , onMeasure 测量 , 并转为多维向量图 ( 纹理 ) , 传递给 GPU 进行渲染 ; 如果没有发生变化 , 调用 invalidate 方法 , 只会在 GPU 中重新渲染 ; 不会重新 摆放 ( onLayout ) 与 测量 ( onMeasure ) ;
三、 自定义布局渲染优化
1. 自定义组件过度绘制问题描述 : 自定义控件 , 在自定义的 onDraw 方法中 , 绘制多张图片 , 如果图片之间产生重叠 , 重叠绘制的部分就出现了过度绘制 ;
2. 自定义组件绘制原则 :
① 两张图片 : 图片 A AA 和 图片 B BB ;
② 图片覆盖 : 当图片 A AA 被图片 B BB 覆盖时 , 只绘制图片 A AA 显示的部分区域 , 图片 A AA 被图片 B BB 覆盖的部分不再绘制 ;
③ 图片 A AA 只绘制没有被覆盖的部分 : 只在图片 A AA 显示的区域绘制图片 A AA 的区域 , 如下图黄色框中的区域 ;
3. 实现上述图片 A AA 在 Canvas 画布上绘制部分图片方式 :
① 完整画布 : onDraw 函数中的 Canvas canvas 参数是完整的画布 ;
② 取出图片 A AA 绘制部分的 Canvas 画布 : 这部分画布就是上图中 , 被黄色框框起来的画布 , 传入的四个参数是黄色矩形框的左上右下参数 , 注意剪切之前先保存画布 ;
// 剪切画布前 , 先保存画布 , 之后还要恢复回去 canvas.save(); // 剪切画布 canvas.clipRect(left, top, right, bottom);
③ 在剪切后的画布中绘制图片 A AA : 在剪切后的画布中 , 绘制图片 A AA , 注意绘制完成后 , 恢复画布 ;
// 在剪切后的画布中 , 绘制图片 A canvas.drawBitmap(...); // 绘制完毕后 , 恢复画布 canvas.restore();
④ 绘制效果 : 上述代码的绘制效果大概就是绘制了部分图片 A AA , 下图中的下面的部分图片 A AA 展示 ;
3. clipRect 函数原型 : 剪切画布 , 获取 Canvas 完整画布的子画布 , 传入左 , 上 , 右 , 下 , 四个值 , 将画布剪切出来 ;
public boolean clipRect(float left, float top, float right, float bottom) { return nClipRect(mNativeCanvasWrapper, left, top, right, bottom, Region.Op.INTERSECT.nativeInt); }