问题
在做一个功能时使用到了Glide来加载图片,Glide.with(requireContext()).placeholder.load 切换图片时如果load返回图片比较慢,这时会闪现一下默认预加载图片,看起来感觉非常不好,为了更丝滑的切换图片这时我想到在placeholder(img)中传入上一次imageview.drawable图像资源,本来是开心的(看起来好丝滑呀!),但是在多次切换之后,啪程序闪现退了,woc!!
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap
哎,意外也来的太快了吧!
不过从报错不难看出来,它想要回收的bitmap对象,我还在使用中,导致报错
解决办法
我这里决定采用复制的方式,旧的资源就让回收吧!
/** 不要用这个,这个方法会让图片尺寸越来越大,导致OOM,请使用下面那个方法,我修复过了
fun copyDrawable(res: Resources, src: Drawable?): Drawable? {
if (src == null) {
return null
}
val bitmap =
Bitmap.createBitmap(
src.intrinsicWidth,
src.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
src.draw(canvas)
return BitmapDrawable(res, bitmap)
}*/
// 2022/12/02 修复之前方法会出现OOM的BUG
fun copyDrawable(src: Drawable?): Drawable? {
if (src == null) {
return null
}
val bitmap =
Bitmap.createBitmap(
src.intrinsicWidth,
src.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
src.draw(canvas)
return BitmapDrawable(null,bitmap)
}
旧的资源不让用,我造一个新的出来不就行了吗
2022/12/02 今天在不同设备测试中发现了另一个bug 非常严重
BitmapDrawable(res, bitmap)导致原图Drawable尺寸越来越大
今天在使用的时候发现程序频繁更新imageview会OOM,我看到之后就想难道我copy的Gide没有帮我释放?我就手动释放一下结果还是会OOM,我debug看了一下图片尺寸8000x8000多?woc这么大,不可能吧,那好嘛我裁剪一下,裁完之后发现离谱的是,图片第一次是300x300,是我裁剪的大小没错,第二次变成562x562,第三次…反正就越来越大越来越大了,这样看起来不对呀,那应该是其它地方的问题,最后我排查到了copyDrawable,我输出复制之前的尺寸是正常,复制之后就变大了,那不是copyDrawable还能是谁,看到copyDrawable方法bitmap转换出来尺寸都正常,到了BitmapDrawable之后就变大了,我就点进去看了BitmapDrawable源码,下面是部分源代码:
private void init(BitmapState state, Resources res) {
mBitmapState = state;
updateLocalState(res);
if (mBitmapState != null && res != null) {
mBitmapState.mTargetDensity = mTargetDensity;
}
}
/**
* Initializes local dynamic properties from state. This should be called
* after significant state changes, e.g. from the One True Constructor and
* after inflating or applying a theme.
*/
private void updateLocalState(Resources res) {
mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
mBitmapState.mBlendMode);
computeBitmapSize();
}
private void computeBitmapSize() {
final Bitmap bitmap = mBitmapState.mBitmap;
if (bitmap != null) {
mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
} else {
mBitmapWidth = mBitmapHeight = -1;
}
}
这是Android的BitmapDrawable部分源代码,computeBitmapSize看这个方法它自己又给我重新计算了宽度高度,看到这里就什么都明白了,(内心OS:我复制图片根本不需它缩放呀),当时我只看到BitmapDrawable(bitmap) 被划横线弃用了,看了注释找到了用BitmapDrawable(res, bitmap)代替,我就没想那么多,原来它要res是为了得到屏幕密度,自动缩图片大小,我勒个去,坑啊,既然BitmapDrawable(bitmap) 弃用了,那就这样写BitmapDrawable(null, bitmap)行了吧,最后改完debug看了一下果然好了
nice,搞定!
复制这种方式可能很多的人都想的到,但是我在网上搜索时看见别人提到的Drawable复制不是很管用,于是我把我的代码分享出来。