-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Description
TLDR; Glide should just log and cancel the load instead of crashing when the activity is destoryed since there is no point in loading anything. The user of the library shouldn't have to explicitly handle this situation.
Glide Version/Integration library (if any): 'com.github.bumptech.glide:glide:3.6.1'
Device/Android Version: Any/Android 5.0+
Issue details/Repro steps/Use case background:
This issue is related to issue #138. When making a call on glide if the activity has been destroyed it will throw an IllegalArgumentException. The following code in the RequestManagerRetriever.java class is responsible:
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static void assertNotDestroyed(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
}
}
This is causing a crash in my crashlytics console for a small percentage of my users. There is likely some kind of timing issue where the user hits home as they are scrolling a big list and the activity is cleaned up which results in this crash. I could fix this issue by wrapping glide and running this code myself and simply not calling glide in this situation. However I believe this is the wrong approach, the library shouldn't be crashing in this situation and instead should log and do nothing.
If the activity is destroyed then the view that is being displayed which the image is being displayed is not visible and as a result no exception should be thrown. Instead glide should log and simply not show the image. Users of the library should not have to explicitly handle situations like these, it makes more sense to just handle it in the library.
Glide load line:
Glide.with(context).load(url).bitmapTransform(new GrayscaleTransformation(getContext())).into(imageView);
Layout XML: not relevant
Stack trace / LogCat:
Fatal Exception: java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:134)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:102)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:87)
at com.bumptech.glide.Glide.with(Glide.java:620)
at com.myapp.myapp$24.run(MyAppClass.java:977)
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:5431)
at java.lang.reflect.Method.invoke(Method.java)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:914)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:707)
Activity
TWiStErRob commentedon Dec 9, 2015
Thanks very much for the detailed report!
What does
com.myapp.myapp$24
do?What triggers it to be scheduled for later execution?
michaelrhughes commentedon Dec 9, 2015
It sounds like what you're getting at is that I can fix the issue on my end instead of making a change in the library. However I do not believe this is the best solution since people use a simplified library like Glide because they don't have to worry about corner cases like this. In my opinion Glide should handle this directly. Obviously I'm no expert on Glide's code so I could be off base here.
To answer your question a network call comes in which then posts to run the code on the UI thread. Since I am in a view there I can do:
What do you guys think?
TWiStErRob commentedon Dec 9, 2015
You're right. I kind of agree with you, it would be simpler if Glide ignored this, but at the same time I don't, here's why. I think in general it's just good to know what's going on in your code. Glide ignoring this would hide a potential bug in your code, that you would consider a feature, but at the same time for someone else it may be a good thing that Glide brings it up. The problem is that there isn't really a good mid-way between log and a crash. Most people ignore logging, especially because Glide doesn't log much unless you turn it on explciitly. On the other hand if your app crashes, you're forced to find out why and fix the bug. Even if you could get away with not doing so.
I think in general if you're going from back->UI thread it's a good thing to check if the UI is still alive. Why would you spend any time updating something that doesn't exist, or not visible and never will be visible. Especially taking away from the precious render time and battery life. In this simple case you may not be doing much, but it's really easy to think of similar use cases which are resource heavy. So regarding your implementation idea, I think you can just ignore the whole
Runnable
($24) if youractivity.isFinishing()
; or even better (together with isFinishing) try to cancel the network request if the activity dies.By the way, to give a little background why that check is in place: Glide attaches a fragment to the activity/fragment if you're using the corresponding
with(...)
. This fragment is used to subscribe to lifecycle events so requests can be automatically paused/resumed and cancelled. See http://stackoverflow.com/a/32887693/253468. Oh, and for this fragment to be added, as a normal one, the activity has to be in good condition. Follow theGlide.with
call chain in code to see more.michaelrhughes commentedon Dec 9, 2015
Thanks for the detailed reply and spending time to look into this, I really appreciate it. It's amazing when devs support their own libraries with this level of support.
Glide is really powerful because, like you are saying, it can respect the lifecycle of activities and fragment. An activity being destroyed is a lifecycle event. Therefore I would argue it should respect and properly handle when your activity is destroyed.
I get where you are coming from but if Glide were to silently fail here the only additional overhead (in my app) would be a single post request which is negligible. This is a very small corner case in my code (13 crashes in 15000 sessions), I wouldn't have found it without crashlytics. Realistically adding ~6 lines of code (the activity destroyed check) isn't a big deal to work around it but I felt that Glide would be better as a library if it handled this situation.
TWiStErRob commentedon Dec 9, 2015
I'm not even a full dev on Glide, I'm just having fun here on Glide issues having my daily dose of brain training. Sam's is the sole owner and main maintainer of Glide. I commit only minor things sometimes.
Considering what I just said, let's see what @sjudd's opinion is on ignoring a load on a destroyed activity with a warn log.
michaelrhughes commentedon Dec 9, 2015
Thanks!
Guang1234567 commentedon Dec 10, 2015
@michaelrhughes
@TWiStErRob
Maybe you can solve this problem instead of using "activity.isFinishing()".
TWiStErRob commentedon Dec 10, 2015
@lihanguang hmm, interesting idea. I'm not sure how it helps though. I mean I don't know whether this will:
These last two (2-3) sound bad, and my guess is that Glide will do 2. when called after destroy.
Guang1234567 commentedon Dec 11, 2015
@TWiStErRob
"Crash when activity is destroyed" means @michaelrhughes call Glide.with(this) after "activity is destoryed", so you can call Glide.with(this) at the certain moment that activity is not destoryed. In my solution above, "the certain moment" is onCreate()
Q1: ignore any loads.
Answer: Donot worry about that. Because i cache the RequestManager instance in onCreate(), then use this instance all the time . You can see the glide demo("com.bumptech.glide.samples.gallery.RecyclerAdapter") .
Q2: go through with them unnecessarily
Answer: "go through with them" means you donnot need to lazy get the instance of RequestManager that attached at the current Context( Activity). So i think "go through with them" in some case is necessarily.
For example: in a adapter ("com.bumptech.glide.samples.gallery.RecyclerAdapter")
Q3: or just delay the crash for a later time.
Answer: no delay crash, because see Q1 answer
TWiStErRob commentedon Dec 11, 2015
@lihanguang so it won't do any of what I said, simply because the RequestManager is paused when activity is destroyed, that's a good one :) It also eagerly initializes the magic fragment which prevents this and possible other weirdnesses.
I wonder if @sjudd did this in the samples knowingly. My guess would be that it just made sense to cache it and some APIs need to get it passed: for example the adapter you quoted benefits because it can be used in Activity/Fragment/or even with AppContext by removing the Glide.with dependency from inside adapter.bind and just passing the actual request manager.
@michaelrhughes did you try this?
sjudd commentedon Dec 12, 2015
Passing the RequestManager in rather than constantly calling Glide.with does a number of things:
It's not intended to avoid this particular assertion, though it can be useful in limited cases where the assertion can't be avoided due to bugs in how fragments are handled in the support libraries (typically affects deeply nested Fragment hierarchies and/or fragment pagers).
The assertion exists because, aside from the fragment issues I mentioned, it never makes sense to try to start an image load while an Activity is being destroyed. It often indicates that you're doing something else dangerous, like starting an image load in an AsyncTask callback for a task that may finish after onDestroy is called and that isn't cancelled.
More practically, because Glide.with() returns a RequestManager the caller then uses, we'd have to create a special mock or pass along some state that indicates that the load should actually be ignored all the down to the into() call, which would be hard to maintain.
Guang1234567 commentedon Dec 17, 2015
Can using "RequestManager" to solve @michaelrhughes 's problem?
TWiStErRob commentedon Dec 17, 2015
@lihanguang Yes it was a good idea, though he didn't respond yet.
39 remaining items
jt-gilkeson commentedon Nov 21, 2017
Just as a heads up for people who run into this (perhaps on older versions of the library and aren't going to upgrade right away) - some adapters may be saving the layoutInflater (or use some other "optimization") which after rotation can inflate a viewHolder with the wrong context (which despite being incorrect - works for the most part). If you then call something like imageView.getContext() and use it with Glide you can run into this issue.
tangyiwu commentedon Jan 30, 2018
I think the way is that we pause request before activity destroyed.
AllanWang commentedon Jan 30, 2018
The crashes come from calling
Glide.with(this)
on an invalid context. Doing what you mentioned above will pause requests that are no longer necessary, but it doesn't solve the crash issue.farhan commentedon Feb 14, 2018
I am using following util function before loading any image
garywzh commentedon Apr 4, 2018
definitely should not crash it, a log is enough
toby78 commentedon Jan 11, 2019
Why don't you have a config variable for Glide in the app to tell which behaviour it should have?
e.g.
a) default setting - the simple version for people who want a single code line to load an image without thinking about all other potential issues in more complex situations
b) current behaviour, loading of image is not stopped automatically and 20 additional code lines are required at some place in your own code
ntpanchal commentedon Apr 25, 2019
Thank you so much. saved my time. 👍
ArcherEmiya05 commentedon Dec 12, 2019
So until today there is still no proper fix on Glide library side? I also still getting a lot of crash report with this even handling it side by side. The context may not be null but if the activity gets destroyed then boom app will still close. If I were a user of the app that would be unpleasant experience because not all user will comprehend what happened. We do understand the reason of throwing exception and its good, but Android platform is too fragmented where each device behaves differently depending on what modification on OS manufacturer does. At least add configuration like
cancelOnDestroy(true)
or just log.wtf it because in one app there will be hundreds scenario of needing to load an images and no one would really not want to do try catch on each of those request hahaha that would be a huge boilerplate. Try catch is said to be use for unexpected behavior where we do not have any control over that situation like slow internet connection but also expensive as its redirect the system flow or behavior.oakkitten commentedon Oct 3, 2020
how about crashing only in debug builds?
yes, this problem is often an error on the part of the programmer, but as evidenced by the numerous issues this can, if rarely, happen even when your code is correct.
it is not reasonable for programmers to expect that Glide will crash in the case when an activity is destroyed. it is also not reasonable for programmers to be aware of all the corner cases where their code calling Glide is called after the destruction of an activity. in other words, a good programmer can have their good code crash in production because Glide thinks that it is reasonable to crash in expected circumstances.
dharmishthawallis2020 commentedon Dec 11, 2020
I have solved my problem using below method. May it help someone. I m using Glide to load image in Runnable Using Handler.
guidovezzoni commentedon Dec 28, 2020
I'm using this context extension before loading:
sahandnayebaziz commentedon Jun 16, 2021
This crash is affecting my app where...
• The app is mainly Jetpack Compose
• The app preloads ~100 small images when it is first launched with the following code:
How can I catch this exception so the app doesn't crash? It seems like, even if I switch this to do them one-by-one, it can still crash after
Glide.with(activity)
evaluates successfully but it is still in the loading / caching stage?