问题描述:
在Glide的onResourceReady()方法中,使用SimplifySpanBuild设置TextView为文本和图片混排的样式时,报错Canvas: trying to use a recycled bitmap。
问题原因:
找了很久,发现是同时使用Glide和SimplifySpanBuild所导致的问题,下面一步步说明。
出错代码:
Glide.with(context).load(imgurl).asBitmap()
.diskCacheStrategy(DiskCacheStrategy.RESULT).fitCenter().into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super
Bitmap> glideAnimation) {
int width = 30;
int height = width;
SimplifySpanBuild simplifySpanBuild = new SimplifySpanBuild();
simplifySpanBuild.append(new SpecialImageUnit(context, resource,
width, height, SpecialGravity.BOTTOM));
mTextView.append(simplifySpanBuild.build());
}
});
先说明一下,SimplifySpanBuild是一个类似SpannableStringBuilder的类,可以方便的实现文本图片混排。
上面的代码乍看一下没有任何问题,使用Glide成功加载完图片后,就可以使用SimplifySpanBuild在mTextView后面显示图片。第一次进入界面调用上部分的代码没有问题,然而当我第二次进入界面时,应用就崩溃了。
报错信息如下:
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@d0d95c4
at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:62)
at android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:226)
at android.view.RecordingCanvas.drawBitmap(RecordingCanvas.java:98)
at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:545)
at android.text.style.DynamicDrawableSpan.draw(DynamicDrawableSpan.java:146)
at android.text.TextLine.handleReplacement(TextLine.java:1026)
at android.text.TextLine.handleRun(TextLine.java:1170)
at android.text.TextLine.drawRun(TextLine.java:509)
at android.text.TextLine.draw(TextLine.java:244)
at android.text.Layout.drawText(Layout.java:569)
at android.text.Layout.draw(Layout.java:321)
at android.text.BoringLayout.draw(BoringLayout.java:444)
at android.widget.TextView.onDraw(TextView.java:7231)
at android.view.View.draw(View.java:20207)
at android.view.View.updateDisplayListIfDirty(View.java:19082)
at android.view.View.draw(View.java:19935)
at android.view.ViewGroup.drawChild(ViewGroup.java:4333)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4112)
at android.view.View.updateDisplayListIfDirty(View.java:19073)
at android.view.View.draw(View.java:19935)
at android.view.ViewGroup.drawChild(ViewGroup.java:4333)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4112)
at android.view.View.updateDisplayListIfDirty(View.java:19073)
at android.view.View.draw(View.java:19935)
找了很久,一直以为是Glide单方面的原因,但不管怎么改依旧有这个报错,直到我在SimplifySpanBuild的build()方法中找到了这处代码。
if (imgWidth < bitWidth && imgHeight < bitHeight) {
Bitmap newBitmap = ThumbnailUtils.extractThumbnail(bitmap, imgWidth, imgHeight);
if (null != newBitmap) {
bitmap.recycle(); //关键,bitmap被回收了
specialImageUnit.setBitmap(newBitmap);
}
}
看到这里我大概知道原因了,第一次进入界面,内存中并没有bitmap缓存,Glide缓存图片到内存缓存中,然后SimplifySpanBuild将图片设置在TextView后面时,是通过旧的bitmap生成了一个新的bitmap,然后将旧的bitmap回收。
所以当第二次进入界面时,虽然在onResourceReady()方法中得到了bitmap的引用,但实际上这个时候bitmap的资源已经被回收了。所以这时候再去使用bitmap就报错崩溃Canvas: trying to use a recycled bitmap,翻译过来就是尝试使用一个已经回收了的bitmap。
问题解决办法:
原因找到了,是因为bitmap内存缓存被回收,又去使用bitmap才报的错。这个时候就来尝试着找解决办法了。
SimplifySpanBuild的源码我们是不可以修改的,那么可不可以从Glide来下手呢?答案是可以的。
在Glide中缓存图片的机制中,Glide缓存图片使用的key是图片的url+一个签名signature(参考Glide官方文档——缓存键(Cache Keys))。先不管这个签名是什么,从实际操作上来看,对于同一个url的图片来说,使用的就是同一个内存缓存,所以可以近似的把url直接看作key。
所以每次进入界面调用Glide加载图片,因为是同一个url的原因,所以从内存中取出来的Bitmap地址始终不变。
每次simplifySpanBuild.build()就会回收内存缓存中的Bitmap,那么使用Glide的skipMemoryCache()方法跳过内存缓存不使用旧的bitmap,而是每次都重新去创建新的bitmap不就行了。
有了思路后,修改后试了试果然没报错了,每次在onResourceReady()方法中获取到的都是不同的bitmap。
修改后的代码如下:
Glide.with(context).load(imgUrl).asBitmap().skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.RESULT).fitCenter().into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
int width = 30;
int height = width;
SimplifySpanBuild simplifySpanBuild = new SimplifySpanBuild();
simplifySpanBuild.append(new SpecialImageUnit(context, resource, width, height, SpecialGravity.BOTTOM));
mTextView.append(simplifySpanBuild.build());
}
});
其实就只需要加一句skipMemoryCache(true)。
还有一种解决方法应该是可以通过设置Glide不同的signature,我没有试,感兴趣的读者可以自行尝试。
参考文章:
如果对你有帮助的话,点赞、评论、赞赏都是对我的鼓励,也是支持我写下去的动力,谢谢!
网友评论