Glide: You cannot start a load for a destroyed activity error

Created on 28 Mar 2016  路  14Comments  路  Source: bumptech/glide

Glide Version: 3.7.0

Integration libraries: OkHttp3

Device/Android Version: All devices

Issue details / Repro steps / Use case background:
I have a fragment that contains a RecyclerView that loads Images using Glide. When the user scrolls really fast to the bottom and quits the app, the error occurs.

Glide load line / GlideModule (if any) / list Adapter code (if any):

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (holder instanceof PostsViewHolder){
        HomeFeedPosts.data postsData = postsList.get(position);

        ((PostsViewHolder) holder).title.setText(postsData.user.username);

        Glide.with(((PostsViewHolder) holder).imagePost.getContext())
                .load(postsData.images.standard_resolution.url)
                .priority(Priority.IMMEDIATE)
                .placeholder(R.drawable.grey_placeholder)
                .into(((PostsViewHolder) holder).imagePost);

        Glide.with(((PostsViewHolder) holder).imageAvatar.getContext())
                .load(postsData.user.profile_picture)
                .priority(Priority.LOW)
                .into(((PostsViewHolder) holder).imageAvatar);


    } else if (holder instanceof ProgressBarViewHolder){
        ((ProgressBarViewHolder) holder).progressBar.setIndeterminate(true);
    }
}

Stack trace / LogCat:

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:629)
at com.saphala.gokilpedia_mobile.adapters.HomeFeedAdapter.onBindViewHolder(HomeFeedAdapter.java:86)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5465)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5498)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4735)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4611)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1988)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1384)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1347)
at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1174)
at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1031)
at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:4055)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
at android.view.Choreographer.doCallbacks(Choreographer.java:574)
at android.view.Choreographer.doFrame(Choreographer.java:543)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5113)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:609)                                                                                    
at dalvik.system.NativeStart.main(Native Method)
question

Most helpful comment

Hmm, that's an interesting repro for this recurring issue. There's likely not a good way around it inside Glide, I suggest one of these workarounds (in descending order of quality):

  1. Pass in a Glide object: new HomeFeedAdapter(Glide.with(this))

java class HomeFeedAdapter extends Adapter { private final RequestManager glide; HomeFeedAdapter(RequestManager glide) { this.glide = glide; } @Override public void onBindViewHolder(ViewHolder holder, int position) { glide.load.... } }

This is a handy workaround, and it also makes your adapter flexible to use with fragment and activity as well; and the code cleaner: view.getContext() is usually a long way to access Glide.
See http://stackoverflow.com/a/32887693/253468 why it's even an improvement.

  1. list.setAdapter(null) in onDestroy() or onStop(), which is incovenient to do all the time
  2. Check for ((Activity)view.getContext()).isDestroyed() or similar method, which may be unsafe and is even more inconvenient to do all the time
  3. Use Glide.with(context.getApplicationContext()), see http://stackoverflow.com/a/32887693/253468 why it may not be a good idea.

All 14 comments

Hmm, that's an interesting repro for this recurring issue. There's likely not a good way around it inside Glide, I suggest one of these workarounds (in descending order of quality):

  1. Pass in a Glide object: new HomeFeedAdapter(Glide.with(this))

java class HomeFeedAdapter extends Adapter { private final RequestManager glide; HomeFeedAdapter(RequestManager glide) { this.glide = glide; } @Override public void onBindViewHolder(ViewHolder holder, int position) { glide.load.... } }

This is a handy workaround, and it also makes your adapter flexible to use with fragment and activity as well; and the code cleaner: view.getContext() is usually a long way to access Glide.
See http://stackoverflow.com/a/32887693/253468 why it's even an improvement.

  1. list.setAdapter(null) in onDestroy() or onStop(), which is incovenient to do all the time
  2. Check for ((Activity)view.getContext()).isDestroyed() or similar method, which may be unsafe and is even more inconvenient to do all the time
  3. Use Glide.with(context.getApplicationContext()), see http://stackoverflow.com/a/32887693/253468 why it may not be a good idea.

Curious: How do you "quit the app" quick enough that the items are binding too late?

There's an OnScrollListener attached to the RecyclerView that loads more data from the server as you scroll to the bottom. To reproduce this issue simply scroll really fast and press the back button.

Ah, I see, so your lazy loading delivers data to a dead activity, it's the same issue as the others: there was always a rogue async call involved.

I might be wrong on this, but doesn't the first option create a memory leak?

I think at worst there's a reference cycle, which doesn't prevent GC from doing its job.

  • Activity references RecyclerView
  • Views reference Activity (getContext)
  • RecyclerView references Adapter
  • Adapter references RequestManager
  • RequestManager references Activity (not sure)
  • Activity references RequestManager through hidden RequestManagerFragment

RequestManager is aware of the activity/fragment lifecycle so any request will be cleared when they die, see linked SO. I don't think anything will be kept after destroy as the fragment is also destroyed.

Ahh ok thanks for clearing that up! The issue is now fixed.

hii...m stuck with java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed

i have three activities in my app..so when i am starting 2nd activity there are some images which are loaded by glide in AsyncTask then i m going to further activity...again i came back to 2nd activity("no error") then i come back to 1st activity now again when i am going to 2nd activity app crashes with that exception...i tried glide.clearMemory()..but it dosent work :( please help me out

@kuldiep I can only say the usual: make sure you don't start a load when the activity is destroyed or being destroyed. You can cancel the async task when the activity ia finishing (e.g. onStop). Please open a new issue with some code and more details, if you want more help.

thanks for help....i think i come up with another solution and it works..i m just clearing glide.get(this).clearMemory in onResume, onRestart nd onDestroy methods..as of now i am not getting any error :)

@kuldiep You might as well just disable memory cache altogether (similar applies to bitmap pools): use MemoryCacheAdapter as seen in wiki; and remember, this is a not a solution you found, but a hack hiding the real problem.

ya my approach is not a solution but i'll keep your approach of stopping async task in onStop method for letter use..thanks

I think the way is that we pause request before activity destroyed.

@Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            Glide.with(this).pauseRequests();
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public void finish() {
+        Glide.with(this).pauseRequests();
+        super.finish();
+    }

I think the way is that we pause request before activity destroyed.

@Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            Glide.with(this).pauseRequests();
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public void finish() {
+        Glide.with(this).pauseRequests();
+        super.finish();
+    }

This will pause all request or only those requests which are mapped to this context?
Eg: If a start glide in fragment and pause request in activity will all that be paused?

Was this page helpful?
0 / 5 - 0 ratings