Android(安卓)bitmap.recycle()导致trying to use a recycled bitmap报错分析
16lz
2021-01-25
在android实际项目中,有时会在Activity的onDestroy()做一些资源释放工作,比如bitmap资源。通常的写法是这样
public class NextActivity extends Activity {private ImageView imageView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_next);}@Overrideprotected 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()过,应该从这里分析具体原因。
更多相关文章
- android PowerManager wakelock
- Android(安卓)解决方案-6.0不提供org.apache.http.*
- Android标题栏上添加多个Menu按钮的实例
- SQLite抛出错误“未关闭游标及数据库对象”特殊原因一例
- Android内容提供器-读取通讯录
- [Android实例] android中进行https连接的方式的详解 (转发)
- Android桌面小组件:最简例子
- 时间和日期选择器DatePicker和TimePicker的使用
- Android应用开发SharedPreferences存储数据的使用方法