前面博客分析了导致app卡顿慢的直接原因,这里就从原因出发,分析一些优化方案(这里主要是从直接影响渲染机制的布局相关进行分析)
1)Invalidations,Layouts,andPerformance(动画,布局的优化)
顺滑精妙的动画是app设计里面最重要的元素之一,这些动画能够显著提升用户体验。下面会讲解Android系统是如何处理UI组件的更新操作的。
通常来说,Android需要把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
在某个View第一次需要被渲染时,DisplayList会因此而被创建,当这个View要显示到屏幕上时,我们会执行GPU的绘制指令来进行渲染。如果你在后续有执行类似移动这个View的位置等操作而需要再次渲染这个View时,我们就仅仅需要额外操作一次渲染指令就够了。然而如果你修改了View中的某些可见组件,那么之前的DisplayList就无法继续使用了,我们需要回头重新创建一个DisplayList并且重新执行渲染指令并更新到屏幕上。
需要注意的是:任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。这个流程的表现性能取决于你的View的复杂程度,View的状态变化以及渲染管道的执行性能。举个例子,假设某个Button的大小需要增大到目前的两倍,在增大Button大小之前,需要通过父View重新计算并摆放其他子View的位置。修改View的大小会触发整个HierarcyView的重新计算大小的操作(特别是绘制动画时影响是很严重的)。如果是修改View的位置则会触发HierarchView重新计算其他View的位置。如果布局很复杂,这就会很容易导致严重的性能问题。我们需要尽量减少Overdraw。
我们可以通过前面介绍的MonitorGPURendering来查看渲染的表现性能如何,另外也可以通过开发者选项里面的ShowGPUviewupdates来查看视图更新的操作,最后我们还可以通过HierarchyViewer这个工具来查看布局,使得布局尽量扁平化(层数尽量减少),移除非必需的UI组件,这些操作能够减少Measure,Layout的计算时间,制作动画时,运动元素与其父控件的关系要好好考虑,尽量在运动元素运动过程中,不会导致其父控件变化,否则就会导致父控件重绘,整个重绘可能影响到整个布局文件,这就是你做出的动画卡顿慢的可能原因之一。
2)Overdraw,Cliprect,QuickReject(过度绘制)
引起性能问题的一个很重要的方面是因为过多复杂的绘制操作。我们可以通过工具来检测并修复标准UI组件的Overdraw问题,但是针对高度自定义的UI组件则显得有些力不从心。
有一个窍门是我们可以通过执行几个APIs方法来显著提升绘制操作的性能。前面有提到过,非可见的UI组件进行绘制更新会导致Overdraw。例如NavDrawer从前置可见的Activity滑出之后,如果还继续绘制那些在NavDrawer里面不可见的UI组件,这就导致了Overdraw。为了解决这个问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少Overdraw。那些NavDrawer里面不可见的View就不会被执行浪费资源(对于不需要用户看见的元素,设置属性为gone,这可以在一定程度上优化性能,这时与设置成invisable相比较的)。
但是不幸的是,对于那些过于复杂的自定义的View(重写了onDraw方法),Android系统无法检测具体在onDraw里面会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。
除了clipRect方法之外,我们还可以使用canvas.quickreject()来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。做了那些优化之后,我们可以通过上面介绍的ShowGPUOverdraw来查看效果。
3)CustomViewsandPerformance(自定义视图的优化)
Android系统有提供超过70多种标准的View,例如TextView,ImageView,Button等等。在某些时候,这些标准的View无法满足我们的需要,那么就需要我们自己来实现一个View,这节会介绍如何优化自定义View的性能。
通常来说,针对自定义View,我们可能犯下面三个错误:
·UselesscallstoonDraw():我们知道调用View.invalidate()会触发View的重绘,有两个原则需要遵守,第1个是仅仅在View的内容发生改变的时候才去触发invalidate方法,第2个是尽量使用ClipRect等方法来提高绘制的性能。
·Uselesspixels:减少绘制时不必要的绘制元素,对于那些不可见的元素,我们需要尽量避免重绘。
·WastedCPUcycles:对于不在屏幕上的元素,可以使用Canvas.quickReject把他们给剔除,避免浪费CPU资源。另外尽量使用GPU来进行UI的渲染,这样能够极大的提高程序的整体表现性能。
最后请时刻牢记,尽量提高View的绘制性能,这样才能保证界面的刷新帧率尽量的高。
4)HiddenCostofTransparency(透明效果的隐藏性能消耗)
这小节会介绍如何减少透明区域对性能的影响。通常来说,对于不透明的View,显示它只需要渲染一次即可,可是如果这个View设置了alpha值,会至少需要渲染两次。原因是包含alpha的view需要事先知道混合View的下一层元素是什么,然后再结合上层的View进行Blend混色处理。
在某些情况下,一个包含alpha的View有可能会触发改View在HierarchyView上的父View都被额外重绘一次。下面我们看一个例子,下图演示的ListView中的图片与二级标题都有设置透明度。
大多数情况下,屏幕上的元素都是由后向前进行渲染的。在上面的图示中,会先渲染背景图(蓝,绿,红),然后渲染人物头像图。如果后渲染的元素有设置alpha值,那么这个元素就会和屏幕上已经渲染好的元素做blend处理。很多时候,我们会给整个View设置alpha的来达到fading的动画效果,如果我们图示中的ListView做alpha逐渐减小的处理,我们可以看到ListView上的TextView等等组件会逐渐融合到背景色上。但是在这个过程中,我们无法观察到它其实已经触发了额外的绘制任务,我们的目标是让整个View逐渐透明,可是期间ListView在不停的做Blending的操作(由于listview的适配器中getview会频繁调用,除了listview会这样,还包括grideview也会),这样会导致不少性能问题。
如何渲染才能够得到我们想要的效果呢?我们可以先按照通常的方式把View上的元素按照从后到前的方式绘制出来,但是不直接显示到屏幕上,而是使用GPU预处理之后,再又GPU渲染到屏幕上,GPU可以对界面上的原始数据直接做旋转,设置透明度等等操作。使用GPU进行渲染,虽然第一次操作相比起直接绘制到屏幕上更加耗时,可是一旦原始纹理数据生成之后,接下去的操作就比较省时省力(其实是将cpu的事落到gpu上去做,并且由于gpu的纹理机制让整体性能提升)。
如何才能够让GPU来渲染某个View呢?我们可以通过setLayerType的方法来指定View应该如何进行渲染,从SDK16开始,我们还可以使用ViewPropertyAnimator.alpha().withLayer()来指定。如下图所示:
另外一个例子是包含阴影区域的View,这种类型的View并不会出现我们前面提到的问题,因为他们并不存在层叠的关系。
为了能够让渲染器知道这种情况,避免为这种View占用额外的GPU内存空间,我们可以做下面的设置。
通过上面的设置以后,性能可以得到显著的提升,如下图所示:
总结一下:直接和渲染相关的就是布局文件,为了达到程序渲染最优,有几个原则1.布局层数尽量少(扁平化)。2,尽量避免过度渲染。3.尽量简化布局,4,经常变化的view的布局深度尽量低(每一次变化都会涉及上层及上上层控件的变化)。这是直接和渲染机制相关的布局,渲染还提出了要求,每一个功能相对单一的模块的执行时间尽量接近16ms(这时帧率为60fps),最多为32ms,若再多就会影响用户体验了,其实16ms是一帧不落的绘制了,从16ms到32ms这段时间丢帧了,但是没影响用户体验,但是大于32ms就很影响了,卡顿慢就出现了。至于这么缩短每个模块的时间,这个就得具体模块的来定了。这里的16ms和32ms是主线程的时间,所以一般耗时较大(例如网络请求,文件操作,以及数据库操作这些典型功能就算再优化,也很难将时间压缩到16ms),这种情况就可以使用额外的线程来处理这些任务栏了,有人可能就有疑问了,既然线程可以解决问题,那我就不用优化了,直接使用线程就好了,这种说法其实是有问题的,因为线程管理还是得占用cpu时间的,如果线程数很多,cpu管理的时间就会变多,值得注意的是渲染机制的第一步解析xml布局文件(测量绘制DisplayList)的工作还得cpu做呢,所以可能会出现一种情况就是把耗时任务都放在新线程里了,但是依旧还是卡顿慢。所以一般是优化后距离16ms这个标准还是相差很远的情况才使用新线程。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.e1idc.net