一、问题背景

在开发过程中,往往会听到 “性能优化” 这个概念,这个概念很大,比如网络性能优化、耗电量优化等等,对 RD 而言,最容易做的或者是影响最大的,应该是 View 的性能优化。当业务愈加庞大、界面愈加复杂的时候,没有一个良好的开发习惯和 View 布局优化常识,做出来的界面很容易出现 “卡顿” 现象,从而严重影响用户体验。

结合具体业务特点进行梳理,对于性能问题的产生大致概括为以下3个方面:

1、首先,需求开发或重构过程中,由于 RD 同学的关注点主要放在单个业务开发上,所以很容易忽略性能上的问题,即使在开发过程中发现了卡顿问题,但由于业务紧张,也不太会放下当前的工作去处理性能方面的问题。

2、其次,测试过程中时而会有 QA 同学反馈说某些页面相比之前的版本出现了严重的卡顿问题,但有时通过发版平台、logcat 等并未找到该机型的卡顿记录,最终一些可能存在的性能问题也就不了了之。

3、再次,大部分PM同学关注的都是 RD 是否有100%实现需求,UI/UE 同学关注的是应用的交互体验是否良好,并没有太多同学会去关注应用的性能问题,对于性能较好的手机可能体验不到差距,对于中低档手机,流畅度却起着关键的作用。

二、问题归类

引起卡顿的原因从细节上可分为以下几类原因:

%E5%88%86%E7%B1%BB.png?version=1&modific

外部因素最为致命!日常开发中更多的应该关心布局的嵌套层级和冗余资源。

比如,当需要将一个 TextView 和一张图片放在一起展示时,我们可以考虑使用 TextView 的 drawableLeft(drawableRight、drawableTop、drawableBottom) 属性来设置图片,而不是使用一个 LinearLayout 来将 TextView 和 ImageView 封装在一起,这样就能减少 View 的绘制层级。

又比如,子元素和父元素都是相同的背景时,就不必在每个子元素中都添加背景属性,等等。

接下来,我们针对过度绘制和布局层级进行测试分析。

三、问题复现

3.1、查看页面是否存在过度绘制

  • 方法一:可通过打开手机“开发者选项”->"调试GPU过度绘制开关",就可以在手机屏幕上查看绘制情况。
  • 方法二:通过 adb 命令开启 GPU 过度绘制调试 :adb shell setprop debug.hwui.overdraw show

如下图所示:

dev.png?version=1&modificationDate=15372

图1:开发者选项GPU.png?version=1&modificationDate=15372

图2:开启GPU过度绘制

开启GPU过度绘制后,点击应用,可以看到各种颜色的区域。依据过度绘制的层度可以分成:

  • 原色:无过度绘制 (一个像素只被绘制了一次)
  • 蓝色:1 次过度绘制 (一个像素被绘制了两次)
  • 绿色:2 次过度绘制 (一个像素被绘制了三次)
  • 粉色:3 次过度绘制 (一个像素被绘制了四次)
  • 红色:4 次及以上过度绘制(一个像素被绘制了五次以上)

    GPU-color.png?version=1&modificationDate
                                           图3:GPU绘制图解

接下来我们从设计的角度来看下App是否GPU绘制过度,看一下以下几个界面:

Screenshot_20180913-163501.png?version=1

图4:QQ浏览器Screenshot_20180917-163952.png?version=1

图5:顺风车-车服务(优化前) Screenshot_20180913-163747.png?version=1

图6:Google浏览器

从上图我们可以看出,QQ浏览器页面GPU绘制比较正常基本都是在1x-2x范围内,滴滴顺风车(优化前)和Google浏览器过度绘制较为严重,基本都是3x-4x。

颜色越深代表绘制的次数越大,当一个屏幕大部分都被粉丝或红色占据时,我们就必须考虑优化了。

3.2、View的绘制流程

为了更好地理解 View 性能优化的原理,以及造成 “卡顿” 的可能原因,我们简单讲解下View的绘制流程,为后续的 hierarchyviewer 分析做铺垫。

我们都知道,View的绘制分为三个阶段:测量、布局和绘制,这三个阶段各自的作用如下:

  • measure: 为整个 View 树计算实际的大小,即设置实际的高(对应属性:mMeasureHeight)和宽(对应属性:mMeasureWidth),每个 View 的控件的实际宽高都是由父视图和本身视图所决定的。
  • layout:为将整个根据子视图的大小以及布局参数将 View 树放到合适的位置上。
  • draw:利用前两部得到的参数,将视图显示在屏幕上。

