Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to fix "canvas: trying to use a recycled bitmap error"?

I'm creating a RecyclerView to show a Grid of pictures. When selecting one of them, it should open a new activity with a transition.

I'm using Glide library to load the pictures and the transition looks awful because it reloads the picture in the new activity. So I had to save it in cache, and then use it for the transition.

I have the code, but sometimes if the picture doesn't load, it throws a Canvas RuntimeException.

This is the log:

07-03 15:19:58.633  28461-28461/jahirfiquitiva.project E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: jahirfiquitiva.project, PID: 28461
    java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@29f09d20
            at android.graphics.Canvas.throwIfCannotDraw(Canvas.java:1282)
            at android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:599)
            at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:538)
            at android.widget.ImageView.onDraw(ImageView.java:1176)
            at android.view.View.draw(View.java:15239)
            at android.view.View.updateDisplayListIfDirty(View.java:14175)
            at android.view.View.getDisplayList(View.java:14197)
            at android.view.GhostView.onDraw(GhostView.java:52)
            at android.view.View.draw(View.java:15239)
            at android.view.View.updateDisplayListIfDirty(View.java:14175)
            at android.view.View.getDisplayList(View.java:14197)
            at android.view.View.draw(View.java:14967)
            at android.view.ViewGroup.drawChild(ViewGroup.java:3406)
            at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3199)
            at android.view.View.updateDisplayListIfDirty(View.java:14170)
            at android.view.View.getDisplayList(View.java:14197)
            at android.view.View.draw(View.java:14967)
            at android.view.ViewGroup.drawChild(ViewGroup.java:3406)
            at android.view.ViewGroup.dispatchDraw(ViewGroup.java:3199)
            at android.view.ViewOverlay$OverlayViewGroup.dispatchDraw(ViewOverlay.java:219)
            at android.view.View.draw(View.java:15248)
            at android.widget.FrameLayout.draw(FrameLayout.java:598)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2906)
            at android.view.View.updateDisplayListIfDirty(View.java:14175)
            at android.view.View.getDisplayList(View.java:14197)
            at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:273)
            at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:279)
            at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:318)
            at android.view.ViewRootImpl.draw(ViewRootImpl.java:2536)
            at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2352)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1982)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1061)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5891)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
            at android.view.Choreographer.doCallbacks(Choreographer.java:580)
            at android.view.Choreographer.doFrame(Choreographer.java:550)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5289)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)

This is the code to open the other activity and save the picture as cache:

private void openViewer(WallpapersAdapter.WallsHolder wallsHolder, int index, final HashMap<String, String> data) {

        final Intent intent = new Intent(wallsActivity, ViewerActivity.class);
        intent.putExtra("wallUrl", data.get(WallpapersActivity.WALL));
        intent.putExtra("wallName", data.get(WallpapersActivity.NAME));
        intent.putExtra("transitionName", ViewCompat.getTransitionName(wallsHolder.wall));

        //save image from drawable
        //get its path and send it to activity
        Bitmap bitmap = drawableToBitmap(wallsHolder.wall.getDrawable());
        //Convert to byte array and send to the other activity

        Log.e("Resolution", bitmap.getWidth() + "x" + bitmap.getHeight());
        try {
            //Write file
            String filename = "bitmap.png";
            FileOutputStream stream = this.openFileOutput(filename, Context.MODE_PRIVATE);
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);

            //Cleanup
            stream.close();
            bitmap.recycle();

            //Pop intent
            intent.putExtra("image", filename);
        } catch (Exception e) {
            e.printStackTrace();
        }

        ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                this, wallsHolder.wall, ViewCompat.getTransitionName(wallsHolder.wall));
        startActivity(intent, options.toBundle());

    }

    public static Bitmap drawableToBitmap (Drawable drawable) {
        Bitmap bitmap = null;
        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            if(bitmapDrawable.getBitmap() != null) {
                return bitmapDrawable.getBitmap();
            }
        }

        if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
        } else {
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);
        return bitmap;
    }

What could I do to fix this issue? Thanks in advance.

like image 362
Jahir Fiquitiva Avatar asked Jul 04 '15 03:07

Jahir Fiquitiva


People also ask

What is recycled bitmap?

The recycle() method allows an app to reclaim memory as soon as possible. Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap" .

What is bitmap in canvas?

Canvas is the place or medium where perfroms/executes the operation of drawing, and Bitmap is responsible for storing the pixel of the picture you draw.


2 Answers

Use this custom ImageView class

public class MyImageView extends ImageView {
 
    public MyImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
 
    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    public MyImageView(Context context) {
        super(context);
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        try {
            super.onDraw(canvas);
        } catch (Exception e) {
            Log.i(MyImageView.class.getSimpleName(), "Catch Canvas: trying to use a recycled bitmap");
        }
    }
 
}
like image 153
Momen Zaqout Avatar answered Nov 09 '22 23:11

Momen Zaqout


I suspect that once in a while your bitmap gets into the recycled state just before the Canvas gets a chance to draw onto it here drawable.draw(canvas);.

A quick solution should be not to call bitmap.recycle();, which is not strictly required for android >2.3.3. If you still want to reclaim this memory forcefully, you'll have to find a way to check when the bitmap is indeed no longer needed (i.e., Canvas had a chance to finish its drawing operations).

like image 45
AndroidEx Avatar answered Nov 10 '22 01:11

AndroidEx