Canvas: trying to use a recycled bitmap android.graphics.Bitmap in Android
Asked Answered
S

12

63

I am working on the crop image class, but encounter a recycled bit map problem:

03-02 23:14:10.514: E/AndroidRuntime(16736): FATAL EXCEPTION: Thread-1470
03-02 23:14:10.514: E/AndroidRuntime(16736): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@428e5450
03-02 23:14:10.514: E/AndroidRuntime(16736):    at android.graphics.Canvas.throwIfRecycled(Canvas.java:1026)
03-02 23:14:10.514: E/AndroidRuntime(16736):    at android.graphics.Canvas.drawBitmap(Canvas.java:1096)
03-02 23:14:10.514: E/AndroidRuntime(16736):    at android.graphics.Bitmap.createBitmap(Bitmap.java:604)
03-02 23:14:10.514: E/AndroidRuntime(16736):    at eu.janmuller.android.simplecropimage.CropImage$1.prepareBitmap(CropImage.java:630)
03-02 23:14:10.514: E/AndroidRuntime(16736):    at eu.janmuller.android.simplecropimage.CropImage$1.run(CropImage.java:636)
03-02 23:14:10.514: E/AndroidRuntime(16736):    at eu.janmuller.android.simplecropimage.CropImage$6.run(CropImage.java:343)
03-02 23:14:10.514: E/AndroidRuntime(16736):    at eu.janmuller.android.simplecropimage.Util$BackgroundJob.run(Util.java:175)
03-02 23:14:10.514: E/AndroidRuntime(16736):    at java.lang.Thread.run(Thread.java:856)

The line that error occur is the mScale = 256.0F / mBitmap.getWidth(); (line 630) please search this for more information.

Notice, the code do not has this error before I adding the checkRotation () function. And that function return a bitmap, and that bitmap has caused the exception. This is the hints

Also, in the function I have copied the original bitmap and recycle the old bitmap, so it should not be the root of problem, you are suggested not to look on the code one by one but search the keywords.

/**
 * The activity can crop specific region of interest from an image.
 */
public class CropImage extends MonitoredActivity {

    final int IMAGE_MAX_SIZE = 1024;

    private static final String TAG                    = "CropImage";
    public static final  String IMAGE_PATH             = "image-path";
    public static final  String SCALE                  = "scale";
    public static final  String ORIENTATION_IN_DEGREES = "orientation_in_degrees";
    public static final  String ASPECT_X               = "aspectX";
    public static final  String ASPECT_Y               = "aspectY";
    public static final  String OUTPUT_X               = "outputX";
    public static final  String OUTPUT_Y               = "outputY";
    public static final  String SCALE_UP_IF_NEEDED     = "scaleUpIfNeeded";
    public static final  String CIRCLE_CROP            = "circleCrop";
    public static final  String RETURN_DATA            = "return-data";
    public static final  String RETURN_DATA_AS_BITMAP  = "data";
    public static final  String ACTION_INLINE_DATA     = "inline-data";

    // These are various options can be specified in the intent.
    private       Bitmap.CompressFormat mOutputFormat    = Bitmap.CompressFormat.JPEG;
    private       Uri                   mSaveUri         = null;
    private       boolean               mDoFaceDetection = true;
    private       boolean               mCircleCrop      = false;
    private final Handler               mHandler         = new Handler();

    private int             mAspectX;
    private int             mAspectY;
    private int             mOutputX;
    private int             mOutputY;
    private boolean         mScale;
    private CropImageView   mImageView;
    private ContentResolver mContentResolver;
    private Bitmap          mBitmap;
    private String          mImagePath;

    boolean       mWaitingToPick; // Whether we are wait the user to pick a face.
    boolean       mSaving;  // Whether the "save" button is already clicked.
    HighlightView mCrop;

    // These options specifiy the output image size and whether we should
    // scale the output to fit it (or just crop it).
    private boolean mScaleUp = true;

    private final BitmapManager.ThreadSet mDecodingThreads =
            new BitmapManager.ThreadSet();

    @Override
    public void onCreate(Bundle icicle) {

        super.onCreate(icicle);
        mContentResolver = getContentResolver();

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.cropimage);

        mImageView = (CropImageView) findViewById(R.id.image);

