Android 性能优化 - Render 篇

1,475 阅读10分钟

前言

本篇文章是udacity上的Android Performace系列视频-Render篇的课程纪要。

这个系列是视频是Google和udacity合作推出的视频,在Google的Android Performace Pattern系列视频的基础上加上了练习的章节,同时还有有趣的过场情节,非常不错的视频。

课程地址:

cn.udacity.com/course/andr…

目录

  • Draw!
  • Android系统的渲染组件
  • 格栅化 - Rasterization
  • 过度绘制 - Overdraw
  • 避免Overdraw
  • Layouts, Invalidation And Performance
  • 布局分析工具 - Hierarchy Viewer
  • 优化布局
  • 总结

DRAW!

Android系统每隔16ms重新绘制一次Activity,也就是说必须在16ms之内完成屏幕刷新的全部逻辑操作,这样才能达到每秒60帧。

PS:每秒60帧是手机硬件决定的,现在大多数手机屏幕刷新频率在60Hz。1000ms/60Hz = 16.666ms/frame。

如果错过了16ms,比如花费了24ms才完成计算,那么就会出现丢帧的情况。

系统准备刷新,但界面还没有准备好,所以用户盯着同一张图看了32ms而不是16ms,丢帧情况下的动画会让用户觉得卡顿。下面让我们来看看造成卡顿的原因以及如何去解决应用中的这些问题。

Android系统的渲染组件

Android系统的渲染Pipeline分为两个关键组件,CPUGPU,两者共同工作在屏幕上绘制图片。每个组件都有自身的特定流程,必须遵守这些特定操作规则才能达到效果。

GPU方面,最常见的性能问题是“过度绘制Overdraw”

CPU方面,最常见的性能问题是“布局的渲染效率”

本文我们将一一介绍,以及如何使用SDK中的可用工具找出拖累应用性能的原因。

格栅化 - Rasterization

在介绍更多之前,我们需要先了解一些底层的机制。Android是如何将复杂的XML布局文件和标记语言转换成用户能看懂的图像的。实际上,这是由格栅化操作来完成的。

格栅化将诸如字符串String、按钮Button、路径Path或形状Shape的一些高级对象拆分到不同的像素上在屏幕上进行显示。格栅化是一个非常费时的操作。GPU的引入就是为了加快格栅化。

GPU使用一些指定的基础指令集来绘制多边形Polygons和纹理Textur,CPU通过OpenGL ES向GPU输入这些指令。

CPU负责把UI组件计算成多边形Polygons或纹理Texture,然后交给GPU进行栅格化渲染。

UI组件转化成一系列多边形和纹理的过程相当耗时、CPU将数据传输给GPU也是非常耗时的动作

所以很明显,必须要减少转换的次数已经上传数据的次数。幸亏OpenGL ES API允许数据上传到GPU后可以对数据进行保存,当下一次绘制一个按钮时,只需要在GPU memory中引用它,然后告诉OpenGL如何绘制。

 从底层的角度来看,渲染性能的优化就是尽可能快地上传数据到GPU,然后尽可能地在不修改的条件下保存数据。

Android Honeycomb之后,整个UI渲染系统就在GPU中运行。Android系统在降低reduce、重新利用reuse和回收recycle GPU资源方面做了很多工作。

比如任何由主题所提供的资源,例如Bitmaps、Drawables等都是一起打包到统一的纹理当中,然后使用网格工具上传到GPU,比如Nine Patches。这样每次你需要绘制这些资源时,都是直接从纹理里面进行获取渲染的,大大加快了这些视图类型的显示。

当然随着UI组件的越来越丰富,有了更多演变的形态。例如显示图片的时候,需要先经过CPU的计算加载到内存中,然后传递给GPU进行渲染。文字的显示更加复杂,需要先经过CPU换算成纹理,然后再交给GPU进行渲染,回到CPU绘制单个字符的时候,再重新引用经过GPU渲染的内容。动画则是一个更加复杂的操作流程。

好了,在了解了GPU绘制的底层原理后,我们来看一个GPU性能的问题瓶颈 - 过度绘制Overdraw。

过度绘制 - Overdraw

过度渲染指的是屏幕上的某个像素点在同一帧的时间内被绘制了多次。有些看不见的布局也进行了绘制,这样就会浪费GPU资源。

目前流行的一些布局是一把双刃剑,带给我们漂亮视觉感受的同时,也造成过度绘制的问题。为了最大限度提高应用的性能,我们必须尽量减少过度绘制。

幸运的是,我们可以通过手机设置里面的开发者选项,打开Show GPU Overdraw的选项,可以观察UI上的Overdraw情况。

蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。(当然最完美的情况是显示应用本来的颜色,不过正如前面提到的现在流行的一些布局为用户提供漂亮的视觉感受,所以某些Overdraw在某种程度上是可以接受的)。

避免Overdraw

有两种避免Overdraw的方法:

一、从视图中清除那些不必要的背景和图片,它们不会在最终渲染图像中显示。

可以从以下方面着手:

