Canvas: trying to use a recycled bitmap android.graphics

QuibblerQuibbler 2020-06-09 约 20 分钟 4644 次阅读

Canvas: trying to use a recycled bitmap android.graphics

又又遇到祖传问题,网上一搜6年前都有这个问题,看来从历史吸取经验教训是最快的成长!

1、异常捕获

复现很容易:写一个Bitmap,调用Bitmap的recycle()方法将其回收掉,再设置给ImageView。

mBitmap.recycle();
    mImageView.setImageBitmap(mBitmap);

捕获到下面的异常:

Process: com.easyicon.learnglide, PID: 8337

    java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@4337e91

        at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1270)

        at android.graphics.Canvas.drawBitmap(Canvas.java:1404)

        at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:544)

        at android.widget.ImageView.onDraw(ImageView.java:1246)

        at android.view.View.draw(View.java:16214)

        at android.view.View.updateDisplayListIfDirty(View.java:15211)

        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)

        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)

        at android.view.View.updateDisplayListIfDirty(View.java:15171)

        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)

        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)

        at android.view.View.updateDisplayListIfDirty(View.java:15171)

        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)

        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)

        at android.view.View.updateDisplayListIfDirty(View.java:15171)

        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)

        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)

        at android.view.View.updateDisplayListIfDirty(View.java:15171)

        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)

        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)

        at android.view.View.updateDisplayListIfDirty(View.java:15171)

        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3605)

        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3585)

        at android.view.View.updateDisplayListIfDirty(View.java:15171)

        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:296)

        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:302)

        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:337)

        at android.view.ViewRootImpl.draw(ViewRootImpl.java:2883)

        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2699)

        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2328)

        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1340)

        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6616)

        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)

        at android.view.Choreographer.doCallbacks(Choreographer.java:670)

        at android.view.Choreographer.doFrame(Choreographer.java:606)

        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)

        at android.os.Handler.handleCallback(Handler.java:739)

        at android.os.Handler.dispatchMessage(Handler.java:95)

        at android.os.Looper.loop(Looper.java:148)

        at android.app.ActivityThread.main(ActivityThread.java:5554)

        at java.lang.reflect.Method.invoke(Native Method)

        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:935)

        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:726)

2、分析

异常堆栈一大长串,前面的是ActivityThread主线程Looper处理事件,通过Handler分发。接着是Choreographer图像绘制。直接看重点ImageView的onDraw()方法:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
            ...
            mDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }

接着调用Drawable的draw(Canvas )方法,Drawable是个抽象类,BitmapDrawable继承自Drawable(还有很多其它类也继承自Drawable),实现draw(Canvas canvas)方法:

@Override
    public void draw(Canvas canvas) {
        final Bitmap bitmap = mBitmapState.mBitmap;
        if (bitmap == null) {
            return;
        }
        ...
            canvas.drawBitmap(bitmap, null, mDstRect, paint);
        ...
    }

接着调用Canvas类的drawBitmap()方法,Canvas继承自BaseCanvas类,该方法定义在BaseCanvas类中:

public void drawBitmap(@NonNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst,
            @Nullable Paint paint) {
        if (dst == null) {
            throw new NullPointerException();
        }
        throwIfCannotDraw(bitmap);
        throwIfHasHwBitmapInSwMode(paint);
        final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
        ...
    }

可以看见在绘制Bitmap方法中,调用了throwIfCannotDraw(Bitmap bitmap)方法:

protected void throwIfCannotDraw(Bitmap bitmap) {
        if (bitmap.isRecycled()) {
            throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap);
        }
        if (!bitmap.isPremultiplied() && bitmap.getConfig() == Bitmap.Config.ARGB_8888 &&
                bitmap.hasAlpha()) {
            throw new RuntimeException("Canvas: trying to use a non-premultiplied bitmap "
                    + bitmap);
        }
        throwIfHwBitmapInSwMode(bitmap);
    }

如果Bitmap已经被回收isRecycled()就抛出异常:RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap)

3、处理

无非就是异常捕获,另外一个就是严谨的时序逻辑(安卓中很难!)

3.1、继承ImageView捕获

既然异常抛出在ImageView的onDraw()阶段,那么就在ImageView的onDraw()方法中捕获它:

private class SafeImageView extends ImageView {
        public SafeImageView(Context context) {
            super(context);
        }
        public SafeImageView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }
        public SafeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
        public SafeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
        
        //捕获可能的异常
        @Override
        protected void onDraw(Canvas canvas) {
            try {
                super.onDraw(canvas);
            } catch (Exception e) {
                Log.e(TAG, e.toString());
            }
        }
    }

3.2、使用时严谨的判断

好在Bitmap提供了isRecycled()方法,使用Bitmap的时候判断是否回收了。

if (null != mBitmap && !mBitmap.isRecycled()) {
        mImageView.setImageBitmap(mBitmap);
    }

参考资料:

        Canvas: trying to use a recycled bitmap android.graphics.Bitmap@XXX

        Android手动回收bitmap,引发Canvas: trying to use a recycled bitmap处理

        trying to use a recycled bitmap android.graphics.Bitmap@af903b

        Bitmap回收—Canvas: trying to use a recycled bitmap android.graphics

        关于Glide出现trying to use a recycled bitmap错误的问题分析

相关推荐

国内开源镜像站点
精选分享

国内开源镜像站点

国内开源镜像站点开源,(Open Source)全称为开放源代码。开源就是要用户利用源代码在其基础上修改和学习的,但开源系统同样也有版权,同样也受到法律保护。国内的高校和一些企业也建立了开源镜像站点,支持开源。从开源站点上面可以下载很多开源资源(Android源码),上大学的时候就在这些镜像网站上下载Linux发行版、Qt等软件。 清华大学:http://mirrors.tuna.tsinghua

2.5k
Honeycam 最好的GIF动图软件
精选分享

Honeycam 最好的GIF动图软件

Honeycam 最好的GIF动图软件推荐一款Gif制作软件:Honeycam,是一款高质量的GIF动图制作和编辑器。以前将视频转GIF,录制屏幕动图要费很大功夫,这款软件真的非常简洁好用,值得收藏,博客上的GIF都是用Honeycam录制的。官网:Honeycam1、简洁的界面下载安装完成后打开,主界面很简洁。2、录制屏幕选择录制,调整好要录制的区域录制完成后,可以编辑每个帧,调整帧时长,删除多

2.2k
网站被注入恶意脚本排查实录:AI 辅助安全分析实战
置顶 精选AI

网站被注入恶意脚本排查实录:AI 辅助安全分析实战

网站被注入恶意脚本排查实录:AI 辅助安全分析实战某天收到华为云监管反馈,称个人博客网站存在色情违规界面。作为技术博主,这简直是晴天霹雳——网站一直正常运行,怎么会突然出现这类问题?经过排查发现,网站被注入了恶意跳转脚本。本文记录完整的排查过程,重点分享如何借助 AI 快速定位问题并修复,希望能给遇到类似情况的开发者提供参考。1、问题发现与初步排查收到监管反馈后,第一时间打开网站检查。首页看起来完

310