        showStorageToast(this);

        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {

            if (extras.getString(CIRCLE_CROP) != null) {

            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
                    mImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
            }

                mCircleCrop = true;
                mAspectX = 1;
                mAspectY = 1;
            }

            mImagePath = extras.getString(IMAGE_PATH);
            mBitmap = checkRotation(mImagePath);

            Log.d("test1",""+mBitmap.isRecycled());

            if (extras.containsKey(ASPECT_X) && extras.get(ASPECT_X) instanceof Integer) {

                mAspectX = extras.getInt(ASPECT_X);
            } else {

                throw new IllegalArgumentException("aspect_x must be integer");
            }
            if (extras.containsKey(ASPECT_Y) && extras.get(ASPECT_Y) instanceof Integer) {

                mAspectY = extras.getInt(ASPECT_Y);
            } else {

                throw new IllegalArgumentException("aspect_y must be integer");
            }
            mOutputX = extras.getInt(OUTPUT_X);
            mOutputY = extras.getInt(OUTPUT_Y);
            mScale = extras.getBoolean(SCALE, true);
            mScaleUp = extras.getBoolean(SCALE_UP_IF_NEEDED, true);
        }


        if (mBitmap == null) {

            Log.d(TAG, "finish!!!");
            finish();
            return;
        }

        // Make UI fullscreen.
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);

        findViewById(R.id.discard).setOnClickListener(
                new View.OnClickListener() {
                    public void onClick(View v) {

                        setResult(RESULT_CANCELED);
                        finish();
                    }
                });

        findViewById(R.id.save).setOnClickListener(
                new View.OnClickListener() {
                    public void onClick(View v) {

                        try {
                            onSaveClicked();
                        } catch (Exception e) {
                            finish();
                        }
                    }
                });
        findViewById(R.id.rotateLeft).setOnClickListener(
                new View.OnClickListener() {
                    public void onClick(View v) {

                        mBitmap = Util.rotateImage(mBitmap, -90);
                        RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
                        mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
                        mRunFaceDetection.run();
                    }
                });

        findViewById(R.id.rotateRight).setOnClickListener(
                new View.OnClickListener() {
                    public void onClick(View v) {

                        mBitmap = Util.rotateImage(mBitmap, 90);
                        RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
                        mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
                        mRunFaceDetection.run();
                    }
                });
        Log.d("test1","a "+mBitmap.isRecycled());
        startFaceDetection();
    }

    private Bitmap checkRotation(String url){
        mSaveUri = getImageUri(url);        
        ExifInterface exif;
        try {
            exif = new ExifInterface(url);
            int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);

            Matrix matrix = new Matrix();
            switch (rotation) {
                case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
                    matrix.setScale(-1, 1);
                    break;

                case ExifInterface.ORIENTATION_ROTATE_180:
                    matrix.setRotate(180);
                    break;

                case ExifInterface.ORIENTATION_FLIP_VERTICAL:
                    matrix.setRotate(180);
                    matrix.postScale(-1, 1);
                    break;

                case ExifInterface.ORIENTATION_TRANSPOSE:
                    matrix.setRotate(90);
                    matrix.postScale(-1, 1);
                    break;

                case ExifInterface.ORIENTATION_ROTATE_90:
                    matrix.setRotate(90);
                    break;

                case ExifInterface.ORIENTATION_TRANSVERSE:
                    matrix.setRotate(-90);
                    matrix.postScale(-1, 1);
                    break;

                case ExifInterface.ORIENTATION_ROTATE_270:
                    matrix.setRotate(-90);
                    break;

                case ExifInterface.ORIENTATION_NORMAL:        
                default:
                    break;
            }

            Bitmap beforeRotate = getBitmap(url);
            int height = beforeRotate.getHeight();
            int width = beforeRotate.getWidth();
            Bitmap afterRotate = Bitmap.createBitmap(beforeRotate, 0, 0, width, height, matrix, true);
            beforeRotate.recycle();
            return afterRotate;

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return mBitmap;  
    }

    private Uri getImageUri(String path) {

        return Uri.fromFile(new File(path));
    }

    private Bitmap getBitmap(String path) {

        Uri uri = getImageUri(path);
        InputStream in = null;
        try {
            in = mContentResolver.openInputStream(uri);

            //Decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;

            BitmapFactory.decodeStream(in, null, o);
            in.close();

            int scale = 1;
            if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
                scale = (int) Math.pow(2, (int) Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
            }

            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            in = mContentResolver.openInputStream(uri);
            Bitmap b = BitmapFactory.decodeStream(in, null, o2);
            in.close();

            return b;
        } catch (FileNotFoundException e) {
            Log.e(TAG, "file " + path + " not found");
        } catch (IOException e) {
            Log.e(TAG, "file " + path + " not found");
        }
        return null;
    }


    private void startFaceDetection() {

        if (isFinishing()) {
            return;
        }

        mImageView.setImageBitmapResetBase(mBitmap, true);

        Util.startBackgroundJob(this, null,
                "Please wait\u2026",
                new Runnable() {
                    public void run() {

                        final CountDownLatch latch = new CountDownLatch(1);
                        final Bitmap b = mBitmap;
                        mHandler.post(new Runnable() {
                            public void run() {

                                if (b != mBitmap && b != null) {
                                    Log.d("test1","test");
                                    mImageView.setImageBitmapResetBase(b, true);
                                    mBitmap.recycle();
                                    mBitmap = b;
                                }
                                if (mImageView.getScale() == 1F) {
                                    mImageView.center(true, true);
                                }
                                latch.countDown();
                            }
                        });
                        try {
                            latch.await();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        mRunFaceDetection.run();
                    }
                }, mHandler);
    }


    private void onSaveClicked() throws Exception {
        // TODO this code needs to change to use the decode/crop/encode single
        // step api so that we don't require that the whole (possibly large)
        // bitmap doesn't have to be read into memory
        if (mSaving) return;

        if (mCrop == null) {

            return;
        }

        mSaving = true;

        Rect r = mCrop.getCropRect();

        int width = r.width();
        int height = r.height();

        // If we are circle cropping, we want alpha channel, which is the
        // third param here.
        Bitmap croppedImage;
        try {

            croppedImage = Bitmap.createBitmap(width, height,
                    mCircleCrop ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
        } catch (Exception e) {
            throw e;
        }
        if (croppedImage == null) {

            return;
        }

        {
            Canvas canvas = new Canvas(croppedImage);
            Rect dstRect = new Rect(0, 0, width, height);
            canvas.drawBitmap(mBitmap, r, dstRect, null);
        }

        if (mCircleCrop) {

            // OK, so what's all this about?
            // Bitmaps are inherently rectangular but we want to return
            // something that's basically a circle.  So we fill in the
            // area around the circle with alpha.  Note the all important
            // PortDuff.Mode.CLEAR.
            Canvas c = new Canvas(croppedImage);
            Path p = new Path();
            p.addCircle(width / 2F, height / 2F, width / 2F,
                    Path.Direction.CW);
            c.clipPath(p, Region.Op.DIFFERENCE);
            c.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
        }

        /* If the output is required to a specific size then scale or fill */
        if (mOutputX != 0 && mOutputY != 0) {

            if (mScale) {

                /* Scale the image to the required dimensions */
                Bitmap old = croppedImage;
                croppedImage = Util.transform(new Matrix(),
                        croppedImage, mOutputX, mOutputY, mScaleUp);
                if (old != croppedImage) {

                    old.recycle();
                }
            } else {

                /* Don't scale the image crop it to the size requested.
                 * Create an new image with the cropped image in the center and
                 * the extra space filled.
                 */

                // Don't scale the image but instead fill it so it's the
                // required dimension
                Bitmap b = Bitmap.createBitmap(mOutputX, mOutputY,
                        Bitmap.Config.RGB_565);
                Canvas canvas = new Canvas(b);

                Rect srcRect = mCrop.getCropRect();
                Rect dstRect = new Rect(0, 0, mOutputX, mOutputY);

                int dx = (srcRect.width() - dstRect.width()) / 2;
                int dy = (srcRect.height() - dstRect.height()) / 2;

                /* If the srcRect is too big, use the center part of it. */
                srcRect.inset(Math.max(0, dx), Math.max(0, dy));

                /* If the dstRect is too big, use the center part of it. */
                dstRect.inset(Math.max(0, -dx), Math.max(0, -dy));

                /* Draw the cropped bitmap in the center */
                canvas.drawBitmap(mBitmap, srcRect, dstRect, null);

                /* Set the cropped bitmap as the new bitmap */
                croppedImage.recycle();
                croppedImage = b;
            }
        }

        // Return the cropped image directly or save it to the specified URI.
        Bundle myExtras = getIntent().getExtras();
        if (myExtras != null && (myExtras.getParcelable("data") != null
                || myExtras.getBoolean(RETURN_DATA))) {

            Bundle extras = new Bundle();
            extras.putParcelable(RETURN_DATA_AS_BITMAP, croppedImage);
            setResult(RESULT_OK,
                    (new Intent()).setAction(ACTION_INLINE_DATA).putExtras(extras));
            finish();
        } else {
            final Bitmap b = croppedImage;
            Util.startBackgroundJob(this, null, getString(R.string.saving_image),
                    new Runnable() {
                        public void run() {

                            saveOutput(b);
                        }
                    }, mHandler);
        }
    }

    private void saveOutput(Bitmap croppedImage) {

        if (mSaveUri != null) {
            OutputStream outputStream = null;
            try {
                outputStream = mContentResolver.openOutputStream(mSaveUri);
                if (outputStream != null) {
                    croppedImage.compress(mOutputFormat, 90, outputStream);
                }
            } catch (IOException ex) {

                Log.e(TAG, "Cannot open file: " + mSaveUri, ex);
                setResult(RESULT_CANCELED);
                finish();
                return;
            } finally {

                Util.closeSilently(outputStream);
            }

            Bundle extras = new Bundle();
            Intent intent = new Intent(mSaveUri.toString());
            intent.putExtras(extras);
            intent.putExtra(IMAGE_PATH, mImagePath);
            intent.putExtra(ORIENTATION_IN_DEGREES, Util.getOrientationInDegree(this));
            setResult(RESULT_OK, intent);
        } else {

            Log.e(TAG, "not defined image url");
        }
        croppedImage.recycle();
        finish();
    }

    @Override
    protected void onPause() {

        super.onPause();
        BitmapManager.instance().cancelThreadDecoding(mDecodingThreads);
    }

    @Override
    protected void onDestroy() {

        super.onDestroy();

        if (mBitmap != null) {

            mBitmap.recycle();
        }
    }


    Runnable mRunFaceDetection = new Runnable() {
        @SuppressWarnings("hiding")
        float mScale = 1F;
        Matrix mImageMatrix;
        FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
        int mNumFaces;

        // For each face, we create a HightlightView for it.
        private void handleFace(FaceDetector.Face f) {

            PointF midPoint = new PointF();

            int r = ((int) (f.eyesDistance() * mScale)) * 2;
            f.getMidPoint(midPoint);
            midPoint.x *= mScale;
            midPoint.y *= mScale;

            int midX = (int) midPoint.x;
            int midY = (int) midPoint.y;

            HighlightView hv = new HighlightView(mImageView);

            int width = mBitmap.getWidth();
            int height = mBitmap.getHeight();

            Rect imageRect = new Rect(0, 0, width, height);

            RectF faceRect = new RectF(midX, midY, midX, midY);
            faceRect.inset(-r, -r);
            if (faceRect.left < 0) {
                faceRect.inset(-faceRect.left, -faceRect.left);
            }

            if (faceRect.top < 0) {
                faceRect.inset(-faceRect.top, -faceRect.top);
            }

            if (faceRect.right > imageRect.right) {
                faceRect.inset(faceRect.right - imageRect.right,
                        faceRect.right - imageRect.right);
            }

            if (faceRect.bottom > imageRect.bottom) {
                faceRect.inset(faceRect.bottom - imageRect.bottom,
                        faceRect.bottom - imageRect.bottom);
            }

            hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop,
                    mAspectX != 0 && mAspectY != 0);

            mImageView.add(hv);
        }

        // Create a default HightlightView if we found no face in the picture.
        private void makeDefault() {

            HighlightView hv = new HighlightView(mImageView);

            int width = mBitmap.getWidth();
            int height = mBitmap.getHeight();

            Rect imageRect = new Rect(0, 0, width, height);

            // make the default size about 4/5 of the width or height
            int cropWidth = Math.min(width, height) * 4 / 5;
            int cropHeight = cropWidth;

            if (mAspectX != 0 && mAspectY != 0) {

                if (mAspectX > mAspectY) {

                    cropHeight = cropWidth * mAspectY / mAspectX;
                } else {

                    cropWidth = cropHeight * mAspectX / mAspectY;
                }
            }

            int x = (width - cropWidth) / 2;
            int y = (height - cropHeight) / 2;

            RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
            hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop,
                    mAspectX != 0 && mAspectY != 0);

            mImageView.mHighlightViews.clear(); // Thong added for rotate

            mImageView.add(hv);
        }

        // Scale the image down for faster face detection.
        private Bitmap prepareBitmap() {

            if (mBitmap == null) {

                return null;
            }

            // 256 pixels wide is enough.
            if (mBitmap.getWidth() > 256) {

                mScale = 256.0F / mBitmap.getWidth();
            }
            Matrix matrix = new Matrix();
            matrix.setScale(mScale, mScale);
            return Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, true);
        }

        public void run() {

            mImageMatrix = mImageView.getImageMatrix();
            Bitmap faceBitmap = prepareBitmap();

            mScale = 1.0F / mScale;
            if (faceBitmap != null && mDoFaceDetection) {
                FaceDetector detector = new FaceDetector(faceBitmap.getWidth(),
                        faceBitmap.getHeight(), mFaces.length);
                mNumFaces = detector.findFaces(faceBitmap, mFaces);
            }

            if (faceBitmap != null && faceBitmap != mBitmap) {
                faceBitmap.recycle();
            }

            mHandler.post(new Runnable() {
                public void run() {

                    mWaitingToPick = mNumFaces > 1;
                    if (mNumFaces > 0) {
                        for (int i = 0; i < mNumFaces; i++) {
                            handleFace(mFaces[i]);
                        }
                    } else {
                        makeDefault();
                    }
                    mImageView.invalidate();
                    if (mImageView.mHighlightViews.size() == 1) {
                        mCrop = mImageView.mHighlightViews.get(0);
                        mCrop.setFocus(true);
                    }

                    if (mNumFaces > 1) {
                        Toast.makeText(CropImage.this,
                                "Multi face crop help",
                                Toast.LENGTH_SHORT).show();
                    }
                }
            });
        }
    };

    public static final int NO_STORAGE_ERROR  = -1;
    public static final int CANNOT_STAT_ERROR = -2;

    public static void showStorageToast(Activity activity) {

        showStorageToast(activity, calculatePicturesRemaining(activity));
    }

    public static void showStorageToast(Activity activity, int remaining) {

        String noStorageText = null;

        if (remaining == NO_STORAGE_ERROR) {

            String state = Environment.getExternalStorageState();
            if (state.equals(Environment.MEDIA_CHECKING)) {

                noStorageText = activity.getString(R.string.preparing_card);
            } else {

                noStorageText = activity.getString(R.string.no_storage_card);
            }
        } else if (remaining < 1) {

            noStorageText = activity.getString(R.string.not_enough_space);
        }

        if (noStorageText != null) {

            Toast.makeText(activity, noStorageText, 5000).show();
        }
    }

    public static int calculatePicturesRemaining(Activity activity) {

        try {
            /*if (!ImageManager.hasStorage()) {
                return NO_STORAGE_ERROR;
            } else {*/
            String storageDirectory = "";
            String state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) {
                storageDirectory = Environment.getExternalStorageDirectory().toString();
            }
            else {
                storageDirectory = activity.getFilesDir().toString();
            }
            StatFs stat = new StatFs(storageDirectory);
            float remaining = ((float) stat.getAvailableBlocks()
                    * (float) stat.getBlockSize()) / 400000F;
            return (int) remaining;
            //}
        } catch (Exception ex) {
            // if we can't stat the filesystem then we don't know how many
            // pictures are remaining.  it might be zero but just leave it
            // blank since we really don't know.
            return CANNOT_STAT_ERROR;
        }
    }


}
Savagery answered 2/3, 2014 at 15:20 Comment(1)
Hey, Have you solved your problem with Micer's answer??Familial
E
68