当一个 Activity 对象被创建完成之后,会将一个 DecorView 对象添加到 Window 中,同时会创建一个 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 对象建立联系,然后绘制流程就会从 ViewGroup 的 performTraversals() 方法开始执行,如下图所示:

view%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B

图7:view的绘制流程

整个绘制流程从 ViewRootImpl 的 performTraversals() 方法开始,在该方法内会调用 performMeasure() 方法进行测量子 View(也就是根 View,顶级的 ViewGroup),调用完 performMeasure()后,会接着调用 performLayout() 和 performDraw() 进行 View 的布局和绘制。

四、问题分析

4.1、较多的背景重叠

过度绘制是指屏幕中某个范围的像素在单个帧中被多次渲染(超过一次),比如父控件设置了背景色,子控件设置了图片显示或者文本显示,这样在子控件的对应区域,就会渲染两次。每次渲染都会带来性能消耗,同一区域渲染的次数越多,那么带来的消耗就越大。

举例来说,粉刷一个房间或一间房子时,给墙壁涂上颜色需要做大量的工作。假如还要重新粉刷一次的话,第二次粉刷的颜色会覆盖住第一次的颜色,第一次的颜色就永远不可见了,等于第一次粉刷做的大量工作就完全被浪费掉。

以"乘客端-顺风车-车服务"页面为例,绘制颜色呈现为红色的原因在于其页面渲染共经历5层绘制:

  • 第一层为地图页,是平台规定的,而顺风车页面对其进行了遮盖处理,所以即使不可见也不可去掉。可参考下图的“快车”地图页
Screenshot_20180918-173810.png?version=1
图8:地图页面
  • 第二层为车服务页面的背景渲染,顺风车统一背景色,所以不可修改为无色背景
Screenshot_20180918-173615.png?version=1
图9:车服务页面
  • 第三层为页面布局中的卡片布局背景
  • 第四层为页面具体控件元素的渲染
  • 第五层为RD在layout中多添加的一层布局背景
Layout如下图所示
carbon.png?version=1&modificationDate=15

图10:车服务Loyout布局

分析布局可知:多层布局重复设置了背景色导致Overdraw。

4.2、复杂的Layout层级

Android的布局文件的加载是LayoutInflater利用pull解析方式来解析,然后根据节点名通过反射的方式创建出View对象实例;同时嵌套子View的位置受父View的影响,类如RelativeLayout、LinearLayout等经常需要measure两次才能完成,而嵌套、相互嵌套、深层嵌套等的发生会使measure次数呈指数级增长,所费时间呈线性增长。

HV-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%
图11:顺风车车服务首页初始状态(优化前)
332.jpg?version=1&modificationDate=15372

图12:初始状态View个数及耗时(优化前)

使用Hierarchy Viewer来看查看一下设置界面,可以从下图中得到首页界面的一些数据及存在的问题:

  • 嵌套共计7层(仅setContentView设置的布局),布局嵌套过深;
  • 共绘制332个View,以及若干个无用布局。
  • 页面渲染总耗时为:50.574ms

由此得到结论:Android渲染需要消耗时间,布局越复杂,性能就越差。那么随着控件数量越多、布局嵌套层次越深,展开布局花费的时间几乎是线性增长,性能也就越差。

五、解决问题

优化的目的,主要就是减少绘制时间。

5.1、过度渲染解决

去掉冗余background后的Overdraw如下图所示:

Screenshot_20180917-164824.png?version=1

图13:顺风车乘客端(优化后)

另外一个容易忽略的点是我们的Activity使用的Theme可能会默认的加上背景色,不需要的情况下也可以去掉。

5.2、Layout层级优化

布局的优化其实说白了就是减少层级,越简单越好,减少overdraw,就能更好的突出性能。

5.2.1、尽量使用相对布局

一般情况下用LinearLayout的时候总会比RelativeLayout多一个View的层级。而每次往应用里面增加一个View,或者增加一个布局管理器的时候,都会增加运行时对系统的消耗,因此这样就会导致界面初始化、布局、绘制的过程变慢。

