学习的过程中善于总结才能快速提升个人的水平, 特别作为程序员. 项目优化也是很重要的一部分, 最近寻思着写一篇文章总结, 不论是对以后的开发或者提升自身的水平都有用. 在写这篇文章之前, 结合自身项目经验网上多篇博客, 这里做个总结, 特别一些细节的原理, 我也会在文末附上相关的链接, 虽然有时候不需要了解原理. 关于 UI 方面的优化不能光看一遍就完事了, 主要还是开始时主动去培养这样的习惯, 本篇的顺序主要是依照 UI 优化重要等级写的.
过度绘制
大家应该都了解过, 简单说一下, 接下来主要讲解我们实际开发中怎么去避免这种现象. Overdraw(过度绘制)是指屏幕上的某个像素在同一帧的时间内被绘制了多次.
如果当前区域被绘制两次, 就是过度绘制一次, 以此类推, 每绘制一次都会消耗性能 CPU,GPU, 还有电量等, 所以作为开发就是尽量减少同一区域绘制次数. 过度绘制主要成因如下:
1, 由于布局复杂造成嵌套布局 2, 布局中的 view 设置多层背景颜色
布局嵌套
使用 merge 标签
使用 RelativeLayout 和 ConstraintLayout(加强版 RelativeLayout)
自定义 Viewgroup, 重写 layout 方法
多层背景
我这里会分为大概三种情况:
单独一个 view, 如果设置了一个前景图片或者颜色, 一定不要设置背景了, 这种情况大部分出现在 imageview 中
对于 viewgroup, 如果不是特别需要, 只设置最底层 view 的背景色
所有的 Activity 都有默认的一层背景, 默认主题颜色, 这也是冷启动出现黑白屏根源, 如果是 launcher 类型 Activity, 可以设置一张图片或者颜色的 drawable 或者 xml(建议不要这样设置, xml 解析会耗时), 并且 launcher 类型 Activity 启动之后要情况背景, 除去内存占用. launcher 类:
- <style name="AppTheme.Launcher">
- <item name="android:windowBackground">@drawable/launch_screens</item>
- </style>
或者如下(不建议):
- <style name="SplashTheme" parent="@style/Theme.AppCompat.Light.NoActionBar">
- <item name="android:windowDisablePreview">true</item>
- </style>
设置加载之后在 oncreat()方法中再设置如下除去内存占用
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- // 将 Windows 的背景图设置为空
- getWindow().setBackgroundDrawable(null);
- super.onCreate(savedInstanceState);
- }
如果是普通 Activity, 则可以:
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- getWindow().setBackgroundDrawable(null);
- }
参考链接: juejin.im/post/5bf8f9...
## 常用布局性能比较
通过上图总结几点:
相同层级效果, 优先级 FrameLayout>=LinearLayout>RelativeLayout>ConstraintLayout
对于负责 UI, 首先优化考虑层级嵌套问题, 优先使用 ConstraintLayout 和 RelativeLayout
使用 LinearLayout 时, 慎用 weight 属性和 measureWithLargestChild 属性, 会引起 2 次渲染
尽量减少 ConstraintLayout 和 RelativeLayout 中太多无效依赖, 可以减少不必要控件的刷新
自定义 view 优化
- (还有需要总结的地方)
- onDraw
自定义 View 里面最重要, 也是优化的最重要部分.
ondraw 方法里面尽量不要去初始化对象, 初始化过程放在构造方法里, 在 ondraw 方法里面初始变量, 如果需要 invalidate(), 则会一直调用 ondraw(), 会导致内存一直创建和回收, 造成内存抖动.
不要频繁的设置可见于不可见方法, 这样也会调用, 尽量在 xml 设置的就是默认的状态, 减少 ondraw 方法调用.
尽量在 View 的内容发生改变的时候才去触发 invalidate 方法
尽量使用 ClipRect 等方法来提高绘制的性能(重叠的部分不去绘制).
减少不必要元素的绘制
不在屏幕的元素尽量使用 Canvas.quickReject 把他们给剔除
onLayout
任何时刻对 View 调用 requestLayout()方法, 都需要遍历整个 View 树, 确定每个视图它们所占用的大小. 如果在 measure 过程中有任何冲突, 可能会多次遍历. 如果 UI 设计师给的效果过于复杂, 就需要自定义 onlayout 方法, 并且可以减少嵌套问题, 优化 measure 过程.
clipRect()
该方法用于裁剪画布, 调用 clipRect()方法后, 只会显示被裁剪的区域, 之外的区域将不会显示. 该方法最后有一个参数 Region.Op, 表示与之前区域的区域间运算种类, 如果没有这个参数, 则默认为 Region.Op.INTERSECT 这几个参数的意义为:
DIFFERENCE 是第一次不同于第二次的部分显示出来
REPLACE 是显示第二次的
REVERSE_DIFFERENCE 是第二次不同于第一次的部分显示
INTERSECT 交集显示
UNION 全部显示
XOR 补集 就是全集的减去交集生育部分显示
clipxx 方法只对设置以后的 drawxx 起作用, 已经画出来的图形, 是不会有作用的.
特殊标签
include
include 标签常用于将布局中的公共部分提取出来, 比如页面所有的 actionbar 那么就可以直接 include 进去了. 注意事项:
外层可以设置宽高, 外层优于内层
外层可以设置 id, 外层 id 优先级高于内层 id
如果一个布局中引用两个 id 相同的 include, 则第一个有效, 第二个无效, 但是两者都会显示.
merge:
merge 标签是作为 include 标签的一种辅助扩展来使用, 它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套. Android 渲染需要消耗时间, 布局越复杂, 性能就越差. 如上述 include 标签引入了之前的 LinearLayout 之后导致了界面多了一个层级. 注意事项:
merge 必须放在布局文件的根节点上.
merge 并不是一个 ViewGroup, 也不是一个 View, 它相当于声明了一些视图, 等待被添加.
merge 标签被添加到 A 容器下, 那么 merge 下的所有视图将被添加到 A 容器下.
因为 merge 标签并不是 View, 所以在通过 LayoutInflate.inflate 方法渲染的时候, 第二个参数必须指定一个父容器, 且第三个参数必须为 true, 也就是必须为 merge 下的视图指定一个父亲节点.
如果 Activity 的布局文件根节点是 FrameLayout, 可以替换为 merge 标签, 这样, 执行 setContentView 之后, 会减少一层 FrameLayout 节点.
自定义 View 如果继承 LinearLayout, 建议让自定义 View 的布局文件根节点设置成 merge, 这样能少一层结点.
因为 merge 不是 View, 所以对 merge 标签设置的所有属性都是无效的.
如果引用到外层的是 LinearLayout ,merge 内部方向跟随 LinearLayout 设置的方向
ViewStub:
ViewStub 直接继承自 View, 默认是不可见的, 没有 measure 过程, 只有加载的时候才会由加载的 xml 替换掉 (ViewStub 只能 inflate 一次, 再次进行 inflate 的时候会报异常) 或者设置为 Visibility 时才可见. 最大特点就是使用才加载. 这里简述一个常用的使用场景, 有时候页面没有数据, 会显示无数据页面或者网络异常页面, 这种情况就很好的利用了使用时才去加载.
注意事项:
ViewStub 只能被 Inflate 一次, inflate 之后 ViewStub 对象就会被置为空
ViewStub 只能用来 Inflate 一个布局文件, 而不是某个具体的 View(可以把 View 写在某个布局文件中)
Android:id--ViewStub 自身的 Id, 无论是否被 inflate, 都可以通过 findViewById 拿到对应的 ViewStub 控件本身.
Android:inflatedId--ViewStub 对用的 layout 里面根节点的 id,inflate 之后可以通过 findViewById 获取到对应的被映射的布局对象
viewStub.inflate()之后, 如果要显示或者隐藏布局跟普通 view 一样, 但是千万不要再一次. inflate()(会报异常).
在 xml 中定义 ViewStub 节点时, 内部不能包含其他节点, 也就是说, ViewStub 是一个自闭合节点, 如果一个布局 view 如果想通过 ViewStub 显示, 只能定义在单独的 xml 文件中.
ViewStub 于 Gone 的对比
设置为 GONE 的 View 不会占用布局空间, 但是会进行类的初始化; 如 ImageView 将 src 设置为一个 BitmapDrawable, 那么该图片将会加载到内中 ViewStub 只有在代码中进行 inflate 之后才会加载进来, 不会占用内存.
性能对比:
space
Space 经常用于组件之间的缝隙, 其 draw()为空, 减少了绘制渲染的过程. 组件之间的距离使用 Space 会提高了绘制效率, 特别是对于动态设置间距会很方便高效. 因为 draw()为空, 对该 view 没有做任务绘制渲染, 所以不能对 Space 设置背景色, 如果需要的间隔需要设置颜色是明显不合适的. Space 相对于 View 设置间距的好处是不用 draw, 缺点是不能设置背景色.
其他
如果初始化 View 不可见的时候, 使用 View.GONE 代替 View.VISIBLE, 设置 GONE 的 view 会加载的时候标记, 不会去 measure 过程.
减少 alpha 值对性能的影响 对于不透明的 View, 显示它只需要渲染一次即可, 可是如果这个 View 设置了 alpha 值, 会至少需要渲染两次. 原因是包含 alpha 的 view 需要事先知道混合 View 的下一层元素是什么, 然后再结合上层的 View 进行 Blend 混色处理, 并且对于设置.
TextView 设置文字和图片减少 View 的解析加载
使用 TextView 设置换行功能, 例如:
- <TextView
- Android:layout_width="match_parent"
- Android:layout_height="match_parent"
- Android:text="第一行 \ 第二行" />
对于确定宽高的 View 或者 ViewGroup, 尽量使用固定的宽高或者 match_parent, 例如: recyclerview 如果 Item 高度是固定的话, 可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源;
附言
UI 渲染
UI 卡顿优化
UI 优化工具 Lint
UI 优化工具 Hierarchy Viewer
https://www.jianshu.com/p/e9e05ce5b0c9 https://github.com/romainguy/ViewServer
来源: https://juejin.im/post/5c009519e51d453ec718a931