Android UI项目优化两年经验之谈

Android系统的卡顿,不流畅是一个被诟病多年,让不少用户以为体验不如iOS的问题。可是通过Google多年对Android系统的迭代,实际上在系统层面上Android已经在UI渲染性能上作出了不少改进和优化,对于应用开发者而言,很大程度上,写不出流畅的页面和动画已经再也不是系统的锅,而是应该努力去学习和理解Android系统的特性,发如今渲染过程当中的瓶颈,作出合理的优化。

回顾Android的历史版本,咱们能够一览Google Android团队在各版本上面作出的具体改进。android

Android2.x时代,UI渲染采用CPU软渲染,基本上滑动列表只能维持在30fps左右,那个时代硬件性能和系统架构确实都不够好。缓存

  • 2011年,Android 3.0 Honeycomb发布,Android开始支持GPU硬件加速渲染,采用OpenGL渲染UI,第一款Android 3.0的设备是MOTO ZOOM,一款分辨率为1280800像素的平板电脑,第一次达到如此高的分辨率( 此前的Android手机最高分辨率为480854),若是说没有硬件加速,Android系统仅靠CPU很难流畅渲染这么高的分辨率。安全

  • 2012年,Android4.1 JellyBean推出的“Project butter”(黄油计划)带来了vSync(垂直同步)和tripple buffer(三重缓冲)技术。vSync定时中断让Android系统渲染更加有节奏,tripple buffer让系统在偶尔丢帧的状况下经过多生成一个buffer解决了以前在丢帧时因buffer不足而致使无法按时渲染下一帧的问题。此外还优化了触摸事件,经过CPU input boost和预测触摸行为减小触摸延时,让系统响应更加灵敏。Android4.1是我认为Android UI渲染架构革新史上里程碑式的一个版本,Cortex双核 A9 CPU+PowerVR SGX540 GPU的Galaxy Nexus渲染720p的屏幕,刷上JellyBean系统以后对比4.0 ICS是巨大的提高,基本能流畅运行在60fps。性能优化

  • 2013年,Android4.3 JellyBean发布,在图形渲染架构上更进一步优化,经过对GPU绘制指令的Reorder&Merge,减小了GPU在绘制时上下文的切换和绘制指令执行的数量,一样的内容能以更小的消耗去渲染,提升了渲染效率从而提升了渲染性能。架构

  • 2014年,Android5.0 Lollipop发布,带来了全新的Material Design设计风格和分担主线程负载的RenderThread,在主线程更新完DisplayList以后,将draw commands所有都同步到RenderThread,而后由RenderThread继续去与OpenGL Render(GPU)打交道完成最终的绘制,而主线程能够在同步完draw commands以后开始处理接下来的input event或进入Idle状态,很明显这减小了主线程的负担,也能让主线程更快抽身出来响应用户的操做减小延迟。另外在较新版本的RecyclerView库中,经过一个叫GapWorker的类在Android 5.0及以上利用主线程在同步完draw commands以后等待下一帧绘制的这一点空闲时间,对将要显示的ViewHolder作了预加载操做,从而让列表在接下来的滑动中更加流畅。app

以后这些年的Android6.x-8.x版本对于性能的改进主要集中在ART Runtime执行效率和速度的改进,以及更加严格的后台运行限制。代码执行更快,后台应用活动受到限制从而减小了与前台应用抢资源的可能,必定程度上也是提升了UI渲染性能。不过也有一些手机厂商的定制系统(ROM),修改系统框架,加了太多定制功能,带来了一些“负优化”,相同甚至更好的硬件性能,运行流畅度比不上相同版本的原生系统。总而言之从5.0开始,Android系统的UI渲染架构确实已经足够好,并且硬件性能还在以每一年20-30%的速度进步,因此在现在的软硬件环境下,咱们彻底能够经过合理优化写出滑动流畅的界面和过渡优雅的动画。框架

咱们知道UI渲染大体分为 Measure->Layout->Draw三个阶段,这也是咱们编写自定义View必须本身实现的三个方法。

