布局的性能优化之所以重要,因为以下两个方面:
(1)布局文件是一个xml文件,inflate布局文件其实就是解析xml,根据标签信息创建相应的布局对象并做关联。xml中的标签和属性设置越多,节点树的深度越深,在解析时要执行的判断逻辑、函数的嵌套和递归就越多,所以时间消耗越多;
(2)inflate操作只是布局影响的第一个环节,一个界面要显示出来,在requestLayout后还要执行一系列的measure、layout、draw的操作,每一步的执行时间都会受到布局本身的影响。而界面的最终显示是所有这些操作完成后才实现的,所以如果布局质量差,会增加每一步操作的时间成本,最终显示时间就会比较长。
界面性能取决于UI渲染性能. 我们可以理解为UI渲染的整个过程是由CPU和GPU两个部分协同完成的.
其中, CPU负责UI布局元素的Measure, Layout, Draw等相关运算执行. GPU负责栅格化(rasterization), 将UI元素绘制到屏幕上.
如果我们的UI布局层次太深, 或是自定义控件的onDraw中有复杂运算, CPU的相关运算就可能大于16ms, 导致卡顿.
这个时候, 我们需要借助Hierarchy Viewer这个工具来帮我们分析布局了. Hierarchy Viewer不仅可以以图形化树状结构的形式展示出UI层级, 还对每个节点给出了三个小圆点, 以指示该元素Measure, Layout, Draw的耗时及性能.
那么布局如何优化?总结如下几点:
1. 遵循一条规则:布局层次尽量少
也就是说,在达到同样布局效果的前提下,xml文件中树的深度尽量的潜。要做到这一点需要合理的使用布局控件:
典型的情况是你可以使用RelativeLayout来代替LinearLayout实现相同的布局效果;
尽量不要嵌套使用RelativeLayout.
尽量不要在嵌套的LinearLayout中都使用weight属性.
Layout的选择, 以尽量减少View树的层级为主.
去除不必要的父布局.
善用TextView的Drawable减少布局层级
如果H Viewer查看层级超过5层, 就需要考虑优化下布局了
还有一种是如果布局树的A节点只有一个子节点B,而B只有一个子节点C,那么B通常是可以去掉的;
合理的使用<merge>标签,如果布局X可以被include到Y中,那么需要考虑X的根节点是否可以设置为<merge>,这样在解析时会将<merge>的子节点添加到Y中,而<merge>本身不会添加。
<include> 使用include来重用布局.
ListView优化
- contentView复用
- 引入holder来避免重复的findViewById.
- 分页加载
2. 使用Lint分析布局
Lint是SDK中tools目录下的工具,ADT中集成了Lint的可视化控制界面。用Lint扫描应用程序,它会从很多方面对应用进行分析,并提示那些可能有缺陷的地方,其中就包含与性能相关的内容。
打开Activity的布局文件 xxx.xml, 在Android Studio菜单栏中开启Lint检查:
选择当前文件:
会在下方弹出分析结果:
分析结果包括用法检测(例如版本特有属性), 国际化(字符串是否提取到strings.xml, Rlt支持等), 以及我们今天的主题---性能分析结果.
点开"Android -> Lint -> Performance"项, 可以看到关于布局性能的建议项. 此例中是说ScrollView的父级LinearLayout是不必要的.
3. 使用HierarchyViewer分析布局
HierarchyViewer(以下简称HV)也是SDK中tools目录下的工具,ADT中也集成了HV的可视化控制界面。可以使用HV查看当前界面的布局,它能提供很多信息,其中有两个可以帮助我们分析性能问题:
HV的树视图展现了视图控件的相互关系,可以用来检查是否有第1点中提到的情况。
树视图中可以显示每个节点measure、layout、draw的时间,并且每一项用一个圆点表示其耗时是否正常,每个圆点分别用绿色、黄色、红色表示耗时正常、警告、危险,这样就可以很方便的找到有性能瓶颈了。如果树视图中没有显示这些时间,你可以点击“Obtain layout times for tree rooted at selected node”按钮刷新界面显示。
3.1 启用H Viewer
比较早接触Android开发的同学可能知道, H Viewer只能在root过的机器才能使用. 主要是在没有root过的机器中view server这个服务是没有开启的. H Viewer就无法连接到机器获取view层级信息.
正所谓高手在民间, 大家都尝试在未root的机器中启用view server来使用H Viewer. 最具代表性的就是romainguy的ViewServer, 只需集成少量代码到你的Activity, 相当于在手机端开启了view server服务,
建立socket通道与PC端的H Viewer通信.
此工程被Android官网吸收, 作为开启H View的方案之一.
完整开启H Viewer的方法如下:
- 手机开启开发者模式, USB调试.
- 根据手机的Android系统版本:
4.0及以下, 没有root. 使用上述的开源工程ViewServer提供的方式.
4.0及以下, 已经root. 无需其他额外设置.
4.1及以上. 需要在PC端设置ANDROID_HVPROTO环境变量.
设置系统环境变量: ANDROID_HVPROTO, 值为ddm
具体设置系统环境变量根据PC系统不同而异.
做完上述配置后, 你就可以打开H Viewer了, 打开DDMS, 如下操作进入H Viewer界面:
3.2 H Viewer界面详解
以详情界面为例说明:
界面分为四个部分:
Window
显示当前连接的设备和供分析的界面. 可手动选择.Tree View
树状图的形式展示该Activity中的View层级结构. 可以放大缩小, 每个节点代表一个View, 点击可以弹出其属性, 当前值, 并且在LayoutView中会显示其在界面中相应位置.
Tree View是我们主要要分析的视图.Tree Overview
Tree View的概览图. 有一个选择框, 可以拖动选择查看. 选中的部分会在Tree View中显示.Layout View
匹配手机屏幕的视图, 按照View的实际显示位置展示出来的框图.
3.3 H Viewer参数解读
- 通过Tree View可以很直观的看到View的层级.
- 点击Tree View的RepoItemView这个节点:
三个小圆点, 依次表示Measure, Layout, Draw, 可以理解为对应View的onMeasure, onLayout, onDraw三个方法.
- 绿色, 表示该View的此项性能比该View Tree中超过50%的View都要快.
- 黄色, 表示该View的此项性能比该View Tree中超过50%的View都要慢.
- 红色, 表示该View的此项性能是View Tree中最慢的.
如果界面的Tree View中红点较多, 那就需要注意了. 一般来说:
1, Measure红点, 可能是布局中嵌套RelativeLayout, 或是嵌套LinearLayout都使用了weight属性.
2, Layout红点, 可能是布局层级太深.
3, Draw红点, 可能是自定义View的绘制有问题, 复杂计算等.
由上图, 可以看到我们的RepoItemView的三项指标都不合格, 证明其还有很多优化空间. 层级, 绘制都可以优化.
4. 使用ViewStub延迟加载视图
ViewStub是一个没有尺寸大小并且不会在布局中嵌套或渲染任何东西的轻量级的视图。如果界面中有一部分视图控件不需要立即显示,则可以将其写到一个单独的layout文件中,用ViewStub标签代替,当要真正显示这部分内容时再通过ViewStub将视图加载进来。
5. 过度绘制(Overdraw)
关于GPU的绘制, 如果我们的界面存在Overdraw, 也可能导致卡顿.
Overdraw: 用来描述一个像素在屏幕上多少次被重绘在一帧上.
通俗的说: 理想情况下, 每屏每帧上, 每个像素点应该只被绘制一次, 如果有多次绘制, 就是Overdraw, 过度绘制了.
5.1 调试Overdraw
Android系统提供了可视化的方案来让我们很方便的查看overdraw的现象:
在"系统设置"-->"开发者选项"-->"调试GPU过度绘制"中开启调试:
此时界面可能会有五种颜色标识:
- 原色: 没有overdraw
- 蓝色: 1次overdraw
- 绿色: 2次overdraw
- 粉色: 3次overdraw
- 红色: 4次及4次以上的overdraw
一般来说, 蓝色是可接受的, 是性能优的.
5.2 Overdraw的分析处理
上面有言, 所谓Overdraw, 就是在一个像素点上绘制了多次. 常见的就是:
- 绘制了多重背景.
- 绘制了不可见的UI元素.
Overdraw主要原因是背景的多重绘制, 或是不可见的View在背后绘制等, 但不仅限于此.