%E5%B8%83%E5%B1%80%E6%AF%94%E8%BE%83.jpg
图14:布局比较

选择布局容器的基本准则:

  • 在RelativeLayout和LinearLayout同时能够满足需求时,尽可能的使用 RelativeLayout 以减少 View 层级,因为可以通过扁平的RelativeLayout降低LinearLayout嵌套所产生布局树的层级,使 View 树趋于扁平化。
  • 在不影响层级深度的情况下,使用 LinearLayout 和 FrameLayout 而不是 RelativeLayout。
relativelayout.png?version=2&modificatio
图15:RelativeLayout布局

5.2.2、使用标签重用Layout

  • 布局重用之include

如果一些布局在许多布局文件中都需要被使用,我们就可以把它单独写在一个布局中,然后使用这个标签在需要使用它的地方把这个布局加进去,这样就达到了重用的目的,最典型的一个用法就是,如果我们自定义了一个TitleBar,这个TitleBar可能需要在每个Activity的布局文件中都使用到,这样我们就可以使用这个标签来实现。

include.png?version=2&modificationDate=1

图16:include标签

直接使用include标签的layout来指定就可以把这个bts_home_tip_full_layout的布局文件加入进去,这样在每个Activity中我们就可以使用include标签来重用这个布局了,不需要在每个里面都重复写一个bts_home_tip_full_layout的布局了,下面我们来看看这个bts_home_tip_full_layout的布局文件。

总结一点:这个标签主要是做到布局的重用,使用这个标签可以把公共布局嵌入到所需要嵌入的地方。

  • 布局重用之merge

在使用了include后可能导致布局嵌套过多,多余不必要的layout节点,从而导致解析变慢。例如Layout A中的RelativeLayout布局中使用了include标签,而在引入的布局文件中也包含了RelativeLayout,那么在Layout A的布局中实际会有两个RelativeLayout被加载进行渲染。

在layout中可以使用merge标签来作为include标签的一种辅助扩展来使用,其主要作用是为了防止在引用布局文件时产生多余的布局嵌套,减少布局的深度。

不必要的节点和嵌套可通过hierarchy viewer或设置->开发者选项->显示布局边界查看。

merge.png?version=1&modificationDate=153

图17:merge标签

5.2.3、按需载入视图

viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,既不会占用显示,也不会占用位置,从而在解析layout时节省cpu和内存。 使用ViewStub并不会影响UI初始化时的性能。

viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。

ViewStub使用延迟加载的方式,当需要时才会加载,避免资源的浪费,减少渲染时间,在需要的时候才加载View。

viewstub.png?version=2&modificationDate=

图18:viewstub标签

最开始使用setContentView(R.layout.bts_home_entrance_layout)的时候,ViewStub只是起到一个占位符的作用,它并不会占用空间,所以对其他的布局没有影响。

当我们点击Button的时候,我们就可以把ViewStub的layout属性指定的布局加载进来,用它来替换ViewStub,这样就把我们需要加载的内容加载进来了。

这里的ViewStub控件的layout指定为bts_home_not_open_layout。当点击button隐藏的时候不会显示bts_home_not_open_layout,而点击button显示的时候就会用bts_home_not_open_layout替代ViewStub。

现在我们再使用Hierarchy Viewer来检测一下:

HV-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%

图19:优化后的车服务首页布局层次

227.jpg?version=1&modificationDate=15372

图20:优化之后的View个数及耗时

优化后:
1. 控件数量从332个减少到227个,减少31.6%;

2.优化后的页面总耗时为39.463ms,页面优化提高21.9%

六、建议

1.保持整体背景统一

建议前期在设计时尽量保持整体背景统一,另外可以检查在布局和代码中设置的背景,有些背景是被隐藏在底下的,它永远不可能显示出来,这种没必要的背景尽量要移除,因为它很可能会严重影响到app的性能。

2.检查和优化布局

首先推荐用Android提供的布局工具Hierarchy Viewer来检查和优化布局。

  • 建议1:如果嵌套的线性布局加深了布局层次,可以使用相对布局来取代。
  • 建议2:用标签来合并布局,这可以减少布局层次。
  • 建议3:用标签来重用布局,抽取通用的布局可以让布局的逻辑更清晰明了。

