88

I read a lot on how to save my instance state or how to deal with my activity getting destroyed during screen rotation.

There seem to be a lot of possibilities but I haven't figured out which one works best for retrieving results of an AsyncTask.

I have some AsyncTasks that are simply started again and call the isFinishing() method of the activity and if the activity is finishing they wont update anything.

The problem is that I have one Task that does a request to a web service that can fail or succeed and restarting the task would result in a financial loss for the user.

How would you solve this? What are the advantages or disadvantages of the possible solutions?

Janusz
  • 187,060
  • 113
  • 301
  • 369
  • 1
    See my answer [here](http://stackoverflow.com/a/12303649/265521). You might also find [this information about what `setRetainInstance(true)` actually does](http://stackoverflow.com/a/12642237/265521) helpful. – Timmmm Oct 04 '12 at 11:57
  • what I would do is simply implement a local service that performs the processing (in a thread) that your asyncTask is doing. To display the results, broadcast the data to your activity. Now the activity is only responsible for showing data and the processing is *never* interrupted by a screen rotation. – Someone Somewhere Aug 24 '13 at 22:11
  • What about using AsyncTaskLoader instead of AsyncTask?? – Sourangshu Biswas Nov 11 '15 at 12:35

13 Answers13

46

You can check out how I handle AsyncTasks and orientation changes at code.google.com/p/shelves. There are various ways to do it, the one I chose in this app is to cancel any currently running task, save its state and start a new one with the saved state when the new Activity is created. It's easy to do, it works well and as a bonus it takes care of stopping your tasks when the user leaves the app.

You can also use onRetainNonConfigurationInstance() to pass your AsyncTask to the new Activity (be careful about not leaking the previous Activity this way though.)

Ragunath Jawahar
  • 19,513
  • 22
  • 110
  • 155
Romain Guy
  • 97,993
  • 18
  • 219
  • 200
  • 1
    i tried it and rotating during book search interrupts and gives me less results than when not rotating, too bad – max4ever Jan 31 '12 at 10:31
  • 1
    I could not find a single usage of AsyncTask in that code. There is a class UserTask that looks similar. Does this project predate AsyncTask? – devconsole May 13 '13 at 13:54
  • 7
    AsyncTask came from UserTask. I originally wrote UserTask for my own apps and later turned it into AsyncTask. Sorry about that I forgot it got renamed. – Romain Guy May 13 '13 at 23:25
  • @RomainGuy Hi, hope you are well. According to your code 2 requests are sent to server although at first task is cancelled but it's not successfully cancelled. I don't know why. Would you please tell me is there any way to resolve this. – iamcrypticcoder May 26 '14 at 18:48
10

This is the most interesting question I've seen regarding to Android!!! Actually I've been already looking for the solution during the last months. Still haven't solved.

Be careful, simply overriding the

android:configChanges="keyboardHidden|orientation"

stuff is not enough.

Consider the case when user receives a phone call while your AsyncTask is running. Your request is already being processed by server, so the AsyncTask is awaiting for response. In this moment your app goes in background, because the Phone app has just come in foreground. OS may kill your activity since it's in the background.

Vit Khudenko
  • 28,288
  • 10
  • 63
  • 91
7

My first suggestion would be to make sure you actually need your activity to be reset on a screen rotation (the default behavior). Every time I've had issues with rotation I've added this attribute to my <activity> tag in the AndroidManifest.xml, and been just fine.

android:configChanges="keyboardHidden|orientation"

It looks weird, but what it does it hand off to your onConfigurationChanged() method, if you don't supply one it just does nothing other than re-measure the layout, which seems to be a perfectly adequate way of handling the rotate most of the time.

Jim Blackler
  • 22,946
  • 12
  • 85
  • 101
  • 5
    But that will prevent the Activity from changing the layout. And therefore forces the user to use his device in a specific orientation dictated by your application and not by his needs. – Janusz Apr 12 '10 at 09:52
  • @Janusz, it doesn't do that. The Activity is reoriented to the landscape/portrait dimensions and redrawn by the phone. It just stops it resetting the Activity, calling onCreate() etc. This is if you are using a general layout for all dimensions (as you should in my opinion). – Jim Blackler Apr 12 '10 at 10:40
  • why isn't that the default? Then all the activity killing problems would never happen? I always was suspicious why this is neccessary – Janusz Apr 12 '10 at 11:06
  • this is great :) thanks this hint was missing in all the posts on the issue... Why isn't that a default... – Janusz Apr 12 '10 at 11:09
  • 77
    Using this technique prevents you from easily using configuration specific resources. For instance, if you want your layout or drawables or strings or whatever to be different in portrait and landscapes, you'll want the default behavior. Overriding the config change should only be done in very specific cases (a game, a web browser, etc.) and not out of laziness or convenience because you are constraining yourself. – Romain Guy Apr 12 '10 at 18:55
  • 38
    Well that's just it, Romain. "if you want your layout or drawables or strings or whatever to be different in portrait and landscapes, you'll want the default behavior", I believe that is a much rarer use case than you envisage. What you call "very specific cases" is most developers I believe. Using relative layouts that work in all dimensions is best practice and it's not that hard. Talk of laziness is highly misguided, these techniques are to improve user experience not reduce development time. – Jim Blackler Apr 12 '10 at 21:04
  • I always build my UserInterface in a way that works in landscape and portrait. It is less work for me if there are changes in the layout later on and I haven't encountered a case where it doesn't works yet. Therefor I think the not destroying the activity would be a very reasonable default behaviour. – Janusz Apr 13 '10 at 08:19
  • 2
    I have found that this works perfect for LinearLayout, but when using RelativeLayout it does not redraw the layout correctly when switching to landscape mode (at least not on the N1). See this questions: http://stackoverflow.com/questions/2987049/how-to-refresh-an-android-relativelayout-when-orientation-changes-without-restart – JohnRock Jun 08 '10 at 11:59
  • 9
    I agree with Romain (he knows what he is talking about, he develops the OS). What happens when you want to port your application to a tablet and your UI look horrible when stretched? If you take the approach of this answer you will need to recode your whole solution because you went with this lazy hack. – Austyn Mahoney Sep 02 '11 at 18:36
  • Afaik its bad practice, using a retainFragment should be the way to go! – Thkru Aug 30 '13 at 09:38
  • in one of my activities, the only UI on the screen is a ProgressDialog, and this turns out to be the ideal solution – dldnh Mar 23 '15 at 00:47
  • @JimBlackler I have a question. I have asyncTask is running and before completing user changes from Portrait to landscape. I don't want to start new AsynTask() and also i am getting back some response from server that I want to pass to activity. Note: I don't want to use below approaches `android:configChanges="keyboardHidden|orientation"` or `onConfigurationChanged`. How can I pass data from `onPostExecute` to new created Activity. Can I solve it by using MVP ? Is it possible without using `Fragment`? Please help or any kind of lead would be really appreciated. Thanks in advance. – rupesh Jun 08 '18 at 12:06
  • @RomainGuy Please refer the above the comment and help me out if you have any solution. Thanks in advance. – rupesh Jun 08 '18 at 12:40
  • @AustynMahoney If we do the layout properly, it wont look stretched. – Sreekanth Karumanaghat Dec 27 '19 at 10:19
  • @JimBlackler I agree with you, that won't be a requirement most of the time. More than that I am just wondering why are these things not properly documented, or are they, if some one provides a link it would be too helpful. – Sreekanth Karumanaghat Dec 27 '19 at 10:20
  • Is it just me who feels that Android makes some very trivial cases (rotation with an async task running) like this too complicated to Handle? – Sreekanth Karumanaghat Dec 27 '19 at 10:22
