Android应用性能优化实践

原文连接 : Speed up your app
原文做者 : UDI COHEN
译文出自 : 开发技术前线 www.devtf.cn。未经容许,不得转载!
译者 : zijianwang90
校对者:
状态 : 完成html

几周以前,我在Droidcon NYC上有过一次关于Android性能优化的演讲。java

我在这个演讲中花费了大量的时间,由于我想经过真实的例子展示性能问题,以及我是经过什么样的工具去发掘这些问题的。由于时间缘由,在演讲中我不得不舍弃一半的内容。在这篇文章中,我会总结在演讲中我所讨论的全部内容,而且给出实例android

点此连接进入演讲视频git

如今,咱们来逐一讨论我在演讲中说起的一些重点内容,但愿个人阐述足够的清晰。首先,在我进行性能优化的时候我遵循以下原则:github

原则

每当我遇到性能问题,或者尝试发现性能问题的时候,我会遵循以下原则:web

  • 坚持性能测试 – 不要用你的眼睛去优化性能。也许在你盯着同一个动画看了几回以后,你会开始相信他运行的愈来愈流畅了。数据不会说谎。在你优化你的代码以前以及以后,使用咱们将要介绍的一系列工具,去屡次的测试你的app到底性能几何。
  • 使用低端设备 – 若是你想要你想暴露你应用的性能问题,低端设备每每会更加的容易。性能强大的设备每每不会太在乎你应用上面的一些优化问题,且不是全部用户都在使用这些旗舰设备。
  • 权衡 – 性能的优化始终围绕着权衡这两个字。你在某一个点上的优化可能会形成另外一点上出现问题。在不少状况下,你会花大量的时间寻找并解决这些问题,但形成这些问 题的缘由也可能使由于例如bitmaps的质量,或是你没有使用正确的数据结构去存储你的数据。因此你要时刻准备好做出必定的牺牲

Systrace

Systrace是一个很是好但却有可能被你忽视的工具,这是由于开发者们每每不肯定Systrace可以为他们提供什么样的信息。shell

Systrace会展现一个运行在手机上程序情况的概览。这个工具提醒了咱们手机实际上是一个能够在同一时间完成不少工做的电脑。在最近的一次SDK更新中,这个工具在数据分析能力上获得了提高,用以帮助咱们寻找性能问题之所在。缓存

下面让咱们来看看Systrace长什么样子:安全

systrace-overview

你能够经过Android Device Monitor Tool或者是命令行来生成Systrace文件,想了解更多猛戳此处性能优化

在视频中,我向你们介绍了Systrace中不一样区域的功能。固然最有趣的仍是Alerts和Frames两栏,它们展现了经过手机来的数据而生成出来的可视化分析结果。让咱们来选择最上方的alerts瞧瞧:

systrace-alert

这个警告指出了,有一个View#draw()方法执行了比较长的时间。咱们能够在下面看到问题的描述,连接,甚至是相关的视频。下面咱们看 Frames这一行,能够看到这里展现了被绘制出来的每一帧,而且用绿、黄、红三颜色来区分它们在绘制时的性能。咱们选一个红色帧来瞅瞅:

systrace-frame

