Canvas: trying to use a recycled bitmap android.graphics

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

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.4k
置顶 精选
博客七周年:AI 一天完成整体重构
AI

博客七周年:AI 一天完成整体重构

博客从 2019 年国庆用 Xiuno BBS 搭建,到 2026 年国庆整整七年。868 篇文章、53 条评论、6060 个代码块,这次与 AI Agent 结对,一天完成从 PHP 论坛到 Next.js 的整体重构与无损迁移。

23
R8编译问题:Missing classes detected while running R8
分享

R8编译问题:Missing classes detected while running R8

R8编译问题:Missing classes detected while running R8Android R8是一个代码混淆和压缩工具,可以将应用程序的大小和安全性优化。它引入了一些新功能,如成员内省、混淆指针、类内省等。但R8使用起来一直不友好,因为自从使用R8之后编译问题不断。主要还是和混淆相关,经常报错,最近又遇到一个:Missing classes detected while ru

8.4k