6

Why don't you always keep a reference to the current AsyncTask on the Singleton provided by Android?

Whenever a task starts, on PreExecute or on the builder, you define:

((Application) getApplication()).setCurrentTask(asyncTask);

Whenever it finishes you set it to null.

That way you always have a reference which allows you to do something like, onCreate or onResume as appropriated for your specific logic:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

If it's null you know that currently there is none running!

:-)

neteinstein
  • 17,529
  • 11
  • 93
  • 123
  • Will this work? Has anyone tested this? Will the task still get killed by the system if a phone call interruption happens or if we navigate to a new activity and then go back? – Robert Jun 29 '11 at 09:41
  • 6
    `Application` instance has its own life cycle - it can be killed by OS too, so this solution may cause a hard-to-reproduce bug. – Vit Khudenko Sep 14 '11 at 11:30
  • 7
    I thought: if Application is killed, the whole app is killed (and thus, all AsyncTasks too)? – manmal Dec 18 '11 at 09:25
  • I think that application can be killed without all asynctasks being (very rare). But @Arhimed with some easy-to-do verifications on the start and end of each asynctask you can avoid the bugs. – neteinstein Dec 18 '11 at 18:35
5

The most proper way to this is to use a fragment to retain the instance of the async task, over rotations.

Here is a link to very simple example making it easy to follow integrate this technique into your apps.

https://gist.github.com/daichan4649/2480065

user2342491
  • 96
  • 1
  • 3
  • Here's another tutorial that makes use of retained fragments: http://blogactivity.wordpress.com/2011/09/01/proper-use-of-asynctask/ – devconsole May 27 '13 at 12:38
3

To my point of view, it's better to store asynctask via onRetainNonConfigurationInstance decoupling it from the current Activity object and binding it to a new Activity object after the orientation change. Here I found a very nice example how to work with AsyncTask and ProgressDialog.

Yury
  • 20,618
  • 7
  • 58
  • 86
3

In Pro android 4. author has suggest a nice way, that you should use weak reference.

Weak reference note

hqt
  • 29,632
  • 51
  • 171
  • 250
2

Android : background processing/Async Opeartion with configuration change

To maintain the states of async opeartion during background process: you can take an help of fragments.

See the following steps :

Step 1: Create a headerless fragment let say background task and add a private async task class with in it.

Step 2 (Optional Step): if you want to put a loading cursor on top of your activity use below code:

Step 3: In your main Activity implement BackgroundTaskCallbacks interface defined in step 1

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

@Override
public void dismiss() {
    super.dismiss();
}

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}

Lennart
  • 9,657
  • 16
  • 68
  • 84
1

One thing to consider is whether the result of the AsyncTask should be available only to the activity that started the task. If yes, then Romain Guy's answer is best. If it should be available to other activities of your application, then in onPostExecute you can use LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

You will also need to make sure that activity correctly handles situation when broadcast is sent while activity is paused.

Community
  • 1
  • 1
Juozas Kontvainis
  • 9,461
  • 6
  • 55
  • 66
1

Have a look at this post. This Post involves AsyncTask performing long running operation and memory leak when screen rotation happens both in one sample application. The sample app is available on the source forge

Vahid
  • 1,625
  • 1
  • 18
  • 33
0

My solution.

In my case i've got a chain of AsyncTasks with the same context. Activity had an access only to first one. To cancel any running task i did the following:

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

Task doInBackground():

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

Activity onStop() or onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}
Pitt90
  • 31
  • 3
0
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}
Atif Mahmood
  • 8,882
  • 2
  • 41
  • 44
0

you can also add android:configChanges="keyboardHidden|orientation|screenSize"

to your manifest example i hope it help

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
eng mohamed emam
  • 549
  • 6
  • 13