美文网首页
Glide+SimplifySpanBuild报错Canvas:

Glide+SimplifySpanBuild报错Canvas:

作者: 程序员张晴天 | 来源:发表于2019-03-01 00:57 被阅读0次

    问题描述:

    在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,我没有试,感兴趣的读者可以自行尝试。

    参考文章:

    如果对你有帮助的话,点赞、评论、赞赏都是对我的鼓励,也是支持我写下去的动力,谢谢!

    相关文章

      网友评论

          本文标题:Glide+SimplifySpanBuild报错Canvas:

          本文链接:https://www.haomeiwen.com/subject/hhzluqtx.html