1、如果在布局中有指定背景,那么将Activity的背景设置为null。

getWindow().setBackgroundDrawable(null);

2、分析布局,将layout XML中一些不必要的背景属性去掉。

 

二、对视图中重叠的屏幕区域进行ClipRect定义,从而降低CPU和GPU的消耗。

Android系统利用了剪辑clipping技术来避免Overdraw,如果能确定某个对象会被完全阻挡,那就完全没必要绘制它。这是最重要的性能优化方法之一。对于复杂的自定义view,系统无法检查onDraw方法具体会执行什么操作,这些情况下,底层系统无法识别如何去绘制对象。从而有可能会造成多次绘制。

比如上图的view,只有最上面的牌是可见的,其他牌都被挡住了。这就意味着绘制那些重叠的像素就是浪费资源。

对于这个问题,我们可以使用Canvas类中一些特别的方法帮助Android系统识别被遮挡的不需要绘制的部分:

1、Canvas.clipRect - 指定特定的绘制边界,边界之外的不进行任何绘制。

如果我们知道自定义View可见部分的范围或者遮挡部分的范围,那么我们就可以定义ClipRect边界,避免遮挡区域的任何绘制操作。

PS:在使用Canvas.clipRect()的前后记得要调用Canvas.save()和Canvas.restore()。

2、Canvas.quickReject - 判断给定区域是否完全在剪辑矩形之外。从而跳过那些非矩形区域内的绘制操作。

Layouts, Invalidation And Performance

通常来说,Android需要把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息(包含GPU要绘制的全部对象的信息列表、执行绘制操作的OpenGL命令列表)。

在某个View第一次需要被渲染时,DisplayList会因此而被创建,当这个View要显示到屏幕上时,我们会执行GPU的绘制指令来进行渲染。如果你在后续有执行类似移动这个View的位置等操作而需要再次渲染这个View时,我们就仅仅需要额外操作一次渲染指令就够了。然而如果你修改了View中的某些可见组件,那么之前的DisplayList就无法继续使用了,我们需要回头重新创建一个DisplayList并且重新执行渲染指令并更新到屏幕上。

需要注意的是:任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。举个例子,假设某个Button的大小需要增大到目前的两倍,在增大Button大小之前,需要通过父View重新计算并摆放其他子View的位置。修改View的大小会触发整个ViewHierarcy的重新计算大小的操作。如果是修改View的位置则会触发ViewHierarch重新计算其他View的位置。如果布局很复杂,这就会很容易导致严重的性能问题。

Hierarchy Viewer可以帮助我们快速可视化整个UI结构,并让我们理解这个结构内的独特视图的相对渲染性能。

布局分析工具 - Hierarchy Viewer

Hierarchy Viewer可以帮助我们快速可视化整个UI结构,并让我们理解这个结构内的独特视图的相对渲染性能。我们可以通过它来查看布局、使得布局尽量简单化、扁平化、移除非必要的布局;同时对渲染相对比较慢的view进行分析,减少Measure、Layout的计算时间。

具体来说,

1、分析页面布局结构

查看页面布局

2、分析渲染性能

点击“Venn”按钮(可以多点击几次让资源达到平衡,结果更准确)查看记录每个节点rendering pipeline的过程,每个节点的三个圆点从左到右依次是measure、layout、draw的过程。圆点的颜色表示这个节点相对于所以其他已经概要显示的节点的性能,分别有绿色、黄色、红色。绿色代表执行速度快于至少一半以上的其他视图节点,黄色表示执行速度属于比较慢的50%,红色意味着这是视图层级中最慢的节点。

对于红色节点,我们需要分析它可能存在的问题。显示红色的节点是否和理论上是最慢的节点是否一致。有必要说明的是,这里显示的是相对性能,可能它的绝对性能并不慢,所以我们还需要结合实际数字进行分析。

具体教程可以参考:

Device Setup for Hierarchy Viewer: Device Setup

Hierarchy Viewer Walkthrough

Profiling with Hierarchy Viewer

布局优化

1、尽量扁平化布局。

如果用RelativeLayout和LinearLayout实现一个相同的布局,通过Hierarchy Viewer可以分析得出RelativeLayout的性能是高于LinearLayout的。因为RelativeLayout更加地扁平化。

2、去掉多余layout节点。

我们知道,布局的渲染速度是跟节点数呈正比的,所以我们可以分析布局尝试去掉多余的节点。也可以利用如<merge>等标签来减少我们布局的层次。

总结

本文主要介绍了渲染相关的性能优化。分别介绍了Android系统渲染的底层机制和最主要的两个组件GPU&CPU。

从GPU的角度来说,存在的性能问题是过度绘制Overdraw,可以利用Show GPU Overdraw来分析,解决方案是修改Layout XML,去掉多余或重复的背景。如果是自定义View,可以利用Canvas的ClipRect来限制绘制区域。

从CPU的角度来说,存在的性能问题是布局渲染的效率,可以利用Hierarchy Views工具分析布局和性能,解决方案是修改Layout XML,使得布局层级减少,尽量扁平化。