image

根据多年对Android系统的了解和开发经验的总结,列出了以下表格工具

image

在Measure/Layout阶段,咱们可能更多关注ViewGroup,即RelativeLayout,ConstraintLayout,LinearLayout,FrameLayout等这些布局类。总体的一个原则就是,尽可能减小子view的数量和layout的嵌套。布局

由于过深的View层级会致使测量阶段耗时较多,复杂的依赖和约束关系可能致使屡次测量。咱们能够经过不少系统提供的工具来查看页面的View层级,仔细审视,看是否有可优化的地方。性能

Hierarchy Viewer是Android SDK里面提供的一个能够查看调试应用Activity页面View层级的工具,而且能够手动触发页面从新渲染,观察每一个view节点的各个阶段的耗时状况。

具体的使用教程能够参看官方文档:developer.android.com/studio/prof…

不过这个工具目前已经被官方做废了,Android Studio里面推出了新的LayoutInspector来代替他,不过目前LayoutInspector只能查看View层级没法查看节点渲染耗时。

image

LayoutInspector是集成在Android Studio里面的工具,能够详细查看调试应用的View层级,以及view节点的各类属性值。

image

另外还有开发者选项里面的“显示布局边界”,也可让咱们比较方便的查看当前界面的View节点组成和分布状况。与Hierarchy Viewer和LayoutInspector不一样的是,

显示布局边界能够查看任何应用界面的View节点大小和位置。不过他查看到的信息有限,只能根据描线看到View节点的边界,位置等信息。 也许是出于安全考虑,没法查看到更多信息,不过咱们能够从黑盒角度审视其余应用界面的布局学习别人的作法和思路。

image

1.经过合并多个要显示的数据到一个View来实现减小View个数

查看界面View层级的目的是为在不影响功能实现的状况下尽量减小View数量和优化View层级,咱们能够经过合并多个要显示的数据到一个View上来实现减小View的目标, 以下图展现了几种优化方案

image

2.采用ConstraintLayout减小布局嵌套

传统方式,对于以下布局咱们可能会采用RelativeLayout和LinearLayout嵌套的方式完成布局,虽然RelativeLayout能够定义view之间的相对关系和约束关系,

可是他没法完成百分比布局,按照权重weight布局,宽高比等布局的场景,因此咱们为了完成复杂界面的布局,每每须要大量的嵌套不一样的layout来组合完成。

image

因此2017年Google在I/O大会上推出了ConstraintLayout来解决此问题,他不只拥有空前强大的布局约束属性,更重要的是性能比RelativeLayout更好,

他足够丰富的布局功能以至于不须要嵌套就能完成很是复杂的界面布局,因此Google推出ConstraintLayout的目的就是为了取代RelativeLayout

来优化UI布局的性能。并且ConstraintLayout是一个不断开发迭代的layout库,不断优化的性能和扩展的功能让他愈来愈强大,学会使用ConstraintLayout 是每个位Android开发者都必需要学会的高级技能。

关于学习ConstraintLayout,这里推荐一些文章供你们学习。相信你们学会ConstraintLayout以后, 就不会再想用RelativeLayout了,并且也尽可能不要再用RelativeLayout,由于即便实现相同功能其性能相对ConstraintLayout也不如。

ConstraintLayout彻底解析

ConstraintLayout官方API文档

ConstraintLayout资源集合

以下界面是采用ConstraintLayout布局的相同界面,能够看到采用ConstraintLayout以后,View的数量和层级都大为减小,这能够显著明显减小View在 测量阶段的耗时。

推荐选择Layout顺序:优先FrameLayout(性能最好)>LinearLayout(性能次之)>ConstraintLayout(功能最强大,复杂布局用它)。

image

3.LinearLayout关闭baselineAligned

LinearLayout有一个属性叫android:baselineAligned,默认值为true,即默认开启baseline基线对齐,是LinearLayout内有多个横向排布的TextView须要优化文字对齐效果的时候一个开关。

