236

I download some data from internet in background thread (I use AsyncTask) and display a progress dialog while downloading. Orientation changes, Activity is restarted and then my AsyncTask is completed - I want to dismiss the progess dialog and start a new Activity. But calling dismissDialog sometimes throws an exception (probably because the Activity was destroyed and new Activity hasn't been started yet).

What is the best way to handle this kind of problem (updating UI from background thread that works even if user changes orientation)? Did someone from Google provide some "official solution"?

grebulon
  • 7,697
  • 5
  • 42
  • 66
fhucho
  • 34,062
  • 40
  • 136
  • 186
  • 4
    My [**blog post**](http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html) on this topic might help. It's about retaining long-running tasks across configuration changes. – Alex Lockwood Sep 07 '13 at 16:24
  • 1
    This [**question**](http://stackoverflow.com/q/3357477/844882) is related as well. – Alex Lockwood Jan 15 '14 at 04:53
  • Just FTR there's a related mystery here .. http://stackoverflow.com/q/23742412/294884 – Fattie May 19 '14 at 16:25

8 Answers8

338

Step #1: Make your AsyncTask a static nested class, or an entirely separate class, just not an inner (non-static nested) class.

Step #2: Have the AsyncTask hold onto the Activity via a data member, set via the constructor and a setter.

Step #3: When creating the AsyncTask, supply the current Activity to the constructor.

Step #4: In onRetainNonConfigurationInstance(), return the AsyncTask, after detaching it from the original, now-going-away activity.

Step #5: In onCreate(), if getLastNonConfigurationInstance() is not null, cast it to your AsyncTask class and call your setter to associate your new activity with the task.

Step #6: Do not refer to the activity data member from doInBackground().

If you follow the above recipe, it will all work. onProgressUpdate() and onPostExecute() are suspended between the start of onRetainNonConfigurationInstance() and the end of the subsequent onCreate().

Here is a sample project demonstrating the technique.

Another approach is to ditch the AsyncTask and move your work into an IntentService. This is particularly useful if the work to be done may be long and should go on regardless of what the user does in terms of activities (e.g., downloading a large file). You can use an ordered broadcast Intent to either have the activity respond to the work being done (if it is still in the foreground) or raise a Notification to let the user know if the work has been done. Here is a blog post with more on this pattern.

James Wald
  • 13,626
  • 5
  • 52
  • 63
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 8
    Thanks a lot for your great answer to this common issue! Just to be thorough, you might add to the step #4 that we have to detach (set to null) the activity in the AsyncTask. This is well illustrated in the sample project, though. – Kevin Gaudin Oct 30 '10 at 15:40
  • Thank you, Mark. It works perfektly for me. And thank you for Sample Project, without it I wouldn't understand it so well. – Tima Nov 29 '10 at 09:04
  • Thanks it helped a lot. How can this be implemented with a list view. Can you pleas look this stackoverflow.com/questions/4583737/ – Labeeb Panampullan Jan 03 '11 at 11:04
  • I am puzzled by this - in your sample code, you call detach() in onRetainNonConfigurationInstance(). This method is not called (I presume) when user finihes the Activity by navigating back or when the Activity is finished by the system due to low memory. Isn't it better to detach() in onDestroy()? – fhucho Feb 13 '11 at 16:24
  • @fhucho: I don't want to detach in those cases. Hence, I don't detach in those cases. – CommonsWare Feb 13 '11 at 17:33
  • @fhucho: More specifically, this is a sample covering configuration changes. It is not a sample covering mid-run closing of the activity or anything else. If you feel you want to `detach()` in your own code for the `onDestroy()` path, go right ahead. In terms of why I detach in `onRetainNonConfigurationInstance()`, that is because that was the first point when I *could* detach and follow the rules outlined by Ms. Hackborn. Whether detaching makes sense in other scenarios (e.g., `onDestroy()`) is up to you with that scenario. – CommonsWare Feb 13 '11 at 17:42
  • 3
    But what if I need to have an access to Activity's members? – Eugene Apr 12 '11 at 18:27
  • What if your Activity has multiple kinds of tasks? – Andrew Sep 20 '11 at 15:23
  • @Andrew: create multiple `AsyncTask` implementations – CommonsWare Sep 20 '11 at 16:01
  • I'm mainly referring to your implementation of onRetainNonConfigurationInstance and call to getLastNonConfigurationInstance. It seems only 1 object can be passed through. – Andrew Sep 20 '11 at 16:16
  • 3
    @Andrew: Create a static inner class or something that holds onto several objects, and return it. – CommonsWare Sep 20 '11 at 16:19
  • 11
    `onRetainNonConfigurationInstance()` is deprecated and the suggested alternative is to use `setRetainInstance()`, but it doesn't return an object. Is it possible to handle `asyncTask` on configuration change with `setRetainInstance()`? – Indrek Kõue Mar 28 '12 at 08:11
  • 10
    @SYLARRR: Absolutely. Have the `Fragment` hold the `AsyncTask`. Have the `Fragment` call `setRetainInstance(true)` upon itself. Have the `AsyncTask` only talk to the `Fragment`. Now, on a configuration change, the `Fragment` is not destroyed and recreated (even though the activity is), and so the `AsyncTask` is retained across the configuration change. – CommonsWare Mar 28 '12 at 11:06
  • @CommonsWare is AsyncTaskLoader now the recommended way of doing this instead of your solution? – numan salati May 18 '12 at 22:07
  • @itfrombit: Only if you are using a `ContentProvider` from an `Activity`. – CommonsWare May 18 '12 at 23:13
  • @CommonsWare i am taking about AsynTaskLoader which can load from any source and not a CursorLoader which loads from a ContentProvider. – numan salati May 24 '12 at 01:44
  • @CommonsWare It will be even more helpful if you update some rules about AsyncTaskLoaders as well. – S.D. Aug 24 '12 at 08:42
  • 3
    @numan No, `AsyncTaskLoader` is not the recommended way of doing this because it is not the same thing at all. An `AsyncTask` performs a single, asynchronous operation. An `AsyncTaskLoader` performs asynchronous loads for an `Activity` and/or `Fragment`, retains loaded data across configuration changes, and automatically performs new loads when changes to the data source are detected. If you need to perform a single, one-time, potentially expensive operation, then it wouldn't make sense to use an `AsyncTaskLoader`... use an `AsyncTask` instead. – Alex Lockwood Jan 11 '13 at 16:36
  • Just a question: Why Step #3: When creating the AsyncTask, supply the current Activity to the constructor ? Why not Create an interface IInterface and implement it in the Acitivity / fragment and then simply call IInterface.SomeMethod() (which updates the View ) from onProgressUpdate ? – theSpyCry May 24 '13 at 15:39
  • @PaN1C_Showt1Me: Because the `AsyncTask` does not have access to anything implementing that interface, except via step #3. – CommonsWare May 24 '13 at 21:33
  • Well I have meant it like this: not to provide the Activity to the constructor directly, but just the interface that the activity (or some other object) implements. They could then decide what to do, everyone would have its own implementation. public MyAsyncTask(IMyInterface myInterface) – theSpyCry May 27 '13 at 08:53
  • 1
    @PaN1C_Showt1Me: You are certainly welcome to pass in an interface implementation if you prefer. – CommonsWare May 27 '13 at 09:26
  • I just added a simple way to avoid using deprecated methods, I hope it gets accepted – Shereef Marzouk Mar 06 '14 at 12:09
  • hmm, I didn't have a copy :( – Shereef Marzouk Mar 06 '14 at 13:13
  • @CommonsWare Hi CW, if you happen to see this comment... When bringing up a full-screen waiting spinner, I followed this **extremely simple solution**: http://stackoverflow.com/a/9123421/294884 Surely, there is some hidden downside there? Is it "too good to be true"? THX – Fattie May 19 '14 at 14:55
  • @JoeBlow Downsides are discussed in the comments to this answer: http://stackoverflow.com/a/3614089/3558355 – Chuck Batson Jul 15 '14 at 14:17
  • @CommonsWare - This solution was exactly what I needed, but as Indrek mentioned above, `onRetainNonConfigurationInstance()` is now deprecated and unusable in an AppCompatActivity. I saw you mentioned how to implement this with `setRetainInstance()` in a Fragment, but is there a way to pull this off without embedding the AsyncTask in a Fragment, i.e., in an AppCompatActivity? – NoChinDeluxe Jan 25 '16 at 18:27
  • 1
    @NoChinDeluxe: "but is there a way to pull this off without embedding the AsyncTask in a Fragment, i.e., in an AppCompatActivity?" -- personally, I only ever use `AsyncTask` in retained fragment. Outside of that pattern, I will fork an ordinary thread and use an event bus (e.g., greenrobot's EventBus) to get results back to an activity. It's possible that there are patterns for successful `AsyncTask` usage that do not involve retained fragments, though I don't know what they are. – CommonsWare Jan 26 '16 at 10:00
  • @CommonsWare - I found a solution. AppCompatActivity has its own special version of this method called `onRetainCustomNonConfigurationInstance()`. I was able to use that with your steps above and it worked as intended. Thanks for your reply. – NoChinDeluxe Jan 26 '16 at 15:26
13

The accepted answer was very helpful, but it doesn't have a progress dialog.

Fortunately for you, reader, I have created an extremely comprehensive and working example of an AsyncTask with a progress dialog!

  1. Rotation works, and the dialog survives.
  2. You can cancel the task and dialog by pressing the back button (if you want this behaviour).
  3. It uses fragments.
  4. The layout of the fragment underneath the activity changes properly when the device rotates.
Timmmm
  • 88,195
  • 71
  • 364
  • 509
  • The accepted answer is about static classes (not members). And those _are necessary_ to avoid that the AsyncTask has a (hidden) pointer to the outer class instance which becomes a memory leak on destroying the activity. – Bananeweizen Jan 06 '13 at 07:10
  • Yeah not sure why I put that about static members, since I actually also used them... weird. Edited answer. – Timmmm Jan 06 '13 at 22:47
  • Could you please update your link? I really need this. – Romain Pellerin Mar 02 '14 at 23:13
  • Sorry, haven't got around to restoring my website - I'll do it soon! But in the mean time it is basically the same as the code in this answer: https://stackoverflow.com/questions/8417885/android-fragments-retaining-an-asynctask-during-screen-rotation-or-configuratio/12303649#12303649 – Timmmm Mar 05 '14 at 21:44
  • 1
    The link is bogus; just leads to a useless index with no indication of where the code is. – FractalBob Jul 17 '15 at 05:10
  • Sorry, changed the link to point to the other answer with all the code. – Timmmm Jul 17 '15 at 07:16
  • Can you please post the content of your blog post within your answer? Outgoing links are kind of frowned upon as a way of answering. – Diti Aug 16 '16 at 12:48
9

I've toiled for a week to find a solution to this dilemma without resorting to editing the manifest file. The assumptions for this solution are:

  1. You always need to use a progress dialog
  2. Only one task is performed at a time
  3. You need the task to persist when the phone is rotated and the progress dialog to be automatically dismisses.

Implementation

You will need to copy the two files found at the bottom of this post into your workspace. Just make sure that:

  1. All your Activitys should extend BaseActivity

  2. In onCreate(), super.onCreate() should be called after you initialize any members that need to be accessed by your ASyncTasks. Also, override getContentViewId() to provide the form layout id.

  3. Override onCreateDialog() like usual to create dialogs managed by the activity.

  4. See code below for a sample static inner class to make your AsyncTasks. You can store your result in mResult to access later.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

And finally, to launch your new task:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

That's it! I hope this robust solution will help someone.

BaseActivity.java (organize imports yourself)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}
Oleg Vaskevich
  • 12,444
  • 6
  • 63
  • 80
4

Did someone from Google provide some "official solution"?

Yes.

The solution is more of an application architecture proposal rather that just some code.

They proposed 3 design patterns that allows an application to work in-sync with a server, regardless of the application state (it will work even if the user finishes the app, the user changes screen, the app gets terminated, every other possible state where a background data operation could be interrumpted, this covers it)

The proposal is explained in the Android REST client applications speech during Google I/O 2010 by Virgil Dobjanschi. It is 1 hour long, but it is extremely worth watching.

The basis of it is abstracting network operations to a Service that works independently to any Activity in the application. If you're working with databases, the use of ContentResolver and Cursor would give you an out-of-the-box Observer pattern that is convenient to update UI without any aditional logic, once you updated your local database with the fetched remote data. Any other after-operation code would be run via a callback passed to the Service (I use a ResultReceiver subclass for this).

Anyway, my explanation is actually pretty vague, you should definititely watch the speech.

Christopher Francisco
  • 15,672
  • 28
  • 94
  • 206
2

While Mark's (CommonsWare) answer does indeed work for orientation changes, it fails if the Activity is destroyed directly (like in the case of a phone call).

You can handle the orientation changes AND the rare destroyed Activity events by using an Application object to reference your ASyncTask.

There's an excellent explanation of the problem and the solution here:

Credit goes completely to Ryan for figuring this one out.

KRP
  • 294
  • 7
  • 22
SMBiggs
  • 11,034
  • 6
  • 68
  • 83
1

After 4 years Google solved the problem just calling setRetainInstance(true) in Activity onCreate. It will preserve your activity instance during device rotation. I have also a simple solution for older Android.

Singagirl
  • 465
  • 1
  • 3
  • 11
  • 1
    The problem people observer happens because Android destroys an activity class at rotation, keyboard extension and other events, but an async task still keeps a reference for the destroyed instance and tries to use it for UI updates. You can instruct Android to do not destroy activity either in manifest or pragmatically. In this case an async task reference remains valid and no problem observed. Since rotation can require some additional work as reloading views and so on, Google doesn't recommend to preserve activity. So you decide. – Singagirl Feb 08 '15 at 03:01
  • Thanks, I knew about the situation but not about setRetainInstance(). What I don't understand is your claim that Google used this to solve the issues asked in the question. Can you link source of information? Thanks. – Redoman Feb 10 '15 at 09:06
  • http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance%28%29 – Singagirl Feb 10 '15 at 21:16
  • onRetainNonConfigurationInstance() This function is called purely as an optimization, and you must not rely on it being called. < From the same source: https://developer.android.com/reference/android/app/Activity#onRetainNonConfigurationInstance() – Dhananjay M Jul 26 '18 at 05:21
0

This is my solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

Basically the steps are:

  1. I use onSaveInstanceState to save the task if it is still processing.
  2. In onCreate I get the task if it was saved.
  3. In onPause I discard the ProgressDialog if it is shown.
  4. In onResume I show the ProgressDialog if the task is still processing.
skuntsel
  • 11,624
  • 11
  • 44
  • 67
Gotcha
  • 1
  • 1
0

you should call all activity actions using activity handler. So if you are in some thread you should create a Runnable and posted using Activitie's Handler. Otherwise your app will crash sometimes with fatal exception.

xpepermint
  • 35,055
  • 30
  • 109
  • 163