Android应用性能优化之优化列表头像过度绘制[一]
操作的是否顺畅、卡顿,决定着整体的流畅程度。
事实上android跟iphone的差别,个人觉得很大程度上决定于流畅程度,无论是动画,还是列表滑动等相关操作,流畅与否,对于用户而言,虽然他们说不出来哪里不对,但是卡与不卡的反馈却是很直接的。
Google也设法想改变这局面。在4.0之后的应用(最低版本4.0)默认开启硬件加速,并且4.1新加了一个Project Butter(黄油计划),设法将渲染帧速提升到60fps。
虽然能看到android的进步,但在实际中,并没有特别的变化,原因很简单。一方面android在3.0之后才开始支持GPU渲染。另外一方面,也是想做此系列的重要原因,应用的顺畅不能仅靠系统去努力,开发者在代码中给予力量才是最重要的。
坐看市面上应用,抛开需求,仅在操作流程程度上,国外应用至少甩开国内应用N条街,从最基本的硬件加速开启,到整体控件优化方案,这就是为什么很多4.1以上的手机,在使用应用上依然找不到那种属于顺畅的感受。
先申明,菜鸟一枚,很多东西都只是初步猜想,能力有限,短时间内无法深入确认原理,并且有些东西是反编其他应用看来的,可能达到了效果,但一些关键的类似适配之类的代码没看到。所以有错在所难免的嘛,希望体谅。
Android应用性能优化之优化列表头像过度绘制
下图为自己的应用蜂巢的主题时间轴列表:
![](http://images0.cnblogs.com/blog/316851/201303/11230653-9ee991f26dc0464c813741d82b5777c1.png)
因为在开启了硬件加速后,还是没有达到60帧数的要求,存在卡顿的感觉。所以我们一步步来改善他吧
这次我们先解决一个很普遍的问题,那就是过度绘制。
而且过度绘制,其实很好理解,举个例子,一个白色背景的窗口,上面有一个黑色背景的布局,最后上面显示的是一个按钮。事实上最后显示的时候,按钮下面的白色背景和黑色背景是看不到的,但在绘制的时候确都画上去了。
当然过度绘制是无法完全避免的,适度的是可以接受的,但过多了之后就会造成性能影响。引用大牛的分析就是:
设备的内存带宽是有限的,当过度绘制导致应用需要更多的带宽(超过了可用带宽)的时候性能就会降低。带宽的限制每个设备都可能是不一样的。一个好的参考目标就是控制过度绘制为2X;这说明您可以绘制一次屏幕,然后在上面绘制最多2次内容,一共绘制每个像素3次。
android提供了对应的工具进行检测,在4.2的开发者选项中,提供了一个叫做“显示GPU过度绘制”的选项。当然,所要分析的页面必须要开启硬件加速。
![](http://images0.cnblogs.com/blog/316851/201303/11230915-c49f14535362424bb140a148b6e29ab2.png)
通过颜色来区别标示过度绘制的情况:蓝色1x过度绘制、绿色2x过度绘制、淡红色3x过度绘制和红色超过4x过度绘制。
我们先用这个来看下:
![](http://images0.cnblogs.com/blog/316851/201303/11230940-eeb8965e70a14cc98d7468752e3b8b1a.png)
头像跟显示的主体图片都是淡红色,并且区域占的很大,所以我们先依次把这两个图片过度绘制的问题解决了。
知道了要优化的地方,我们就要分析具体问题了,其实优化过度绘制的通用思路比较简单,就是要知道具体每一层都绘制了什么,删去完全没必要的层级。当然这是最基本的思路,要实现这个思路我们就需要用到另外一个工具Tracer for OpenGL。
这个工具能够记录每一次绘画的动作,并且能展现出来,这样我们就能看到具体的绘画情况了。
使用要求:
工具需要连接到一个运行Android 4.1(API级别16)或更高设备上的运行,所以记得开启4.1及以上模拟器或真机来进行分析调试
使用方法:
打开ADT的Tracer for OpenGL Es标签(默认好像是没添加的,需要手动添加下),有一个向上的箭头。点击后会弹出选项对话框,如下。
![](http://images0.cnblogs.com/blog/316851/201303/11231025-408fa0cd52e2472b80501beebd439d7f.png)
Device:选择需要调试的设备。
Application Package:填入包名,也就是AndroidMainfest.xml里面谢的那个,比如com.example.XXXX
Activity to launch:填入需要测试的页面路径。也就是除了包名外的身下部分。比如test.xxxActivity
Data Collection Options:有三个选项,第一个选项是快速定位对应帧,具体的效果就是能看到每一帧最终生成的样子,第二个选项是关键,查看每帧的每个绘制命令及效果。
Destination File:存放数据的路径,建议找个剩余空间大的地方。
都选择完后,点击Trace后就开始分析。设备会直接进入所填写的分析界面,如果没有进入,或者进入后工具的状态没有任何改变,就说明失败了,需要重新设置并重来。当设备上显示完整你需要分析的界面部分时,稍过一会,就可以点击stop来查看结果了。
[个人经验:第三方工具就没有一个不奇葩的,此工具也不例外。依照之前经验,建议点击之前,对应的设备记得把锁屏界面解开,否者失败的可能性很大]
在结果中可以看到每个发送给gpu的GL命令,选择每一帧,能在Frame Summary中看到每一帧的最终效果。并且能在Detail界面中看到每一个绘画命令执行后的界面状态。
以下为头像区域的绘制情况:
(1)
(2)![](//images0.cnblogs.com/blog/316851/201303/11231155-192227cc02d741b5994196b9e9b6d227.png)
![](http://images0.cnblogs.com/blog/316851/201303/11231139-3cc17d2f1bf840528e93405865e6456c.png)
![](http://images0.cnblogs.com/blog/316851/201303/11231155-192227cc02d741b5994196b9e9b6d227.png)
能看出,其实是先将头像的图片画了出来,然后再将一个4个圆角的图片画在之前图片的上面。形成圆角头像的效果。
以上讲的只是一个整体的思路,通过对应的工具,查找出每一步的绘画步骤,来规避多余的绘制命令,从而达到减少过度绘制的目的。
接下来开始解决这个头像的问题,按常理将要实现这个效果,就必须要图片覆盖,所以用通用的思路是无法解决这个过度绘制的问题。当初这个制作圆角头像的方法是从path那里学来的,那我们就看看,path是不是也无法解决此问题。
![](http://images0.cnblogs.com/blog/316851/201303/11231732-9bfc56c897ac48609d264c87741a64cc.png)
好吧,蓝色,证明path能解决这个过度绘制。
看下反编出来的代码,应该能猜个大概出来。
首先先看看布局,是不是普通的ImageView.
![](http://images0.cnblogs.com/blog/316851/201303/11231720-0c54066c9a54412ba7baf31db2c1b5f1.png)
path首页里的布局东西真心多,终于在茫茫大海中找到了我们的目标。
果真,重写了,不是普通的控件,CacheableProfilePhoto。Google了一下,看来不是开源的空间,那就直接从代码里猜吧。
![](http://images0.cnblogs.com/blog/316851/201303/11231329-ab3c0fc8e6364eff93c2101ebb36566f.png)
此控件继承于OverlayImageView,那我们先看看OverlayImageView是啥
![](http://images0.cnblogs.com/blog/316851/201303/11231345-c2810fe50fc64eba8b4f1da4d3d2ad45.png)
恩恩,看来就是他了,作为一个继承于ImageView的家伙,果断直接看OnDraw方法。[常人是无法忍受path这满地都是食物名称为变量的反编代码.坑爹啊]
![](http://images0.cnblogs.com/blog/316851/201303/11231550-db0738a6f003406aae5c22b7e280fb83.png)
发现绘制的整体流程是①先获取一个DrawingCanvas类型的变量,如果此变量为空,则初始化它。②然后似乎有一个方法,将此变量作为参数传入了进去。③之后此变量可以返回一个bitmap,④最后将这个bitmap画在canvas上。
流程挺简单,简直的关键就在于,画上去的bitmap到底是个啥东西。
要弄懂这个东西,我们就必须弄懂DrawingCanvas是什么。
![](http://images0.cnblogs.com/blog/316851/201303/11231610-1ac3924525bd4a969a13092349a5c7e0.png)
看来是一个Canvas,并且拥有一个类型为Bitmap的变量,有一个方法来返回这个bitmap变量。
在DrawingCanvas的代码里,似乎看不到绘制的处理,说明另有其他地方,之前在OverlayImageView看到个方法将此类型的变量作为参数,那我们就看看那个方法做了什么事情。
在OverlayImageView中有个叫做protected void wheatbiscuit(DrawingCanvas paramDrawingCanvas)的方法
![](http://images0.cnblogs.com/blog/316851/201303/11231643-ca138842340141bf8f41cf87d8668234.png)
![](http://images0.cnblogs.com/blog/316851/201303/11231655-e8eaea6b0b0a45e4a4a650224ad02da9.png)
这下有点清晰了,此方法中对传入的DrawingCanvas变量绘制了两次。那现在我们方便来看下这两次方便都画了什么。
这是第一次,那很明显,应该是属于这个ImageView的图片资源
Drawable localDrawable1 = getDrawable();
这是第二次,redwine是在setOverlayResource方法中被赋值的,此方法传入的是i = localTypedArray.getResourceId(0, -1);最后定为到的是app:overlay="@drawable/moment_avatar" ,那就明白了,第二次绘制上去的,其实就是覆盖在上面的画有4个圆角的图片。
Bitmap localBitmap2 = ((BitmapDrawable)this.redwine).getBitmap();
终于真相大白了,总结下,其实path就是将原本每次都要绘制两次的图片,事先就绘制好并且缓存起来,这样就不会过度绘制了。
来,我们看看效果:
![](http://images0.cnblogs.com/blog/316851/201303/11234504-b1db081221174f528c054eb31c4a3299.png)
看吧,头像变蓝了吧?!成功。
当然,还有很多代码细节这里就不详细展开说了,毕竟同样的思想,可以有不同的代码实现逻辑。
最主要还是想传达一个思路。
或许这思路,大家都懂,就我刚知道吧。o(╯□╰)o
这次关于头像的优化就先写到这吧,废话多了点,请见谅。
不过奇怪的是,正文图片的过度绘制,从分析来看似乎正常了耶=。=,忘记之前改了哪了...假动作晃到自己了。明天认真翻代码下。
下篇会继续正文图片的相关优化写下去...