以下图所示咱们能够大概了解一下baseline的含义。

image

若是咱们的LinearLayout内的多个TextView不须要基线对齐,或者说并不包含TextView而是一些其余的View,那么咱们能够关闭此功能来减小测量的耗时,避免开启后因对齐基线而多测量一次。

下图是LinearLayout measure时的部分代码,很明显能够看到由于对齐基线而多了一次测量。

image

4.ViewStub延迟初始化

ViewStub是一个不可见,零尺寸的View,可在使用时能够按需初始化布局。当ViewStub被设置为visible或者调用inflate方法时,指定的layout会被加载填充, 被加载的layout将代替ViewStub添加到ViewStub的父布局中。

image

咱们能够经过阅读ViewStub的源码看到他在未inflate时几乎无开销,因此对于部分一开始不使用的layout,能够采用此种方式加载,提升初次inflate和渲染主界面的速度。

image

5.include布局时善用merge标签,减小嵌套

当咱们有一些layout能够复用时,咱们每每能够把它单独写到一个layout文件里面,而后须要的时候再include进主layout里面,layout文件每每须要一个根布局来包装全部的子View(ViewGroup),

而这个根布局被include以后每每会由于上层是一个一样的ViewGroup变得多余,这里咱们能够用Merge标签做为根布局来包含全部子view而去除原有的ViewGroup,减小一层嵌套。

介绍完了mesure/layout阶段的优化技巧,咱们再来看看在draw阶段咱们能够作哪些优化。 在进行优化以前首先给你们介绍一个很是重要的查看界面渲染流畅度的工具,GPU呈现模式分析,经过他咱们能够很直观的观察界面渲染的帧率和流畅度

image

关于每条竖线的分段颜色的含义,以下是一个详细介绍,他们表明了每一帧渲染过程的各个阶段,分段长度则表明这一阶段的耗时

image

5.减小Overdraw过分绘制

过分绘制指的是在同一块屏幕空间内发生的重叠绘制次数,这是不可避免会存在的问题,咱们能作的就是尽可能减小这种现象。

很幸运开发者选项里面的“调试GPU过分绘制”提供了相应的工具让咱们能够很直观的观察到这种现象

image

发生过分绘制咱们能优化的主要是避免绘制看不见的元素,这里面最容易犯错的地方就是绘制了看不见的View背景。

下图显示了咱们经过去掉多个ViewGroup的背景色,而后仅在窗口背景上设置须要的背景颜色,减小了Overdraw又不影响功能实现。

image

下图是去掉多余背景以后的效果,结果很是明显,大幅减小了overdraw现象,这样在draw的阶段能够有更好的性能表现。

image

6.Canvas.clipRect/quickReject

Canvas.clipRect()能够定义绘制的边界,边界之外的部分不会进行绘制。

Canvas.quickReject()能够用来测试指定区域是否在裁剪范围以外,若是要绘制的元素位于裁剪范围以外,就能够直接跳过绘制步骤。

下图演示了一个包含多个层叠View的自定义ViewGroup,当发生重叠时不可避免要产生很严重的过分绘制状况,但实际上底层被遮盖的View的某一部分是不可见的, 咱们能够经过在ViewGroup drawChilds的时候,经过计算显示和不显示的区域来作合理的clip,避免绘制看不见的部分。

image

实际上若是你看过DrawerLayout的源码,他里面的实现也有相似这样的优化,在Drawer打开的时候底下View被遮盖的部分区域被clip不绘制。

image

经过下图咱们能够明显看出来,Drawer并无比右边没遮盖的区域绘制更多层,由于Drawer底下被遮盖的区域被clip作了优化。

image

7.占位背景图优化

在显示图片的时候,咱们有些时候可能须要给图片加边框和阴影或者设置默认占位图片作修饰,若是当图片加载完了以后咱们仍然须要显示占位图来修饰原图, 那么可能在被目标图遮盖的那部分区域就多绘制了一层占位图的背景,致使即便不显示也要被绘制。

image

