Android bitmap.recycle()导致trying to use a recycled bitmap报错分析

在android实际项目中,有时会在Activity的onDestroy()做一些资源释放工作,比如bitmap资源。通常的写法是这样

public class NextActivity extends Activity {

	private ImageView imageView;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_next);
	}

	@Override
	protected void onDestroy() {
		if (imageView instanceof ImageView) {
			Drawable d = imageView.getDrawable();
			if (d != null && d instanceof BitmapDrawable) {
				Bitmap bmp = ((BitmapDrawable) d).getBitmap();
				bmp.recycle();
				bmp = null;
			}
			imageView.setImageBitmap(null);
			imageView.setBackgroundDrawable(null);
			if (d != null) {
				d.setCallback(null);
			}
		}
		System.gc();
		super.onDestroy();
	}
}

对应的xml是这样

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".NextActivity" >

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
	android:src="@drawable/ic_launcher" />

</RelativeLayout>
我们这里模拟启动Activity操作,MainActivity启动NextActivity,第一次启动正常,按back键,第二次启动,就会抛出java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@cd46a67。报错信息很明确,试图使用一个已经回收的bitmap。 我们log一下NextActivity的onCreate()方法,打印bitmap的哈希码值

第一次和第二次的实例为什么是相同的呢?每次onCrate()不是应该重新绘制吗?为什么相同呢?其实这是android的优秀设计,我们这里的bitmap使用xml的src来指定的Drawable,Android系统每次解析图片优先于从缓存中拿,没有才去创建,所以第一次是创建的实例,第二次是从缓存中拿到的数据。为了刨根问底,我们看一下源码。

我们知道xml给控件设置属性最终都是使用pull解析在用代码创建,那么我们应该看一下ImageView的构造方法

ImageView.class

    public ImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initImageView();

        TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.ImageView, defStyle, 0);

        Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
        if (d != null) {
            setImageDrawable(d);
        }

  <span style="white-space:pre">	</span>......
        
        a.recycle();

        //need inflate syntax/reader for matrix
    }
我们重点看第8行,getDrawalbe(com.android.internal.R.styleable.ImageView_src);点进去

    public Drawable getDrawable(int index) {
        final TypedValue value = mValue;
        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
            if (false) {
                System.out.println("******************************************************************");
                System.out.println("Got drawable resource: type="
                                   + value.type
                                   + " str=" + value.string
                                   + " int=0x" + Integer.toHexString(value.data)
                                   + " cookie=" + value.assetCookie);
                System.out.println("******************************************************************");
            }
            return mResources.loadDrawable(value, value.resourceId);
        }
        return null;
    }
这里13行装载Drawable,这个方法是Resources的,继续点进去。

    /*package*/ Drawable loadDrawable(TypedValue value, int id)
            throws NotFoundException {

	......

        Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);

        if (dr != null) {
            return dr;
        }
        Drawable.ConstantState cs;
        if (isColorDrawable) {
            cs = sPreloadedColorDrawables.get(key);
        } else {
            cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
        }
        if (cs != null) {
            dr = cs.newDrawable(this);
        } else {
            if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            }

            if (dr == null) {
                if (value.string == null) {
                    throw new NotFoundException(
                            "Resource is not a Drawable (color or path): " + value);
                }

                String file = value.string.toString();

                if (TRACE_FOR_MISS_PRELOAD) {
                    // Log only framework resources
                    if ((id >>> 24) == 0x1) {
                        final String name = getResourceName(id);
                        if (name != null) android.util.Log.d(TAG, "Loading framework drawable #"
                                + Integer.toHexString(id) + ": " + name
                                + " at " + file);
                    }
                }

                if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie "
                        + value.assetCookie + ": " + file);

                if (file.endsWith(".xml")) {
                    Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
                    try {
                        XmlResourceParser rp = loadXmlResourceParser(
                                file, id, value.assetCookie, "drawable");
                        dr = Drawable.createFromXml(this, rp);
                        rp.close();
                    } catch (Exception e) {
                        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
                        NotFoundException rnf = new NotFoundException(
                            "File " + file + " from drawable resource ID #0x"
                            + Integer.toHexString(id));
                        rnf.initCause(e);
                        throw rnf;
                    }
                    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);

                } else {
                    Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
                    try {
                        InputStream is = mAssets.openNonAsset(
                                value.assetCookie, file, AssetManager.ACCESS_STREAMING);
        //                System.out.println("Opened file " + file + ": " + is);
                        dr = Drawable.createFromResourceStream(this, value, is,
                                file, null);
                        is.close();
        //                System.out.println("Created stream: " + dr);
                    } catch (Exception e) {
                        Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
                        NotFoundException rnf = new NotFoundException(
                            "File " + file + " from drawable resource ID #0x"
                            + Integer.toHexString(id));
                        rnf.initCause(e);
                        throw rnf;
                    }
                    Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
                }
            }
            }
        }

        if (dr != null) {
            dr.setChangingConfigurations(value.changingConfigurations);
            cs = dr.getConstantState();
            if (cs != null) {
                if (mPreloading) {
                    final int changingConfigs = cs.getChangingConfigurations();
                    if (isColorDrawable) {
                        if (verifyPreloadConfig(changingConfigs, 0, value.resourceId,
                                "drawable")) {
                            sPreloadedColorDrawables.put(key, cs);
                        }
                    } else {
                        if (verifyPreloadConfig(changingConfigs,
                                LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
                            if ((changingConfigs&LAYOUT_DIR_CONFIG) == 0) {
                                // If this resource does not vary based on layout direction,
                                // we can put it in all of the preload maps.
                                sPreloadedDrawables[0].put(key, cs);
                                sPreloadedDrawables[1].put(key, cs);
                            } else {
                                // Otherwise, only in the layout dir we loaded it for.
                                final LongSparseArray<Drawable.ConstantState> preloads
                                        = sPreloadedDrawables[mConfiguration.getLayoutDirection()];
                                preloads.put(key, cs);
                            }
                        }
                    }
                } else {
                    synchronized (mAccessLock) {
                        //Log.i(TAG, "Saving cached drawable @ #" +
                        //        Integer.toHexString(key.intValue())
                        //        + " in " + this + ": " + cs);
                        if (isColorDrawable) {
                            mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                        } else {
                            mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                        }
                    }
                }
            }
        }

        return dr;
    }



这里的代码较多,做了一些删节,只留下重点部分,方面阅读,可以看到第6行是先从缓存中拿Drawable,然后第20行else才是真正创建Drawable的地方,50行是pull解析xml的地方90行就放入了缓存,其中sPreloadedDrawables和mDrawableCache是LongSparseArray<Drawable.ConstantState>[]该类就是对HashMap的优化类,可以当作HashMap来使用,这样就不难理解,重新创建Bitmap的时候是同一个实例了,好了,明白了原因。做一个小结吧!

总结:

1.通过XML给控件设置的Drawable最好不要recycle(),除非该Drawable只使用一次。

2.android系统会将使用过的资源(R.Drawable)会放入缓存中,优化下次使用的速度。

3.出现trying to use a recycled bitmap报错的原因,就是使用的相同的实例,并且之前recycle()过,应该从这里分析具体原因。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值