Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RuntimeException using recycled bitmap after os memory trim (0.13.0) #546

Closed
19craig opened this issue Oct 6, 2020 · 5 comments
Closed
Labels
bug Something isn't working

Comments

@19craig
Copy link

19craig commented Oct 6, 2020

Describe the bug
Reported from production app:
Fatal Exception: java.lang.RuntimeException
Canvas: trying to use a recycled bitmap android.graphics.Bitmap@d0434cd
coil.drawable.CrossfadeDrawable.draw
The app reported 5 memory trim events immediately before the crash

Expected behavior
Ideally, reload whatever is necessary to draw the image. At least return an error and don't crash.

To Reproduce
I have not been able to reproduce in development.

Stack Trace
android.graphics.BaseCanvas.throwIfCannotDraw (BaseCanvas.java:66)
android.graphics.RecordingCanvas.throwIfCannotDraw (RecordingCanvas.java:277)
android.graphics.BaseRecordingCanvas.drawBitmap (BaseRecordingCanvas.java:88)
android.graphics.drawable.BitmapDrawable.draw (BitmapDrawable.java:548)
coil.drawable.CrossfadeDrawable.draw (CrossfadeDrawable.java:69)
coil.drawable.CrossfadeDrawable.draw (CrossfadeDrawable.java:83)

android.widget.ImageView.onDraw (ImageView.java:1434)
android.view.View.draw (View.java:21561)
android.view.View.updateDisplayListIfDirty (View.java:20404)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:4432)
android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:4405)
android.view.View.updateDisplayListIfDirty (View.java:20355)
android.view.ThreadedRenderer.updateViewTreeDisplayList (ThreadedRenderer.java:575)
android.view.ThreadedRenderer.updateRootDisplayList (ThreadedRenderer.java:581)
android.view.ThreadedRenderer.draw (ThreadedRenderer.java:654)
android.view.ViewRootImpl.draw (ViewRootImpl.java:3755)
android.view.ViewRootImpl.performDraw (ViewRootImpl.java:3559)
android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:2879)
android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:1792)
android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:7868)
android.view.Choreographer$CallbackRecord.run (Choreographer.java:967)
android.view.Choreographer.doCallbacks (Choreographer.java:791)
android.view.Choreographer.doFrame (Choreographer.java:726)
android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:952)
android.os.Handler.handleCallback (Handler.java:883)
android.os.Handler.dispatchMessage (Handler.java:100)
android.os.Looper.loop (Looper.java:214)
android.app.ActivityThread.main (ActivityThread.java:7386)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:499)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:980)

Version
Coil 0.13.0
Crash occurred on HMD Global, Android v10

@19craig 19craig added the bug Something isn't working label Oct 6, 2020
@colinrtwhite
Copy link
Member

Hmm I'm not able to reproduce this as well, however a few other instances of this have been reported on Coil's issue tracker. I'm going to leave this issue open as a central location if other people are able to reproduce.

Setting ImageLoader.bitmapPoolingEnabled(false) when constructing your ImageLoader disables Coil's bitmap reference counting + recycling behaviour which should fix this issue. Bitmap pooling is most effective on API 23 and below and is less effective on API 24 and above.

@colinrtwhite colinrtwhite added the help wanted Issues that are up for grabs + are good candidates for community PRs label Dec 20, 2020
@colinrtwhite colinrtwhite changed the title CrossfadeDrawable.draw RuntimeException using recycled bitmap after os memory trim (0.13.0) RuntimeException using recycled bitmap after os memory trim (0.13.0) Mar 9, 2021
@colinrtwhite
Copy link
Member

colinrtwhite commented Mar 9, 2021

Added some more explanation on why this can occur here.

@tprochazka
Copy link

So without pooling. Thumbnail is cached in memory in which way? As JPG? So then the only thing that happens without pooling is that image needs to be decoded back to the Bitmap object for displaying? But in-memory cache, has already a small size, so this operation will be very fast anyway, especially on 23+ devices right?

@colinrtwhite
Copy link
Member

@tprochazka Bitmaps are always stored in memory as their raw, uncompressed format. JPG/PNG/BMP are all compressed formats that only make a difference before the bitmap is decoded. Bitmap pooling only allows Coil to re-use previously allocated bitmaps to decode new images into them. It's object pooling for bitmaps. With bitmap pooling disabled, the only thing that changes is Coil allocates a new bitmap instead of re-using one that was already allocated for a previous image request.

@colinrtwhite colinrtwhite removed the help wanted Issues that are up for grabs + are good candidates for community PRs label Oct 6, 2021
@colinrtwhite
Copy link
Member

Closing this out as bitmap pooling is removed in 2.0.0-rc01 so this error shouldn't occur anymore. Check out here for why bitmap pooling was removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants