顺风车Android性能优化之View布局优化

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

一、问题背景

在开发过程中,往往会听到 “性能优化” 这个概念,这个概念很大,比如网络性能优化、耗电量优化等等,对 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:用标签来重用布局,抽取通用的布局可以让布局的逻辑更清晰明了。
相关实践学习
基于阿里云DeepGPU实例,用AI画唯美国风少女
本实验基于阿里云DeepGPU实例,使用aiacctorch加速stable-diffusion-webui,用AI画唯美国风少女,可提升性能至高至原性能的2.6倍。
目录
相关文章
|
17天前
|
缓存 监控 Android开发
安卓应用性能优化的实用策略
【4月更文挑战第2天】 在竞争激烈的应用市场中,一款应用的性能直接影响用户体验和市场表现。本文针对安卓平台,深入探讨了性能优化的关键要素,包括内存管理、代码效率、UI渲染和电池使用效率。通过分析常见的性能瓶颈,并提供针对性的解决策略,旨在帮助开发者构建更加流畅、高效的安卓应用。
|
20天前
|
缓存 监控 Java
构建高效Android应用:从优化用户体验到提升性能
在竞争激烈的移动应用市场中,为用户提供流畅和高效的体验是至关重要的。本文深入探讨了如何通过多种技术手段来优化Android应用的性能,包括UI响应性、内存管理和多线程处理。同时,我们还将讨论如何利用最新的Android框架和工具来诊断和解决性能瓶颈。通过实例分析和最佳实践,读者将能够理解并实施必要的优化策略,以确保他们的应用在保持响应迅速的同时,还能够有效地利用系统资源。
|
26天前
|
调度 数据库 Android开发
构建高效Android应用:Kotlin协程的实践与优化
在Android开发领域,Kotlin以其简洁的语法和平台友好性成为了开发的首选语言。其中,Kotlin协程作为处理异步任务的强大工具,它通过提供轻量级的线程管理机制,使得开发者能够在不阻塞主线程的情况下执行后台任务,从而提升应用性能和用户体验。本文将深入探讨Kotlin协程的核心概念,并通过实例演示如何在实际的Android应用中有效地使用协程进行网络请求、数据库操作以及UI的流畅更新。同时,我们还将讨论协程的调试技巧和常见问题的解决方法,以帮助开发者避免常见的陷阱,构建更加健壮和高效的Android应用。
35 4
|
18天前
|
Java Android开发 开发者
构建高效Android应用:Kotlin协程的实践与优化
在响应式编程范式日益盛行的今天,Kotlin协程作为一种轻量级的线程管理解决方案,为Android开发带来了性能和效率的双重提升。本文旨在探讨Kotlin协程的核心概念、实践方法及其在Android应用中的优化策略,帮助开发者构建更加流畅和高效的应用程序。通过深入分析协程的原理与应用场景,结合实际案例,本文将指导读者如何优雅地解决异步任务处理,避免阻塞UI线程,从而优化用户体验。
|
2天前
|
缓存 移动开发 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第18天】 在移动开发的世界中,打造一个既快速又流畅的Android应用并非易事。本文深入探讨了如何通过一系列创新的技术策略来提升应用性能和用户体验。我们将从用户界面(UI)设计的简约性原则出发,探索响应式布局和Material Design的实践,再深入剖析后台任务处理、内存管理和电池寿命优化的技巧。此外,文中还将讨论最新的Android Jetpack组件如何帮助开发者更高效地构建高质量的应用。此内容不仅适合经验丰富的开发者深化理解,也适合初学者构建起对Android高效开发的基础认识。
2 0
|
8天前
|
存储 数据库 Android开发
构建高效安卓应用:采用Jetpack架构组件优化用户体验
【4月更文挑战第12天】 在当今快速发展的数字时代,Android 应用程序的流畅性与响应速度对用户满意度至关重要。为提高应用性能并降低维护成本,开发者需寻求先进的技术解决方案。本文将探讨如何利用 Android Jetpack 中的架构组件 — 如 LiveData、ViewModel 和 Room — 来构建高质量的安卓应用。通过具体实施案例分析,我们将展示这些组件如何协同工作以实现数据持久化、界面与逻辑分离,以及确保数据的即时更新,从而优化用户体验并提升应用的可维护性和可测试性。
|
11天前
|
监控 API Android开发
构建高效安卓应用:探究Android 12中的新特性与性能优化
【4月更文挑战第8天】 在本文中,我们将深入探讨Android 12版本引入的几项关键技术及其对安卓应用性能提升的影响。不同于通常的功能介绍,我们专注于实际应用场景下的性能调优实践,以及开发者如何利用这些新特性来提高应用的响应速度和用户体验。文章将通过分析内存管理、应用启动时间、以及新的API等方面,为读者提供具体的技术实现路径和代码示例。
|
12天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
26天前
|
缓存 前端开发 Android开发
构建高效Android应用:从设计原则到性能优化
随着移动设备成为我们日常生活不可或缺的一部分,开发一个流畅且响应迅速的Android应用变得至关重要。本文将探讨如何通过遵循Android设计原则和实施细致的性能优化策略来构建高效的Android应用程序。我们将深入分析应用架构的选择、内存管理的要点以及UI设计的优化,旨在为开发人员提供一套实用的指导方针,帮助他们提升应用的整体性能和用户体验。
|
缓存 Android开发 数据格式
Android ListView性能优化,异步加载图片
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/48184383 ListView性能优...
1156 0