ntfy icon indicating copy to clipboard operation
ntfy copied to clipboard

Android: notifications with large images attached crash the app

Open daedric7 opened this issue 1 year ago • 1 comments

Hello

I've noticed this for a while but has it's only trigger by very large images, it's difficult to find a case.

A notification sent via curl with a very large image attached to a topic makes the app crash when opening that topic.

All other topics keep working, the app keeps receiving more notifications on that topic although unable to display them.

The last image to cause this was this: (WARNING: NSFW!)

https://i.imgur.com/pKiQk6l.jpg , 4.4mb 4480x6720

Android creates a log report and sends it somewhere, but i cannot intercept it.

The only error i found was:

java.lang.RuntimeException: Canvas: trying to draw too large(120422400bytes) bitmap.

daedric7 avatar Nov 08 '22 11:11 daedric7

I have experienced crashes with large photos as well, so I can confirm this happens. I think this should be one of the next Android tickets to be worked on. The app should never crash.

(Side note: You could have linked any other large photo ... Like, one from NASA or Wikipedia...)

binwiederhier avatar Nov 08 '22 19:11 binwiederhier

This is a little trickier than expected, because I cannot catch a RuntimeException, and whether the exception is raised depends on your system and your available RAM. Figuring out whether an image is too large is non-trivial, because a JPG of 4480x6720 (as you posted) expends to 120 MB in memory. I could limit previews to 1 MB JPG files, or to certain dimensions, but it's not a sure thing.

The only way to solve this would be to bring in a library that intelligently does the image loading in a memory efficient way. And I' not sure I want to bring in a library just for that.

(I am working on this though, and will hopefully have a solution soon)

binwiederhier avatar Nov 19 '22 14:11 binwiederhier

Perhaps the server, upon receiving a image could create a thumbnail. The whole file and the thumbnail could be passed to the Android app, but only the thumb would be previewed.

In anycase, i find this issue peculiar. The tested smartphone is not weak on memory, 8GB.

daedric7 avatar Nov 19 '22 23:11 daedric7

This is the corresponding stack trace I got from the Google Play console for this issue; and from reproducing it locally:

Exception java.lang.RuntimeException: Canvas: trying to draw too large(120422400bytes) bitmap.
  at android.graphics.RecordingCanvas.throwIfCannotDraw (RecordingCanvas.java:266)
  at android.graphics.BaseRecordingCanvas.drawBitmap (BaseRecordingCanvas.java:94)
  at android.graphics.drawable.BitmapDrawable.draw (BitmapDrawable.java:549)
  at android.widget.ImageView.onDraw (ImageView.java:1446)
  at com.google.android.material.imageview.ShapeableImageView.onDraw (ShapeableImageView.java:198)
  at android.view.View.draw (View.java:23195)
  at android.view.View.updateDisplayListIfDirty (View.java:22062)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw (ConstraintLayout.java:1994)
  at android.view.View.draw (View.java:23198)
  at android.view.View.updateDisplayListIfDirty (View.java:22062)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.draw (View.java:23198)
  at android.view.View.updateDisplayListIfDirty (View.java:22062)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at androidx.recyclerview.widget.RecyclerView.drawChild (RecyclerView.java:5370)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.draw (View.java:23198)
  at androidx.recyclerview.widget.RecyclerView.draw (RecyclerView.java:4769)
  at android.view.View.updateDisplayListIfDirty (View.java:22062)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.draw (View.java:23198)
  at android.view.View.updateDisplayListIfDirty (View.java:22062)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw (ConstraintLayout.java:1994)
  at android.view.View.updateDisplayListIfDirty (View.java:22053)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.updateDisplayListIfDirty (View.java:22053)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.updateDisplayListIfDirty (View.java:22053)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.updateDisplayListIfDirty (View.java:22053)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.updateDisplayListIfDirty (View.java:22053)
  at android.view.View.draw (View.java:22926)
  at android.view.ViewGroup.drawChild (ViewGroup.java:4529)
  at android.view.ViewGroup.dispatchDraw (ViewGroup.java:4290)
  at android.view.View.draw (View.java:23198)
  at com.android.internal.policy.DecorView.draw (DecorView.java:819)
  at android.view.View.updateDisplayListIfDirty (View.java:22062)
  at android.view.ThreadedRenderer.updateViewTreeDisplayList (ThreadedRenderer.java:682)
  at android.view.ThreadedRenderer.updateRootDisplayList (ThreadedRenderer.java:688)
  at android.view.ThreadedRenderer.draw (ThreadedRenderer.java:786)
  at android.view.ViewRootImpl.draw (ViewRootImpl.java:4579)
  at android.view.ViewRootImpl.performDraw (ViewRootImpl.java:4290)
  at android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:3517)
  at android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:2286)
  at android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:8948)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1231)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1239)
  at android.view.Choreographer.doCallbacks (Choreographer.java:899)
  at android.view.Choreographer.doFrame (Choreographer.java:832)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1214)
  at android.os.Handler.handleCallback (Handler.java:942)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:201)
  at android.os.Looper.loop (Looper.java:288)
  at android.app.ActivityThread.main (ActivityThread.java:7893)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:936)

binwiederhier avatar Nov 27 '22 17:11 binwiederhier

I managed to monkey patch this using this fix: https://github.com/binwiederhier/ntfy-android/commit/d09fdb2ff259f9e4d8cf83b515d37f4d3062d6b3 - The workaround is from here: https://stackoverflow.com/a/53334563/1440785

It works by checking if the loaded image is > 100 MB in memory and exits out gracefully if it does. The image is not rendered in that case. I could have gone for a "resize and display" option, but I think this is sufficient for now.

Will be in the next Android release.

binwiederhier avatar Nov 27 '22 17:11 binwiederhier