在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()过,应该从这里分析具体原因。