scrollingCache和animateCache
scrollingCache是listview这种滚动布局的一个属性,animateCache是viewgroup的一个属性。他们的作用都是控制DrawingCache。他们都可以在xml布局中控制,也可以用代码调用:
mylayout.setAnimationCacheEnabled(false);
setAnimationCacheEnabled源码如下:
/**
* Enables or disables the children's drawing cache during a layout animation.
* By default, the drawing cache is enabled but this will prevent nested
* layout animations from working. To nest animations, you must disable the
* cache.
*
* @param enabled true to enable the animation cache, false otherwise
*
* @see #isAnimationCacheEnabled()
* @see View#setDrawingCacheEnabled(boolean)
*/
public void setAnimationCacheEnabled(boolean enabled) {
setBooleanFlag(FLAG_ANIMATION_CACHE, enabled);
}
方法的注释说他的功能是在执行一个Layout动画时开启或关闭子控件的绘制缓存。默认情况下,绘制缓存是开启的,但是这将阻止嵌套Layout动画的正常执行。对于嵌套动画,你必须禁用这个缓存。这个属性如果设置true后,在动画绘制过程中会为每一个子布局设置cache,这会提高显示效果,但是需要消耗更多内存和更长的初始化时间。这个属性默认是true。
为什么设置了缓存,动画会更加平滑,是因为避免了在每一帧的重绘。设置了缓存的动画还可以被硬件加速,因为在硬件层,渲染系统可以把bitmap交给GPU处理,并对其进行快速的矩阵操作(如改变透明度,平移、旋转)。而不使用缓存的情况下,则是在每一帧进行重绘,即调用onDraw()方法。
scrollingCache属性和animateCache相似,源码如下:
/**
* Enables or disables the children's drawing cache during a scroll.
* By default, the drawing cache is enabled but this will use more memory.
*
* When the scrolling cache is enabled, the caches are kept after the
* first scrolling. You can manually clear the cache by calling
* {@link android.view.ViewGroup#setChildrenDrawingCacheEnabled(boolean)}.
*
* @param enabled true to enable the scroll cache, false otherwise
*
* @see #isScrollingCacheEnabled()
* @see View#setDrawingCacheEnabled(boolean)
*/
public void setScrollingCacheEnabled(boolean enabled) {
if (mScrollingCacheEnabled && !enabled) {
clearScrollingCache();
}
mScrollingCacheEnabled = enabled;
}
对于listview当滚动的时候,实际上是可见的item布局的执行了动画,使用缓存可以加速动画。但是他的缺点就是它消耗的内存。所以可以手动设置关闭,对于流畅性目前并没有发现有什么影响。
优化后的listview:
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@color/list_background_color"
android:dividerHeight="0dp"
android:listSelector="#00000000"
android:smoothScrollbar="true"
android:scrollingCache="false"
android:animationCache="false" />
下面写一个demo验证chche对内存的影响首先关闭硬件加速
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mxn.soul.demo" >
<application
android:allowBackup="true"
android:icon="@mipmap/icon"
android:label="@string/app_name"
android:name=".BaseApplication"
android:theme="@style/AppTheme" >
<activity
android:name=".Test5Activity"
android:hardwareAccelerated="false"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
在一个LinearLayout中放16个imageview,让这个LinearLayout执行缩小动画,imageview执行旋转动画
public class Test5Activity extends Activity {
private ImageView[] mImageViews = new ImageView[16];
private int[] mImageViewIDs = {R.id.img1,R.id.img2,R.id.img3,R.id.img4,R.id.img5,R.id.img6,R
.id.img7,R.id.img8,R.id.img9,R.id.img10,R.id.img11,R.id.img12,R.id.img13,R.id.img14,R
.id.img15,R.id.img16} ;
private LinearLayout mylayout ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test5);
mylayout = (LinearLayout) findViewById(R.id.mylayout);
for(int n = 0 ; n < mImageViews.length ; n ++ ){
mImageViews[n] = (ImageView) findViewById(mImageViewIDs[n]);
}
mylayout.setAnimationCacheEnabled(true);
mylayout.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
doAnimation() ;
}
});
}
public void doAnimation() {
AnimationSet animationSet=new AnimationSet(true);
ScaleAnimation scaleAnimation=new ScaleAnimation(
1, 0.1f, 1, 0.1f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);
animationSet.addAnimation(scaleAnimation);
mylayout.startAnimation(scaleAnimation) ;
RotateAnimation rotateAnimation =new RotateAnimation(0f,360, Animation.RELATIVE_TO_SELF,
0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotateAnimation.setDuration(2000);
animationSet.addAnimation(rotateAnimation);
for(int n = 0 ; n < mImageViews.length ; n ++ ){
mImageViews[n].startAnimation(rotateAnimation) ;
}
}
}
最终效果如下:
观察内存使用情况,设置mylayout.setAnimationCacheEnabled(false);时如下:
可以看出不管动画如何变化,内存没有变化。
设置mylayout.setAnimationCacheEnabled(true);时如下:
可以看出不管动画变化是,内存在不断增加,之后被回收,因为缓存不断地产生了新的bitmap。对于动画地流畅性来说几乎看出有什么不同。
为了更清楚的观察设置了缓存后onDraw方法的调用情况,我们用自定义的view代替ImageView.
public class MyImageView extends ImageView {
static int count = 0 ;
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyImageView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (BuildConfig.DEBUG) Log.d("===MyImageView","onMeasure 我被调用了"+System.currentTimeMillis());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
count++ ;
if (BuildConfig.DEBUG) Log.d("===MyImageView", "onDraw 我被调用了"+System.currentTimeMillis()
+"==="+count);
}
}
使用一个静态变量count记录onDraw调用的次数。
设置mylayout.setAnimationCacheEnabled(false);时第一次启动如下:
这个很容易理解,因为有16个view所以调用了16次onDraw。第一次点击开始动画后效果如下:
从第17次开始,到800次结束,平均每一个view调用的onDraw次数为 (800-16)/16 = 49 次.
第二次点击开始动画后效果如下:
从第801次开始,到1552次结束,平均每一个view调用的onDraw次数为 (1552-800)/16 = 47 次.
可以看出在不设置缓存的情况的onDraw调用次数均大于40.
现在把setAnimationCacheEnabled改为true进行测试。第一次启动和刚才结果一样。
第一次点击开始动画后效果如下:
可以看出onDraw调用次数大大减少,平均是 (260-16)/16 = 15.25 次.
第二次点击开始动画后效果如下:
平均是 (488-260)/16 = 14.25 次.
这说明了设置了缓存后onDraw调用次数会减少,但同时会增加内存。那么为什么onDraw调用次数会减少呢,在源码中可以找到答案。
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* @param invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
onDraw是通过invalidate()触发的,从注释中可以看到如果设置了缓存,同时View的内容和大小没有变化,那么invalidate可以设置false。这个标志位的改变,导致后面的onDraw没有必要执行,因为有了缓存就直接显示缓存就好了,不用重新执行onDraw。等到了动画的下一帧如果图片的内容、大小还没变,就继续使用缓存,直到内容或大小改变,就重新生成缓存。同理如果没有设置缓存,那么就不能减少onDraw的次数了,因为每一次不管图片内容和大小有没有改变,都要调用onDraw。
个人认为一般情况下在不影响流畅性的前提下,应该尽量减少内存的使用,所以这个scrollingCache和animateCache应该设置false。对于onDraw里需要开销比较大的view,则视情况而定。
平时工作中,一些很简单布局完全可以对scrollingCache和animateCache属性进行false设置,以便节约内存,对显示效果及流畅度应该几乎无影响。如作者最后所说,如果ondraw里操作较多耗时较长则要视情况而定。
http://souly.cn/%E6%8A%80%E6%9C%AF%E5%8D%9A%E6%96%87/2016/01/05/DrawingCache%E8%A7%A3%E6%9E%90/