Try to add this before calling recycle() methods to make sure bitmap isn't already recycled:

if (mBitmap != null && !mBitmap.isRecycled()) {
    mBitmap.recycle();
    mBitmap = null; 
}
Elapse answered 2/3, 2014 at 15:26 Comment(7)
I keep getting the same error with these exact lines (Android 4.4.4).Durango
Nice try, but didn't fix my error. I had to add this code in onDestroy, to release all bitmaps in the end of activity. +1 anyway for the correct approachMaldives
According to documentation for recycle() "This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap."Mclellan
@KalimahApps Thanks, that makes sense. But note that OP was asking about how to solve the error occurring when using recycle(). Also maybe in 2014 this wasn't in documentation.Elapse
The problem with this is it doesn't actually remedy the problem, because once that bitmap tries to get used again when the page loads, it's been recycled, this solution only fixes the issue of trying to recycle a recycled image, not using a recycled image.Demanding
Android doesn't allows us reuse recycled Bitmap .just comment the bitmap.recycle() to resolve this error.Compensable
There are already condition on bitmap class recycle() func. You can check Bitmap.java. -> public void recycle() { if (!mRecycled) {...Extragalactic
S
22

For those that did not find a solution so far. I had the same problem. I tried to recycle a bitmap in onPause like this:

final Drawable drawable = mImageView.getDrawable();

if (drawable instanceof BitmapDrawable) {
    BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
    Bitmap bitmap = bitmapDrawable.getBitmap();
    bitmap.recycle();
}

if (preView != null && !preView.isRecycled()) {
    preView.recycle();
    preView = null;
}

After returning back i got the exception: Canvas: trying to use a recycled bitmap

Solution for me: I had to add the following

mImageView.setImageBitmap(null);
Staley answered 11/12, 2014 at 10:18 Comment(1)
The setImageBitmap(null) was what I needed. I had a RecyclerViewHolder with an ImageButton that was holding onto the recycled bitmap and trying to access it later after I had called .recycle() in if in my Adapter's datasource.Grumble
A
7

In my case error was caused because I changed visibility from gone to visible (or vice versa) of an element of the layout. And as consequence the space for the imageview and the bitmap created changed, so recycling caused app to crash. Avoid this and your problem will be fix.

Anarch answered 28/8, 2015 at 9:42 Comment(1)
so you are telling that remove bitmap.recycle() as user1111144 said?Turgescent
K
5

Android doesn't allows us reuse recycled Bitmap .just comment the bitmap.recycle() to resolve this error. For more details click here

Kunstlied answered 2/3, 2014 at 15:31 Comment(1)
Then the bitmap hangs around in memory and causes leaks, which is almost worse.Demanding
M
3

I have this error that happens randon, I can't reproduce it in a systematic way.

Use this custom ImageView class to fix it:

public class ImageViewExt extends AppCompatImageView {

private static final String TAG = ImageViewExt.class.getSimpleName();


public ImageViewExt(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

public ImageViewExt(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public ImageViewExt(Context context) {
    super(context);
}

@Override
protected void onDraw(Canvas canvas) {
    try {
        super.onDraw(canvas);
    } catch (Exception e) {
        //Catch Canvas: trying to use a recycled bitmap
        //e.printStackTrace();
    }
}

}

Mesnalty answered 22/6, 2021 at 15:41 Comment(0)
I
2

In my case, i have inflate a layout containing imageview with src image where i have faced same error. That case, problem could be resolved by adding source image programmatically like:

((ImageView)view.findViewById(R.id.imageview)).setImageBitmap(BitmapFactory.decodeResource(getContext().getResources(),
                R.drawable.testimage));
Infra answered 11/4, 2016 at 11:7 Comment(0)
E
1

This should fix the issue in accordance to the android documentation here : https://developer.android.com/topic/performance/graphics/manage-memory#java Here's the code just incase the link doesn't work .

    private int cacheRefCount = 0;
private int displayRefCount = 0;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
public void setIsDisplayed(boolean isDisplayed) {
    synchronized (this) {
        if (isDisplayed) {
            displayRefCount++;
            hasBeenDisplayed = true;
        } else {
            displayRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}

// Notify the drawable that the cache state has changed.
// Keep a count to determine when the drawable is no longer being cached.
public void setIsCached(boolean isCached) {
    synchronized (this) {
        if (isCached) {
            cacheRefCount++;
        } else {
            cacheRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}

private synchronized void checkState() {
    // If the drawable cache and display ref counts = 0, and this drawable
    // has been displayed, then recycle.
    if (cacheRefCount <= 0 && displayRefCount <= 0 && hasBeenDisplayed
            && hasValidBitmap()) {
        getBitmap().recycle();
    }
}

private synchronized boolean hasValidBitmap() {
    Bitmap bitmap = getBitmap();
    return bitmap != null && !bitmap.isRecycled();
}
Estancia answered 9/8, 2019 at 7:12 Comment(0)
G
1

for me the problem was i was using an arraylist to store all the bitmap values inorder to parse them into an adapter, what i was doing was that i was resizing the bitmap and then re orienting them ( the same bitmap file ) and adding them both in the arraylist

SOLUTION Created a temp instance of Bitmap and resized it and then reoriented the temp one and stored the second one in the arraylist and parsed to the adapter

Graduation answered 15/8, 2019 at 21:13 Comment(0)
D
1

Probably the image/png files which you are using have bigger dimensions then 512pixels e.g width or height is greater than 512pixels. Try to decrease the dimensions to at least 512 pixels. In my case I was also getting same error like E/BitmapDrawable: Canvas: trying to use a recycled bitmap.

By using above described way, I was able to solve the error in a day.

Keynote 512 pixels maybe not exact value for threshold, so take a look at your logcat too.

Dis answered 28/10, 2019 at 19:21 Comment(0)
A
1

Just remove this line mBitmap.recycle(); and it will work.

Aplomb answered 15/7, 2021 at 9:14 Comment(0)
M
0

The Fatal Exception: java.lang.RuntimeException error occurs when your application is trying to use a recycled bitmap. This error is caused by an attempt to use a bitmap that has already been recycled by the garbage collector.

For example:

    @BindingAdapter({"restaurantLogo"})
    public static void loadRestaurantLogo(CircleImageView imageView, String logoUrl) {
        Glide.with(imageView.getContext())
            .load(logoUrl)
            .asBitmap()
            .error(R.drawable.image_tmdone_logo_round)
            .placeholder(R.drawable.image_tmdone_logo_round)
            .into(new BitmapImageViewTarget(imageView) {
                @Override
                protected void setResource(Bitmap resource) {
                    RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
                        imageView.getContext().getResources(), resource
                    );
                    circularBitmapDrawable.setCircular(true);
                    imageView.setImageDrawable(circularBitmapDrawable);
                }
            });
    }

The setResource function - this is we are getting bitmap, but it was already recycled!

    @Override
    protected void setResource(Bitmap resource) {
        RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
            imageView.getContext().getResources(), resource
        );
        circularBitmapDrawable.setCircular(true);
        imageView.setImageDrawable(circularBitmapDrawable);
    }

Here is the solution:

    @BindingAdapter({"restaurantLogo"})
    public static void loadRestaurantLogo(CircleImageView imageView, String logoUrl) {
        Context context = imageView.getContext();
        Resources resources = context.getResources();
        Drawable defaultImage = ContextCompat.getDrawable(context, R.drawable.image_tmdone_logo_round);

        try {
            Glide.with(context)
                .load(logoUrl)
                .asBitmap()
                .error(defaultImage)
                .placeholder(defaultImage)
                .into(new BitmapImageViewTarget(imageView) {
                    @Override
                    protected void setResource(Bitmap bitmap) {
                        if (!bitmap.isRecycled()) {
                            RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap);
                            circularBitmapDrawable.setCircular(true);
                            imageView.setImageDrawable(circularBitmapDrawable);
                        } else {
                            // The bitmap is recycled, so we need to load it again before displaying it
                            loadRestaurantLogo(imageView, logoUrl);
                        }
                    }
                });
        } catch (Exception e) {
            if (e.getMessage() != null && e.getMessage().contains("Canvas: trying to use a recycled bitmap")) {
                loadRestaurantLogo(imageView, logoUrl);
            } else {
                Log.e("StoreObject", "Error loading restaurant logo", e);
            }
        }
    }
Mohandis answered 16/2, 2023 at 8:37 Comment(0)
M
0

I ran into this issue and it appears it was due to accessing the same image across different activities in quick succession. Basically I selected an image in activity 1, dismissed that activity and went back to activity 2 where I immediately displayed that image. Some race condition was causing the crash above about 30-50% of the time. I found a workaround in my case to just create a copy of the bitmap like so in activity 2 before displaying:

val bitmap2 = Bitmap.createScaledBitmap(bitmap, bitmap.width, bitmap.height, true)

This seems hacky but worked in my case so hopefully it can help others. If anyone knows a smarter solution to this please comment.

Midi answered 14/10, 2023 at 16:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.