因此这里咱们提出一种优化方法,根据不一样的状态显示不一样的占位修饰图。当未加载完成是显示带背景的占位图,当加载完成后咱们显示透明背景的占位图 这样被目标图遮盖的区域就不会多绘制一层无用的背景了。

image

优化完了以后结果以下,很明显,使用透明背景的边框图少了一次绘制。

image

在这里总结一下:避免Overdraw的核心原则始终只有一个,就是避免绘制看不见的元素

8.Alpha blending透明度合成优化

image

若是直接对每一个View分别作alpha合层会致使丢掉他们以前的层叠效果,致使看见被覆盖的底层的View,显然这不是咱们想要的结果

image

咱们想要的结果是以下图所示,Alpha合成以后仍然能保留堆叠信息。实际上系统已经为Alpha合层作出了合理处理,在帧缓冲(FrameBuffer)绘制以前,

会建立离屏缓冲区(off-screen buffer/Canvas Layer)进行绘制,而后以一个总体进行透明度合层,结果再被复制到帧缓冲。也就是说在绘制带透明度的View时, 咱们须要双倍的开销。

image

为了不由于透明度合层致使绘制时双倍的开销,有些须要alpha合层的地方咱们能够作一些优化,好比TextView须要设置alpha值,若是没有背景,咱们能够直接修改文字颜色值,

在原颜色值的基础上加入透明度混合计算以后直接修改文字颜色值,这样避免alpha合层时建立离屏缓冲从而减小开销提升性能。

image

对于自定义View,咱们能够经过重写onSetAlpha方法返回true来向上层框架代表本身有能力处理alpha值的变化,经过将alpha值设置到paint上, 在draw的时候直接处理了alpha相关信息。

image

8.重写hasOverlappingRendering方法

hasOverlappingRendering顾名思义就是是否存在重叠渲染的意思。hasOverlappingRendering方法是打算让继承自View的子类覆盖重写的方法,

当View的alpha<1时他是一个优化点,默认返回true会在渲染时启用离屏缓冲致使双倍开销,而若是View没有重叠绘制的话,那么能够返回false,

这样View在alpha<1的状况下绘制时就不会启用离屏缓冲,必定程度上优化了渲染性能。

image

下面是ImageView的源码,能够看到当判断没有background的时候,重写hasOverlappingRendering返回false,优化了alpha合成性能。

image

9.Use Hardware Layer

当给一个View.setLayerType(View.LAYER_TYPE_HARDWARE,null)后,就定义了此View采用Hardware Layer(背后采用一个硬件相关纹理)来加速渲染。

Hardware Layer对于ViewGroup以及其所包含的子Views一块儿作Alpha Blending,ColorFilter很是有用。Hardware Layer能够缓存整个复杂的View树到一个纹理上, 减小绘制命令,当须要对整个View树作动画的时候,只须要渲染一次(层)。

image

下面这段代码显示了咱们执行一个旋转动画,开启或不开启HW Layer的对比效果

image

实际运行效果以下,能够看到ViewGroup初始状态是包含4个堆叠的View,本身还有背景色,过渡绘制很严重有不少层,

未开启HWLayer作动画的时候,依然须要渲染不少层,致使帧率不高常常顶到16ms的基线在跑,而开启HW Layer以后,

优化效果立竿见影,ViewGroup及其子View都被合层为一个HWLayer上渲染,只须要绘制一层,运行动画的帧率明显流畅了很多。

image

##最后总结一下Draw阶段的优化原则:

  • 尽可能减小没必要要的绘制。能够经过减小overdraw,Avoid Alpha Blending,clip Canvas,Use Hardware Layer等手段来完成。
  • 避免在调用频繁的路径(eg:onDraw,onBindViewHolder)建立对象,格式化数据和作大量的计算,善用缓存和局部刷新机制。

UI性能优化是一个永无止境的工做,若是咱们能把全部可优化的点都优化好,一点点积累优点,量变就会产生质变,最后必定达会到满意的效果。

相关文章
相关标签/搜索