在最下方,咱们看到了与这一帧所相关的一些警告。在这三个警告中,有一个是咱们上面所提到的(View#draw())。接下来咱们在这一帧处放大并在下方展开“Inflation during ListView recycling”这条警告:

systrace-frame-zoomin

咱们能够看到警告部分的总耗时,32毫秒,远高于了咱们对保障60fps所需的16毫秒绘制时间。同时还有更多的ListView每一个条目的绘制时 间,大约是6毫秒每一个条目,总共五个。而Description描述项中的内容会帮助咱们理解问题,甚至提供问题的解决方案。回到咱们上一张图片,咱们可 以在“inflate”这一个块区处放大,而且观察究竟是哪些View在被填充过程当中耗时比较严重。

下面是另一个渲染过慢的实例:

systrace-2-frame

在选择了某一帧以后,咱们能够按“m”键来高亮这一帧,而且在上方看到了这一部分的耗时,如图,咱们看到了这一阵的绘制总共耗时超过19毫秒。而当咱们展开这一帧惟一的一个警告时,咱们发现了“Scheduling delay”这条错误。

Scheduling delay(调度延迟)的意思就是一个线程在处理一块运算的时候,在很长一段时间都没有被分配到CPU上面作运算,从而致使这个线程在很长一段时间都没有完成工做。咱们选择这一帧中最长的一块,从而获得更加详细的信息:

systrace-2-slice

在红框区域内,咱们看到了“Wall duration”,他表明着这一区块的开始到结束的耗时。之因此叫做“Wall duration”,是由于他就像是墙上的一个时钟,从线程的一开始就为你计时。

可是,CPU Duration一项中显示了实际CPU在处理这一区块所消耗的时间。

很显然,两个时间的差距仍是很是大的。整个区块耗时18毫秒,而在这之中CPU只消耗了4毫秒的时间去运算。这就有点奇怪了,因此咱们应该看一下在这整个过程之中,CPU去干嘛了。

systrace-2-cpu

能够看到,全部四个线程都很是的繁忙。

选择其中的一个线程会告诉咱们是哪一个程序在占用他,在这里是一个包名为com.udinic.keepbusyapp的程序。在这里,因为另一个程序占用CPU,致使了咱们的程序未能得到足够的CPU资源。

可是这种状况实际上是暂时的,由于被其余后台应用占用CPU的状况并很少见(- -),但仍有其余应用的线程或是主线程占用CPU。而Traceview也只能为咱们提供一个概览,他的深度是有限的。因此要找到咱们app中究竟是什么 让咱们的CPU繁忙,咱们还要借助另外一个工具——Traceview。

Traceview

Traceview是一个性能测试工具,展现了全部方法的的运行时间。下面让咱们来瞅瞅他是啥样的:

traceview-overview

这个工具能够从Android Device Monitor中打开也能够经过代码打开。更多的消息信息清看这里

下面让咱们来看看每一列的含义:

  • Name – 方法名,以及他们在上面图表中所对应的颜色。
  • Inclusive CPU Time – CPU在处理这个方法以及全部子方法(如被他调用的全部方法)的总耗时。
  • Exclusive CPU Time – CPU在处理这一个单独方法的总耗时。
  • Inclusive/Exlusive Real Time – 从方法的开始执行到执行结束的总耗时,和Systrace中的“Wall duration”相似
  • Calls+Recursion – 这个方法被调用的次数,以及被递归调用的次数。
  • CPU/Real time per Call – 在处理这个方法时的CPU耗时的平均值以及实际耗时的平均值。另外的列展现了这个方法全部调用的累计耗时

我打开一个滑动不太顺滑的应用。开启记录,滑动一点后中止记录。展开getView()方法,以下图:

traceview-getview

这个方法被调用了12次,每次CPU会消耗3毫秒左右,可是每次调用的总耗时却高达162毫秒!绝对有问题啊!

而看看这个方法的children,咱们能够看到这其中的每一个方法在耗时方面是如何分布的。Thread.join()方法战局了98%的 inclusive real time。这个方法在等待另外一个线程结束的时候被调用。在Children中另一个方法就是Tread.start()方法,而之因此整个方法耗时很 长,我猜想是由于在getView()方法中启动了线程而且在等待它的结束。

可是这个线程在哪儿?

咱们在getView()方法中并不能看到这个线程作了什么,由于这段逻辑不在getView()方法之中。因而我找到了Thread.run()方法,就是在线程被建立出来时候所运行的方法。而跟随这个方法一路向下,我找到了问题的元凶。

traceview-thread

我发现了BgService.doWork()方法的每次调用花费了将近14毫秒,而且有四十个这东西!并且getView()中还有可能调用屡次 这个方法,这就解释了为何getView()方法执行时间如此之长。这个方法让CPU长时间的保持在了繁忙状态。而看看Exclusive CPU time,咱们能够看到他占据了80%的CPU时间!此外,根据Exclusive CPU time排序,能够帮咱们更好的定位那些耗时很长的方法,而他们颇有可能就是形成性能问题的罪魁祸首。

关注这些耗时方法,例如getView(),View#onDraw()等方法,能够很好的帮助咱们寻找为何应用运行缓慢的缘由。但有些时候,还 会有一些其余的东西来占用宝贵的CPU资源,而这些资源若是被运用在UI的绘制上,也许咱们的应用会更加流畅。Garbage Collector垃圾回收机制会不时的运行,回收那些没用的对象,一般来说这不会影响咱们在前台运行的程序。但若是GC被运行的过于频繁,他一样能够影 响咱们应用的执行效率。而咱们该如何知道回收的是否过于频繁了呢…

内存调优 Memory Profiling

Android Studio在最近的更新中给予了咱们更增强大的工具去分析性能问题。在底部Android选项中的Memory选项卡,会显示有多大的数据在何时被分配到了堆内存之中,他是长成这个样子的:

mem-graph

而当图表中出现一个小的下滑的时候,说明GC回收发生了,他清除了没必要要的对象而且腾出了必定的堆空间。而在这张图表的左侧有两个工具供咱们使用,Head dump和Allocation Tracker。

Heap dump

为了找出究竟是什么正在占用咱们的堆内存,咱们可使用左边的heap dump按钮。他会提供一个堆内存占用状况的快照,而且会在Android Studio中打开一个单独的报告界面。

heap-overview

在左侧,咱们看到一个图标展现了堆中全部的实例,按照类进行分组。而对于每个实例,会展现有多少个实例的对象被分配到堆中,以及他们的所占用的空 间(Shallow size浅尺寸),以及这些对象在内存中仍然占用的空间,后者告诉了咱们多少的内存空间将会被释放若是这些实例被释放。这个工具可让咱们直观的观察处内 存是被如何占用的,帮助咱们分析咱们使用的数据结构和对象之间的关系,以便发现问题并使用更加高效的数据结构,解开和对象之间的关联,而且下降 Ratained Memory的占用。而最终目的,就是尽量的下降咱们的内存占用。

回过头来看图表,咱们发现MemoryActivity存在39个实例,这对于一个Activity来讲有点奇怪。在右边选择其中的一个实例,会在下方看到全部的对这个实例的引用树状列表。

heap-reftree

其中一个是ListenersManager对象中的一个集合。而观察这个activity的其余实例,就会他们都由于这个对象而被保留在了内存之中。这也解释了为何这些对象占用了如此多的内存:

heap-retained

这个现象就叫作“内存泄露”,咱们的activity已经被销毁,可是他们的对象却由于始终被引用着而没法被垃圾回收。咱们能够避免这种状况,例如 确保这些对象再被销毁后不会被其余对象一直引用着。在咱们这个例子中,在Activity被销毁后,ListernesManager并不须要保持着对这 些对象的引用。因此解决办法就是在onDestroy()回调方法中移除这些引用。

内存泄露以及其余较大的对象会在堆中占据不少的控件,它们减小着可用内存的同时也频繁的形成垃圾回收。而垃圾回收又回形成CPU的繁忙,而堆内存并不会变得更大,最终就会致使更悲剧的结果发生:OutOfMemoryException内存溢出,并致使程序崩溃。

另一个更先进的工具就是Eclipse Memory Analyzer Tool (Eclipse MAT):

eclipse-mat

这个工具能够作全部Android Studio能够作的,而且辨别可能出现的内存泄露,以及提供更加高级的搜索功能,例如搜索全部大于2MB的Bitmap实例,或是搜索全部空的Rect对象

另一个很好的工具是LeakCanary,是一个第三方库,能够观察应用中的对象而且确保它们没有形成泄漏。而若是形成泄漏了,会有一个推送来提醒你在哪里发生了什么。

leakcanary

Allocation Tracker

咱们能够在内存图表的左侧找到Allocation Tracker的启动和中止按钮。他会生成一个在必定时间内被生成的全部实例的报告,而且按照类分型分组:

alloc-class

或者按照方法分组:

alloc-method

同时它还能经过美观的可视化界面,告诉咱们哪些方法或类拥有最多的实例。

利用这些信息,咱们能够找到哪些占用过多内存,引起过屡次垃圾回收且又对耗时很是敏感的方法。咱们也能够利用这个工具找到不少短命的相同类的实例,从而能够考虑使用对象池的思想去尽可能的减小过多的实例被建立。

常见内存小技巧

如下是一些我写代码时候遵循的规律或是技巧:

  • 枚举在性能问题上一直是一个被常常讨论的话题。这里是一个讨论枚举的视频,指出了枚举所消耗的内存空间,这还有一段关于这个视频的讨论,固然其中存在着一些误导。可是回过头来,枚举真的比通常的常量更加占用空间吗?确定的。可是这必定很差吗?未必。若是你在编写一个library库而且须要很强的类型安全性,那么也许可使用枚举而非其余办法,例如@IntDef。而若是你只是有一堆的常量,使用枚举也许就不能么明智了。仍是那句话,在你作决定以前必定要权衡与取舍。
  • 自动装箱 – 自动装箱是一个从原始数据类型到对象型数据的装箱过程(例如int到Integer)。每当一个原始类型数据被装箱到一个对象类型数据,一个新的对象就产 生了(震惊吧。。)。因此若是发生了不少次的自动装箱,势必会加快GC的执行频率,并且自动装箱是很容易被咱们忽视的。而解决办法,在使用这些类型的时候 尽可能一致,若是你在应用中彻底使用原始数据类型,那么尽可能避免他被平白无故的自动封装。你可使用咱们上面提到的memery profiling工具去寻找这些过于大量的对象类型数据,也能够经过Traceview去寻找相似 Integer.valueOf(),Long.valueOf()这样的方法来判断是否发生了大量没必要要的自动封装。
  • HashMap vs ArrayMap / Sparse*Array – 既然提到了自动装箱的问题,那么使用HashMap的话,就须要咱们使用对象类型做为键。而若是咱们在整个应用中使用的都是基本数据类型的“int”,那 么在咱们使用HashMap时候就会发生自动装箱,而这时也许咱们就能够考虑使用SparseIntArray。而假如咱们仍然须要键为对象类型,那么我 们可使用ArrayMap。ArrayMap和HashMap很相似,可是在底层的实现原理却不尽相同, 这也会让咱们更加高效的使用内存,但要付出必定的性能代价。两种方法都会比HashMap更加节省内存空间,可是相比于HashMap,查询和增删的速度 上会有必定的牺牲。固然,除非你具备至少1000条的数据源,不然在运行时也不会对速度形成太大的影响,这也是你使用他们替代HashMap的缘由之一。
  • 注意Context – 在咱们前面也看到了,Activity是很是容易形成内存泄露的。在Android中,最容易形成内存泄露的当属Activity。而且这些内存泄露会浪 费大量的内存,由于他们持有着他们UI中全部的View,而这些View一般会占据不少的控件。在开发过程当中的不少操做须要Context,而我一般也会 使用Activity来传递。因此必定要搞清楚你对这个Activity作了什么。若是一个引用被缓存起来了,且这个对象的生命周期比你的 Activity还要长,那么在咱们解除这个引用以前,就会形成内存泄露了。
  • 避免非静态 – 当咱们建立非静态内部类,而且初始化它的时候,在其内部会建立一个外部类的隐式引用。而若是内部类的生命周期比外部类还要长,那么外部类也一样会被保留在 内存之中,尽管咱们已经彻底不须要它了。例如,在Activity内建立了一个继承自AsyncTask的内部类,完后在Activity运行的时候启动 这个async task,再杀掉Activty。那么这时候这个async task会保持着这个Activity直到执行结束。而解决办法也很简单,不要这么作,尽可能使用静态内部类。

监测GPU(GPU Profiling)

在Android 1.4中的一个全新工具,就是能够查看GPU绘制。

gpu-overview

每一条线意味着一帧被绘制出来,而每条线中的不一样颜色又表明着在绘制过程当中的不一样阶段:

  • Draw (蓝色) 表明着View#onDraw()方法。在这个环节会建立/刷新DisplayList中的对象,这些对象在后面会被转换成GPU能够明白的OpenGL 命令。而这个值比较高多是由于view比较复杂,须要更多的时间去建立他们的display list,或者是由于有太多的view在很短的时间内被建立。
  • Prepare (紫色) – 在Lollipop版本中,一个新的线程被加入到了UI线程中来帮助UI的绘制。这个线程叫做RenderThread。它负责转换display list到OpenGL命令而且送至GPU。在这过程当中,UI线程能够继续开始处理后面的帧。而在UI线程将全部资源传递给RenderThread过程 中所消耗的时间,就是紫色阶段所消耗的时间。若是在这过程当中有不少的资源都要进行传递,display list会变得过多过于沉重,从而致使在这一阶段过长的耗时。
  • Process (红色) – 执行Display list中的内容并建立OpenGL命令。若是有过多或者过于复杂的display list须要执行的话,那么这阶段会消耗较长的时间,由于这样的话会有不少的view被重绘。而重绘每每发生在界面的刷新或是被移动出了被覆盖的区域。
  • Execute (黄色) – 发送OpenGL命令到GPU。这个阶段是一个阻塞调用,由于CPU在这里只会发送一个含有一些OpenGL命令的缓冲区给GPU,而且等待GPU返回空 的缓冲区以便再次传递下一帧的OpenGL命令。而这些缓冲区的总量是必定的,若是GPU太过于繁忙,那么CPU则会去等待下一个空缓冲区。因此,若是我 们看到这一阶段耗时比较长,那多是由于GPU过于繁忙的绘制UI,而形成这个的缘由则多是在短期内绘制了过于复杂的view。

在Marshmallow版本中,有更多的颜色被加了进来,例如Measure/Layout阶段,input handling输入处理,以及一些其余的:

gpu-colors-marsh

在使用这些功能以前,你须要在开发者选项中开启GPU rendering(GPU呈现模式分析):

gpu-settings2

接下来咱们就能够经过如下这条adb命令获得咱们想要获得的全部信息:

咱们能够本身收集这些信息并建立图表。这个命令也会打印出一些其余有用的信息,例如view层级中的层数,display lists的大小等等。在Marshmallow中,咱们也会获得更多的信息:

gpu-adb

若是咱们须要自动化测试咱们的app,那么咱们能够本身建立服务器去运行在特定节点执行这些命令(如列表滚动,重度动画等),并观察这些数值的变 动。这能够帮助咱们找出在哪里出现了性能的降低,而且产品上线以前找到问题的所在。咱们也可以经过”framestats”关键字来找到更多更加精确的数 据,这里有更详尽的解释

但这可不是获取GPU Rendering数据的惟一方式!

咱们在开发者选项中看过了GPU呈现模式分析内的Profile GPU Rendering”选项后,还有另一个选项就是”On screen as bars”(在屏幕上显示为条形图)。打开这个后,咱们就能够直观的看到每一帧在绘制过程当中所消耗的时间,绿色的横线则表明16ms的60fps零界值。

gpu-onscreen

在右边的例子中,咱们能够看到不少帧都超出了绿线,这也意味着它花了多余16毫秒的时间去绘制。而蓝色占据了这些线条的主体,咱们知道这多是由于 过多或是过于复杂的view在被绘制。在这种状况下,当我滑动列表,由于列表中view的结构比较复杂,有一些view已经被绘制完成而一些由于过于复杂 还处于绘制阶段,而这可能就是形成这些帧超过绿线的缘由——绘制起来实在太复杂了。

Hierarchy Viewer

我很是喜欢这个工具,同时也由于那么多人彻底不用而感到一丝的悲凉。

使用Hierarchy Viewer,咱们能够得到性能数据,观察View层级中的每个View,而且能够看到全部View的属性。咱们一样能够导出theme数据,这样能够 看到每个style中的属性值,可是咱们只能在单独运行Hierarchy Viewer的时候才能这么干,而非经过Android Monitor。一般在我进行布局设计以及布局优化的时候,我会使用到这个工具。

hierview-overview

在正中间咱们看到的树状结构就表明了View的层级。View的层级能够很宽,但若是太宽的话(10层左右),也许会在布局和测量阶段消耗大量的性 能。在每一次View经过View#onMeasure()方法测量的时候,或是经过View#onLayout()方法布局他的全部子view的时候, 这些方法又回传递到它全部的子view上面而且重头来过。有的布局会将上述步骤作两次,例如RelativeLayout以及某些经过配置的 LinearLayout,而若是它们又层层嵌套,那么这些方法的传递会大量的增长。

在右下方,咱们看到了一个咱们布局的“蓝图”,标注了每个view的位置。当咱们点击这里(或者从树状结构中),咱们会在左侧看到他全部的属性。 在设计布局时候,有时候我不肯定为何一个view被摆在那里,而使用这个工具,我能够在树状图中找到这个view,选择,并观察他在预览窗口中的位置。 我还经过view在屏幕上最终的绘制尺寸,来设计有趣的动画,而且使用这些信息让动画或者View的位置更加的精准。我也能够经过这个工具来寻找被其余 View不当心盖住从而找不到的View,等等等等。

hierview-colors

对于每个view咱们能够得到他测量、布局以及绘制的用是和它所包含的全部子view。在这里颜色表明了这个view在绘制过程当中,相比树中其余 view的性能表现,这是咱们找到这些性能不足view的最佳途径。鉴于咱们可以看到全部view的预览,咱们能够沿着树状图,跟随view被建立的顺 序,找寻那些能够被舍弃的多余步骤。而其中之一,也是对性能影响很是大的,就是过分绘制。

过分绘制

正如咱们在GPU Profiling部分看到的,在Execute黄色阶段,若是GPU有过多的东西要在屏幕上绘制,整个阶段会消耗更多的时间,同事也增长了每一帧所消耗 的时间。过分绘制每每发生在咱们须要在一个东西上面绘制另一个东西,例如在一个红色的背景上画一个黄色的按钮。那么GPU就须要先画出红色背景,再在他 上面绘制黄色按钮,此时过分绘制就是不可避免的了。若是咱们有太多层须要绘制,那么则会过分的占用GPU致使咱们每帧消耗的时间超过16毫秒。

overdraw-gif

使用“Debug GPU Overdraw”(调试过分绘制)功能,全部的过分绘制会以不一样颜色的形式展现在屏幕上。1x或是2x的过分绘制没啥问题,即使是一小块浅红色区域也不算太坏,但若是咱们看到太多的红色区域在屏幕上,那可能就有问题了,让咱们来看几个例子:

overdraw-examples

在左边的例子中,咱们看到列表部分是绿色的,一般还OK,可是在上方发生覆盖的区域是一片红色,那就有问题了。在右边的例子中,整个列表都是浅红 色。在两个例子中,都各有一个不透明的列表存在2x或3x的过分绘制。这些过分绘制可能发生在咱们给Activity或Fragment设置了全屏的背 景,同时又给ListView以及ListView的条目设置了背景色。而经过只设置一次背景色便可解决这样的问题。

注意:默认的主题会为你指定一个默认的全屏背景色,若是你的activity又一个不透平的背景盖住了默认的背景色,那么你能够移除主题默认的背景 色,这样也会移除一层的过分绘制。这能够经过配置主题配置或是经过代码的方法,在onCreate()方法中调用 getWindow().setBackgroundDrawable(null)方法来实现。

而使用Hierarchy Viewer,你能够导出一个全部view层级的PSD文件,在Photoshop中打开,而且调查不一样的layout以及不一样的层级,也可以发现一些在 布局中存在的过分绘制。而使用这些信息能够移除没必要要的过分绘制。并且,不要看到绿色就知足了,冲着蓝色去!

透明度

使用透明度可能会影响性能,可是要去理解为何,让咱们瞅瞅当咱们给view设置透明度的时候到底发生了什么。咱们来看一下下面这个布局:

alpha-before

咱们看到这个layout中又三个ImageView而且重叠摆放。在使用最常规的设置透明度的方法setAlpha()时,方法会传递到没一个子 view上面,在这里是每个ImageView。然后,这些ImageView会携带新的透明值被绘制入帧缓冲区。而结果就是:

alpha-direct

这并非咱们想要看到的结果。

由于每个ImageView都被赋予了一个透明值,致使了本应覆盖的部分被融合在一块儿。幸运的是,系统为咱们解决了这个问题。布局会被复制到一个非屏幕区域缓冲区中,而且以一个总体来接收透明度,其结果再被复制到帧缓冲区。结果就是:

alpha-complex

可是,咱们是要付出性能上面的代价的。

假如在帧缓冲区内绘制以前,还要在off-screen缓冲区中绘制一遍的话,至关于增长了一层不可见的绘制层。而系统并不知道咱们是但愿这个透明 度以何种的形式去展示,因此系统一般会采用相对复杂的一种。可是也有不少设置透明度的方法可以避免在off-screen缓冲区中的复杂操做:

  • TextView – 使用setTextColor()方法替代setAlpha()。这种方法使用Alpha通道来改变字色,字也会直接使用它进行绘制。
  • ImageView – 使用setImageAlpha()方法替代setAlpha()。原理同上。
  • 自定义控件 – 若是你的自定义控件并不支持相互覆盖,那就无所谓了。全部的子view并不会想上面的例子中同样,由于覆盖而相互融合。而经过复写 hasOverlappingRendering()并将其返回false后,便会通知系统使用最直接的方式绘制view。同时咱们也能够经过复写 onSetAlpha()返回true来手动操控设置透明度后的逻辑。

硬件加速

在Honeycomb版本中引入了硬件加速(Hardware Accleration)后,咱们的应用在绘制的时候就有了全新的绘制模型。它引入了DisplayList结构,用来记录View的绘制命令,以便更快的进行渲染。但还有一些很好的功能开发者们每每会忽略或者使用不当——View layers。

使用View layers(硬件层),咱们能够将view渲染入一个非屏幕区域缓冲区(off-screen buffer,前面透明度部分提到过),而且根据咱们的需求来操控它。这个功能主要是针对动画,由于它能让复杂的动画效果更加的流畅。而不使用硬件层的 话,View会在动画属性(例如coordinate, scale, alpha值等)改变以后进行一次刷新。而对于相对复杂的view,这一次刷新又会连带它全部的子view进行刷新,并各自从新绘制,至关的耗费性能。使 用View layers,经过调用硬件层,GPU直接为咱们的view建立一个结构,而且不会形成view的刷新。而咱们能够在避免刷新的状况下对这个结构进行进行 不少种的操做,例如x/y位置变换,旋转,透明度等等。总之,这意味着咱们能够对一个让一个复杂view执行动画的同时,又不会刷新!这会让动画看起来更 加的流畅。下面这段代码咱们该如何操做:

很简单,对吧?

是的,可是再使用硬件layers的时候仍是有几点要牢记在心:

  • 回收 – 硬件层会占用GPU中的一块内存。只在必要的时候使用他们,好比动画,而且过后注意回收。例如在上面ObjectAnimator的例子中,咱们增长了一 个动画结束监听以便在动画结束后能够移除硬件层。而在Property animator的例子中,咱们使用了withLayers(),这会在动画开始时候自动建立硬件层而且在结束的时候自动移除。
  • 若是你在调用了硬件View layers后改变了View,那么会形成硬件硬件层的刷新而且再次重头渲染一 遍view到非屏幕区域缓存中。这种状况一般发生在咱们使用了硬件层暂时还不支持的属性(目前为止,硬件层只针对如下几种属性作了优化:otation、 scale、x/y、translation、pivot和alpha)。例如,若是你另外一个view执行动画,而且使用硬件层,在屏幕滑动他们的同时改 变他的背景颜色,这就会形成硬件层的持续刷新。而以硬件层的持续刷新所形成的性能消耗来讲,可能让它在这里的使用变得并不那么值。

而对于第二个问题,咱们也有一个可视化的办法来观察硬件层更新。使用开发者选项中的“Show hardware layers updates”(显示硬件层更新)

hwl-devoptions2

当打开该选项后,View会在硬件层刷新的时候闪烁绿色。在好久之前我有一个ViewPager在滑动的时候有点不流畅。在开发者模式启动这个选项后,我再次滑动ViewPager,发现了以下状况:

hwl-calproblem

左右两页在滑动的时候彻底变成了绿色!

这意味着他们在建立的时候使用了硬件层,并且在滑动的时候也界面也进行了刷新。而当我在背景上面使用时差效果而且让条目有一个动画效果的时候,这些 处理确实会让它进行刷新,可是我并无对ViewPager启动硬件层。在阅读了ViewPager的源码后,我发现了在滑动的时候会自动为左右两页启动 一个硬件层,而且在滑动结束后移除掉。

在两页间滑动的时候建立硬件层也是能够理解的,但对我来讲小有不幸。一般来说加入硬件层是为了让ViewPager的滑动更加流畅,毕竟它们相对复杂。但这不是个人app所想要的,我不得不经过一些编码来移除硬件层。

硬件层其实并非什么酷炫的东西。重要的是咱们要理解他的原理而且合理的使用他们,要否则你确实会遇到一些麻烦。

DIY

在准备上述这一系列例子的过程当中,我进行了不少的编码去模拟这些情景。你能够在这个Github项目中找到这些代码,同时也能够在Google Play中找到。我用不一样的Activity区分了不一样的情景,而且尽可能将他们的用文档解释清楚,以便于帮助你们理解不一样的Activity中是出现哪一种问题。你们能够边阅读各个Activity的javadoc的同时,利用咱们前面讲到的工具去玩儿这个App。

更多信息

随着安卓系统的不断进化,你有话你的应用的手段也在不断变多。不少全新的工具被引入到了SDK中,以及一些新的特性被加入到了系统中(比如硬件层这东西)。因此与时俱进和懂得取舍是很是重要的。

这是一个很是棒的油管播放列表,叫Android Performance Patterns,一些谷歌出品的短视频,讲解了不少与性能相关的话题。你能够找到不一样数据结构之间的对比(HashMap vs ArrayMap),Bitmap的优化,网络优化等等,吐血推荐!

加入Android Performance Patterns的G+社群,和你们一块儿讨论,分享心得,提出问题!

更多有意思的连接:

  • 了解安卓中的图形结构(Graphics Architecture)。例如关于UI的渲染,不一样的系统组件,好比SurfaceFlinger,以及他们之间是如何交互的。比较长,可是值得一看!
  • Google IO 2012上的一段演讲,展现了绘制模型(Drawing model)是如何工做的。
  • 一段来自Devoxx 2013的关于Anrdroid性能的研讨,展现了一些在Anrdroid 4.4对绘制模型的一些优化,而且经过demo的形式展现了对不一样优化工具的使用(Systrace,Overdraw等等)。
  • 一篇很是好的关于“预防性优化”(Preventative Optimizations)的文章,阐述了他和“不成熟的优化”有和区别。不少的开发者并不优化他们的代码,由于他们认为这些影响并不明显。可是记住, 问题也是聚沙成塔的。若是你有机会去优化很小的一点,即使是很是微不足道的一点,也是应该的。
  • 安卓中的内存管理 – 一个2011年的Google IO视频,仍然值得一看。视频展现了安卓是如何管理不一样app的内存的,以及如何使用Eclipse MAT去发现问题。
  • 一个叫作Romain Guy的谷歌工程师的案例研究,经过优化一个第三方的推特客户端。在这个研究中,Romain展现了他是如何发现问题的,而且建议了相应的解决方案。另外一篇文章跟进了这个问题,展现了这个app在从新制做后的一些其余问题。

我真心但愿你经过这篇文章得到到了足够丰富的信息和信心,从今天开始优化你的应用吧!

尝试用工具去记录,并经过一些开发者选项中的选项,开搞吧。欢迎来G+上分享你在安卓性能优化上面的心得!

相关文章
相关标签/搜索