• 2011-06-12

    [Code] Android备忘6 - Drawing Cache - [Code]

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/sgzxy-logs/135415287.html

    androidDrawing Cache机制是文档中除了函数说明就几乎没提过的东西,网上各种文章和论坛讨论也是语焉不详、胡乱猜测。而这一机制却决定着android是否能真正打造高级用户体验。花了一整天研究源码后,下面我将讨论这个问题,对界面效率并不执着者请绕行。

     

     

    1Drawing Cache的作用

    答:每个ViewGroupDispatchDraw它的child时,会先getDrawingCache(位图)。如果DrawingCache存在,则直接使用cache来绘制,反之才调用childdraw方法。这样就牺牲了一定的内存,但减少了draw方法的开销。对于复杂的View,这一代价是相当值得的。

     

     

    2、该使用Drawing Cache的哪些API

    答:实际只有两个API是我推荐使用的:setDrawingCacheEnabledsetDrawingCacheBackgroundColor。其实有很多方法来操纵甚至自己实现Drawing Cache机制,但这里节省时间,我只谈论个人认为的最佳实践。

    setDrawingCacheEnabled是用于打开或关闭自动的cahce机制。这个自动是啥意思呢?就是绘制时总是会取得cache,如果cache不存在或者invalid了,那么就更新生成cache

    setDrawingCacheBackgroundColor在我们手中的用途是设置一个背景颜色缓存(必须是非0值),从而可以使用RGB_565的格式来生成cache,省略透明通道从而减少cache占用的空间和运算量。这一函数不一定符合你的需求,但在能够调用的时候尽量记得去调用。

     

    我推荐的做法如下:

    (1)对于每一个不是经常变化的view,使用setDrawingCacheEnabled(true),并尽量使用setDrawingCacheBackgroundColor。什么是经常变化的View呢?EditText因为光标闪烁就是个例子。任何经常定时调用invalid方法的view都不要去cache

    (2)要注意避免多余的cache,比如说一个ViewGroup中包含多个比较静态的child,那么你要么打开ViewGroup的cache,要么一一打开child的cache,将两者都打开是多余的。同时你要理解这两种做法的优劣区别:打开parent的cache,可以进一步减少parent绘制的代价(否则每次都要把child的cache重新绘制到一起并进行重叠计算);打开child的cache,可以在单个child发生改变时以更小的代价重建cache。但是最重要的区别在于对ViewGroup使用animation时,animation开始时必然要重建其绑定View的cache(会调用invalidate设置~DRAWING_CACHE_VALID标志位),这时候如果ViewGroup打开了cache则需要全部重建,如果child打开了cache那么只是parent额外进行一次绘制而已(每个child的cache不变)。因此,第二种做法是经常更加推荐的。

    (3)对于setDrawingCacheEnabled(true)view,在进行连续变化时,手动在变化开始前设置setDrawingCacheEnabled(false),在变化结束后重新设回true

    (4)尽量为每一个会调用AnimationViewGroup(无论它平时是否进行cache)使用setDrawingCacheBackgroundColor。这会大大减少Animation时的cache开销。

     

    按理说,依照上面四条法则,你就能最大限度地发挥Drawing cache的作用并抑制其副作用。但是,鉴于Android杯具的设计,如果你的工作中同时大量涉及AnimationDrawing Cache,你就必须有一些更透彻的认识才能避免不知不觉的错误。

     

     

    3Animationdrawing cache方面到底做了啥?

     

    答:其实是ViewGroup为了保证Animation的流畅,设计了一个Animation Cache机制。这一机制就是将ViewGroup的所有child都强制建立Drawing Cache,在Animation结束后再释放cache。其中onAnimationStart方法负责建立cacheonAnimationEnd方法负责释放cache

     

     

    4、我们一般可能犯什么错?

    答:看你是否会这样做:对于某个动画后要消失的View,使用AnimationListeneronAnimationEnd事件,在里面把要消失的Viewparentremove掉或者setVisibility(GONE)

    这样做的结果是,Animation结束时可能会卡一下,哪怕你同时clearAnimation后依然如此。

     

    为什么呢?

    因为无论你是removechild还是setVisibility(GONE),都会立刻触发destroyDrawingCache方法,也就是cache会被清除掉。

    然后再从Trace Stack看一下onAnimationEnd的触发时机,是在draw方法中,是在即将绘制View之前(就是说无论你remove了还是GONE了,都阻止不了View的绘制)。于是,当要画Animation的最后一帧时,必须重新建立cache(前面被你destroy掉了),哪怕这一帧很有可能完全不用画(比如View已经移动到屏幕外了)。

     

    如何解决?

    貌似继承View.onAnimationEnd在里面清除掉无用的View可以避免重建cache,但是我没试,因为View.onAnimationEnd本身会引起另外一个问题(后面再说)。而除此之外我根本没看到android提供了其它接口可以解决上述问题,于是不得不发明了一个trick

    trick是这样:定义一个EmptyAnimation类,除了extends Animation外什么都不做。当你对A中的child B进行Animation,结束时想把BAremove掉或者GONE掉,你应该在BAnimation开始的同时,也同时启动A的一个EmptyAnimationDurationBAnimation一样),然后在这个EmptyAnimationonAnimationEnd事件时,去执行将B移除的操作,并且记得对B进行clearAnimation

    实际上,如果你想让B在动画结束后消失,那么动画的最后一帧是不用显示的(最后一帧表达的就是:消失)。使用以上trick,就可以在B绘制最后一帧前,把B给清除掉,同时把动画也给关掉(避免调用View.onAnimationEnd),因此目前所知是最高效的办法。

     

     

    5、更蛋疼的问题

    为什么非要避开View.onAnimationEnd呢?因为ViewGroup.onAnimationEnd会将childDrawing Cache机制统统关掉,完全不管这个child原来是否setDrawingCacheEnabled(true)

    所以非常悲催,对于任何Animation,你都必须记得在AnimationListener.onAnimationEnd的时候去clearAnimation,并且手动将一些你觉得不需要的cache给关掉(因为View.onAnimationStart将child的cache都打开了)。

    分享到: