帧动画通常会在xml里定义一个animation-list,并在代码中设置ImageView.setBackgroundResource()来实现。
如果动画内存消耗很大,会在帧动画结束时recycle使用到的bitmap(回收相关代码在文章底部),但是有时候会遇到
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@530acaa
这个crash。搜索了下相关问题,原来是Android sdk对resource drawable进行了缓存,而缓存的图片又被recycle导致的,来看下获取drawable的源码。
点击setBackgroundResource跳转到View.java
public void setBackgroundResource(@DrawableRes int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
}
Drawable d = null;
if (resid != 0) {
d = mContext.getDrawable(resid);
}
setBackground(d);
mBackgroundResource = resid;
}
发现是通过mContext.getDrawable(resid),点进去跳到Context.java
public final Drawable getDrawable(int id) {
return getResources().getDrawable(id, getTheme());
}
继续跟进到Resources.java
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
if (value == null) {
value = new TypedValue();
} else {
mTmpValue = null;
}
getValue(id, value, true);
}
final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
return res;
}
到loadDrawable()这个方法了
Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
if ((id >>> 24) == 0x1) {
final String name = getResourceName(id);
if (name != null) {
Log.d("PreloadDrawable", name);
}
}
}
final boolean isColorDrawable;
final DrawableCache caches;
final long key;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
caches = mColorDrawableCache;
key = value.data;
} else {
isColorDrawable = false;
caches = mDrawableCache;
key = (((long) value.assetCookie) << 32) | value.data;
}
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme.
if (!mPreloading) {
final Drawable cachedDrawable = caches.getInstance(key, theme);
if (cachedDrawable != null) {
return cachedDrawable;
}
}
// Next, check preloaded drawables. These may contain unresolved theme
// attributes.
final ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
if (cs != null) {
dr = cs.newDrawable(this);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(value, id, null);
}
// Determine if the drawable has unresolved theme attributes. If it
// does, we'll need to apply a theme and store it in a theme-specific
// cache.
final boolean canApplyTheme = dr != null && dr.canApplyTheme();
if (canApplyTheme && theme != null) {
dr = dr.mutate();
dr.applyTheme(theme);
dr.clearMutated();
}
// If we were able to obtain a drawable, store it in the appropriate
// cache: preload, not themed, null theme, or theme-specific.
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
}
return dr;
}
代码29-31行,可以看到如果有缓存直接返回缓存里的drawable,如果没有缓存就会在最后调用cacheDrawable进行缓存。
直接通过xml设置背景,但是最后需要回收的话看来并不合适,那手动获取并添加,比如
for (int i = 0; i < 36; i++) {
StringBuilder resStr = new StringBuilder("000");
if (i < 10) {
resStr.append("0");
}
resStr.append(i);
int resId = getResources().getIdentifier(resStr.toString(), "drawable", getPackageName());
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
boxSuccessAnim.addFrame(bitmapDrawable, 80);
}
附上动画结束时回收的代码public static void recycleAnimationDrawable(AnimationDrawable animationDrawables) {
if (animationDrawables != null) {
animationDrawables.stop();
for (int i = 0; i < animationDrawables.getNumberOfFrames(); i++) {
Drawable frame = animationDrawables.getFrame(i);
if (frame != null) {
if (frame instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) frame).getBitmap();
if (null != bitmap) {
bitmap.recycle();
}
}
frame.setCallback(null);
}
}
animationDrawables.setCallback(null);
}
System.gc();
}