Android 帧动画 trying to use a recycled bitmap引起的崩溃问题

本文深入探讨了帧动画在Android中的实现方式及内存管理问题,特别是关于资源缓存导致的Bitmap回收异常。通过分析Android系统内部Drawable加载流程,揭示了缓存机制与Bitmap回收之间的冲突,并提供了手动管理帧动画资源的有效方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

帧动画通常会在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();
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值