subsampling-scale-image-view
subsampling-scale-image-view copied to clipboard
Crash with RejectedExecutionException when used in RecyclerView
Hello, I'm getting a lot of reports in production from crashes happening with this error:
Fatal Exception: java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@1ec5e0b rejected from java.util.concurrent.ThreadPoolExecutor@dcd94e8[Running, pool size = 17, active threads = 17, queued tasks = 128, completed tasks = 1856]
Complete stacktrace here
Fatal Exception: java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@1ec5e0b rejected from java.util.concurrent.ThreadPoolExecutor@dcd94e8[Running, pool size = 17, active threads = 17, queued tasks = 128, completed tasks = 1856]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2085)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:848)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1394)
at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:651)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.execute(SubsamplingScaleImageView.java:1881)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.refreshRequiredTiles(SubsamplingScaleImageView.java:1312)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.preDraw(SubsamplingScaleImageView.java:1359)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.checkReady(SubsamplingScaleImageView.java:1196)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.onTileLoaded(SubsamplingScaleImageView.java:1698)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.access$5900(SubsamplingScaleImageView.java:72)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView$TileLoadTask.onPostExecute(SubsamplingScaleImageView.java:1685)
at com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView$TileLoadTask.onPostExecute(SubsamplingScaleImageView.java:1628)
at android.os.AsyncTask.finish(AsyncTask.java:695)
at android.os.AsyncTask.access$600(AsyncTask.java:180)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6826)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
Note that the details of the ThreadPoolExecutor vary depending on the source, although it is always Running and has a pool size equal to the active threads.
Expected behaviour
It raises a catchable error, or queue the job for another time.
Actual behaviour
It crashes without any way to prevent it ourselves.
Steps to reproduce
I'm using this library to display selected photos from user input. The only tricky part is that I use it heavily in a RecyclerView. So at a time, there can be dozens of SubsamplingImageView where I would first use recycle() to make sure the list would be updated, then setImage() with local images.
The SubsamplingImageView itself has isPanEnabled, isQuickScaleEnabled & isZoomEnabled set to false, with setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP).
Affected devices
It happens across all devices consistently with our users' mobile park, although it seems it doesn't happen on Android 10 nor 11 (as a result of an internal change to the framework I suppose).
| Devices | Android versions |
|---|---|
![]() |
![]() |
Affected images
N/A
I'm happy to provide more information or to help if needed, although I couldn't reproduce on my side I have enough reports to have some more information.
Seems related to #501 (now closed) & #519 (although for another issue).
I suggest providing your own executor with setExecutor if your app is doing so much work on the default executor that the queue is exhausted.
@davemorrissey Thanks for your answer.
I'll look into as I'm not very familiar with AsyncTask's API, but isn't that just going to hide the problem if I provide a bigger Executor? Instead of failing gracefully and reporting the error in onImageLoadError() for example.
If you provide an executor with an unbounded queue you wouldn't get this error. I'm assuming your app does a lot of other work with AsyncTasks or on the same default executor it uses, because it's very hard to imagine SSIV by itself could queue 128 tasks and not cause an out of memory error. It's designed for no more than three instances at once.
You can make your own Executor instance that catches and reports rejected execution, or possibly provides a different implementation of RejectedExecutionHandler.

