Android的UI渲染性能优化

一、概述

对于app里的每一个view,android系统都会经过三部曲来渲染:measure,layout,draw。

可以在脑中回想下你搭建的view的xml布局文件结构,measure从最顶部的节点开始,顺着layout树形结构依次往下:测量每个view需要在屏幕当中展示的尺寸大小(上图当中:LinearLayout;RelativeLayout,LinearLayout;然后是textView0和LinearLayout Row1点分支,该分支又有另外3个子节点)。每个子节点都需要向自己的父节点提供自己的尺寸来决定展示的位置,遇到冲突的时候,父节点可以强制子节点重新measure(由此可能导致measure的时间消耗为原来的2-3倍)。这就是为什么扁平的view结构会性能更好。节点所处位置越深,套嵌带来的measure越多,计算就会越费时。我们来看一些具体的例子,看measure是怎么影响渲染性能的。

Remeasureing Views(重新测量views)

并不是只有发生错误的时候才会触发remeasure。RelativeLayouts经常需要measure所有子节点两次才能把子节点合理的布局。如果子节点设置了weights属性,LinearLayouts也需要measure这些节点两次,才能获得精确的展示尺寸。如果LinearLayouts或者RelativeLayouts被套嵌使用,measure所费时间可能会呈指数级增长(两个套嵌的views会有四次measure,三个套嵌的views会有8次的measure)。

一旦view开始被measure,该view所有的子view都会被重新layout,再把该view传递给它的父view,如此重复一直到最顶部的根view。layout完成之后,所有的view都被渲染到屏幕上。需要特别注意到是,并不是只有用户看得见的view才会被渲染,所有的view都会。后面我们会看下“屏幕重复绘制”的问题。app拥有的views越多,measure,layout,draw所花费的时间就越久。要缩短这个时间,关键是保持view的树形结构尽量扁平,而且要移除所有不需要渲染的view。移除这些view会对加速屏幕渲染产生明显的效果。理想情况下,总共的measure,layout,draw时间应该被很好的控制在16ms以内,以保证滑动屏幕时UI的流畅。

二、两个概念

merge翻译成中文是合并的意思,在Android中通过使用merge能够减少视图的节点数, 

从而减少视图在绘制过程消耗的时间,达到提高UI性能的效果。使用merge时通常需要注意以下几点:
  1. merge必须放在布局文件的根节点上。
  2. merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
  3. merge标签被添加到A容器下,那么merge下的所有视图将被添加到A容器下。
  4. 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。
  5. 如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点。
  6. 自定义View如果继承LinearLayout,建议让自定义View的布局文件根节点设置成merge,这样能少一层结点。
  7. 因为merge不是View,所以对merge标签设置的所有属性都是无效的。
ViewStub是一个轻量级的View,它一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化。

三、给开发的界面优化 Advice


优化布局的结构


        布局结构太复杂,会减慢渲染的速度,造成性能瓶颈。我们可以通过以下这些惯用、有效的布局原则来优化:


  • 避免复杂的View层级。布局越复杂就越臃肿,就越容易出现性能问题,寻找最节省资源的方式去展示嵌套的内容;


  • 尽量避免在视图层级的顶层使用相对布局 RelativeLayout 。相对布局 RelativeLayout 比较耗资源,因为一个相对布局 RelativeLayout 需要两次度量来确保自己处理了所有的布局关系,而且这个问题会伴随着视图层级中的相对布局 RelativeLayout 的增多,而变得更严重;


  • 布局层级一样的情况建议使用线性布局 LinearLayout 代替相对布局 RelativeLayout,因为线性布局 LinearLayout 性能要更高一些;确实需要对分支进行相对布局 RelativeLayout 的时候,可以考虑更优化的网格布局 GridLayout ,它已经预处理了分支视图的关系,可以避免两次度量的问题;


  • 相对复杂的布局建议采用相对布局 RelativeLayout ,相对布局 RelativeLayout 可以简单实现线性布局 LinearLayout 嵌套才能实现的布局;


  • 不要使用绝对布局 AbsoluteLayout ;


  • 将可重复使用的组件抽取出来并用 标签进行重用。如果应用多个地方的 UI 用到某个布局,就将其写成一个布局部件,便于各个 UI 重用。官方详解 「 戳我 」


  • 使用 merge 标签减少布局的嵌套层次,官方详解 「 戳我 」;


  • 去掉多余的不可见背景。有多层背景颜色的布局,只留最上层的对用户可见的颜色即可,其他用户不可见的底层颜色可以去掉,减少无效的绘制操作;


  • 尽量避免使用 layoutweight 属性。使用包含 layoutweight 属性的线性布局 LinearLayout 每一个子组件都需要被测量两次,会消耗过多的系统资源。在使用 ListView 标签与 GridView 标签的时候,这个问题显的尤其重要,因为子组件会重复被创建。平分布局可以使用相对布局 RelativeLayout 里一个 0dp 的 view 做分割线来搞定,如果不行,那就……;


  • 合理的界面的布局结构应是宽而浅,而不是窄而深;


优化处理逻辑


  • 按需载入视图。某些不怎么重用的耗资源视图,可以等到需要的时候再加载,提高UI渲染速度;


  • 使用 ViewStub 标签来加载一些不常用的布局;


  • 动态地 inflation view 性能要比用 ViewStub 标签的 setVisiblity 性能要好,当然某些功能的实现采用 ViewStub 标签更合适;


  • 尽量避免不必要的耗资源操作,节省宝贵的运算时间;


  • 避免在 UI 线程进行繁重的操作。耗资源的操作(比如 IO 操作、网络操作、SQL 操作、列表刷新等)耗资源的操作应用后台进程去实现,不能占用 UI 线程,UI 线程是主线程,主线程是保持程序流畅的关键,应该只操作那些核心的 UI 操作,比如处理视图的属性和绘制;


  • 最小化唤醒机制。我们常用广播来接收那些期望响应的消息和事件,但过多的响应超过本身需求的话,会消耗多余的 Android 设备性能和资源。所以应该最小化唤醒机制,当应用不关心这些消失和事件时,就关闭广播,并慎重选择那些要响应的 Intent 。


  • 为低端设备考虑,比如 512M 内存、双核 CPU 、低分辨率,确保你的应用可以满足不同水平的设备。


  • 优化应用的启动速度。当应用启动一个应用时,界面的尽快反馈显示可以给用户一个良好的体验。为了启动更快,可以延迟加载一些 UI 以及避免在应用 Application 层级初始化代码。


猜你喜欢

转载自blog.csdn.net/shushucn2012/article/details/79485705