顺风车Android性能优化之View布局优化相关推荐

  1. android textview 文字居中_Android布局优化,看这3点就够了

    码个蛋(codeegg)第 712 次推文 作者:Android技术 博客:https://www.jianshu.com/p/2ee61b88175e 前言 在编写Android布局时总会遇到这样或 ...

  2. Android性能优化系列之布局优化,Android程序员校招蚂蚁金服

    25 26 rInflate方法关键代码 void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet ...

  3. Android优化五:布局优化

    1.减少布局层级 Google在API文档中建议View树的高度不宜超过10层. 以前我们用Eclipse写代码时,自动生成的模板是以LinearLayout为根节点的,但是后面变成了Relative ...

  4. Android优化篇之布局优化

    绝大部分APP的设计中,都是提供界面与用户进行交互通信,如何保证页面流畅不卡顿也成为我们需要关注的重点,本篇将介绍如何针对布局进行优化. 1.捕获定位界面是否存在卡顿掉帧的情况 1.1 打开Andro ...

  5. android Merger 代替 FrameLayout:布局优化

    <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android=" ...

  6. Android 系统性能优化(80)---Android性能优化:这是一份详细的布局优化 指南(含lt;includegt;、lt;Viewstubgt;、lt;mergegt;)

    Android性能优化:这是一份详细的布局优化 指南(含<include>.<Viewstub>.<merge>) 前言 在 Android开发中,性能优化策略十分 ...

  7. Android 性能优化---(7)布局优化

    Android性能优化:布局优化 详细解析 前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的布局优化,希望你们会喜欢. 目录 1. 影响的性能 布局性能的好坏 主要影响 ...

  8. Android性能优化:布局优化 详细解析(含include、ViewStub、merge讲解 )

    1. 影响的性能 布局性能的好坏 主要影响 :Android应用中的页面显示速度 2. 如何影响性能 布局影响Android性能的实质:页面的测量 & 绘制时间 1个页面通过递归 完成测量 & ...

  9. android布局时长分析,Android性能优化:布局优化 详细解析(含、、讲解 )

    前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的布局优化,希望你们会喜欢. 目录 1. 影响的性能 布局性能的好坏 主要影响 :Android应用中的页面显示速度 2. ...

最新文章

  1. 逻辑设计中复位的稳妥处理方法?
  2. LiveRTMP 之RTMP直播高效推送缓冲区
  3. 混合编程黑科技:跨语言编程问题迎刃而解的3个要点
  4. C语言判断二叉树是否为二叉搜索树(附完整源码)
  5. 2021-10-12Spring缓存注解@Cacheable、@CacheEvict、@CachePut使用
  6. mongodb数据库淘汰_mongodb 内存数据淘汰策略
  7. dp打开思路:HDU1029 HDU1087 HDU1176 HDU1257 POJ1458(水题不水)
  8. 计算机应用基础综合测试题一,计算机应用基础综合测试题.doc
  9. android 获取文件夹的字节数,android java file 清理垃圾获取文件大小 删除文件等操作...
  10. 远程调试运行在Resin上面的Web应用程序
  11. 高阶无模型自适应迭代学习控制学习记录
  12. 税务系统什么时候使用计算机,2020年税务师考试题量、答题要求及计算器使用规定...
  13. 太极助手发公开信解释越狱捆绑原因
  14. android 批量保存网页图片大小,360浏览器看图模式 一键保存“高清套图”
  15. Android ADB 环境变量配置
  16. Linux 通配符 与 正则表达式 的区别与详解
  17. kux2mp4(优酷kux转换为mp4软件) v2021
  18. 4个关键,如何清晰的做好数据分析
  19. win10关闭windows聚焦_Win10聚焦锁屏壁纸无法自动更换的处理方法
  20. 从高中编码员到国际技术演讲者— Arun Michael Dsouza访谈

热门文章

  1. verilog中的定点数、浮点数、定点小数、定点整数的表示及运算
  2. Nuget包管理工具(程序包控制台执行语句)
  3. Day04:循环结构(while、do-while、for)
  4. 11 旋转数组的最小数字
  5. [svc]centos7的服务治理-systemd
  6. RS-485总线和Modbus通信协议的关系
  7. Ubuntu下好的PDF阅读器介绍
  8. Python 第七篇:socket编程
  9. Oracle redo 日志切换时间频率
  10. Inondb中的checkpoint