Best practice: AsyncTask during orientation change Best practice: AsyncTask during orientation change multithreading multithreading

Best practice: AsyncTask during orientation change


Do NOT use android:configChanges to address this issue. This is very bad practice.

Do NOT use Activity#onRetainNonConfigurationInstance() either. This is less modular and not well-suited for Fragment-based applications.

You can read my article describing how to handle configuration changes using retained Fragments. It solves the problem of retaining an AsyncTask across a rotation change nicely. You basically need to host your AsyncTask inside a Fragment, call setRetainInstance(true) on the Fragment, and report the AsyncTask's progress/results back to it's Activity through the retained Fragment.


I usually solve this by having my AsyncTasks fire broadcast Intents in the .onPostExecute() callback, so they don't modify the Activity that started them directly. The Activities listen to these broadcasts with dynamic BroadcastReceivers and act accordingly.

This way the AsyncTasks don't have to care about the specific Activity instance that handles their result. They just "shout" when they're finished, and if an Activity is around that time (is active and focused / is in it's resumed state) which is interested in the results of the task, then it will be handled.

This involves a bit more overhead, since the runtime needs to handle the broadcast, but I usually don't mind. I think using the LocalBroadcastManager instead of the default system wide one speeds things up a bit.


Here is another example of an AsyncTask that uses a Fragment to handle runtime configuration changes (as when the user rotates the screen) with setRetainInstance(true). A determinate (regularly updated) progress bar is also demonstrated.

The example is partly based on the official docs, Retaining an Object During a Configuration Change.

In this example the work requiring a background thread is the mere loading of an image from the internet into the UI.

Alex Lockwood appears to be right that when it comes to handling runtime configuration changes with AsyncTasks using a "Retained Fragment" is best practice. onRetainNonConfigurationInstance() gets deprecated in Lint, in Android Studio. The official docs warn us off using android:configChanges, from Handling the Configuration Change Yourself, ...

Handling the configuration change yourself can make it much more difficult to use alternative resources, because the system does not automatically apply them for you. This technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications.

Then there is the issue of whether one should use an AsyncTask at all for the background thread.

The official reference for AsyncTask warns ...

AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent pacakge such as Executor, ThreadPoolExecutor and FutureTask.

Alternatively one could use a service, loader (using a CursorLoader or AsyncTaskLoader), or content provider to perform asynchronous operations.

I break the rest of the post into:

  • The Procedure; and
  • All the code for the above procedure.

The Procedure

  1. Start with a basic AsyncTask as an inner class of an activity (it doesn't need to be an inner class but it will probably be convenient to be). At this stage the AsyncTask does not handle runtime configuration changes.

    public class ThreadsActivity extends ActionBarActivity {    private ImageView mPictureImageView;    private class LoadImageFromNetworkAsyncTask                          extends AsyncTask<String, Void, Bitmap> {        @Override        protected Bitmap doInBackground(String... urls) {            return loadImageFromNetwork(urls[0]);        }        @Override        protected void onPostExecute(Bitmap bitmap) {            mPictureImageView.setImageBitmap(bitmap);        }    }    /**     * Requires in AndroidManifext.xml     *  <uses-permission android:name="android.permission.INTERNET" />     */    private Bitmap loadImageFromNetwork(String url) {        Bitmap bitmap = null;        try {            bitmap = BitmapFactory.decodeStream((InputStream)                                          new URL(url).getContent());        } catch (Exception e) {            e.printStackTrace();        }        return bitmap;    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_threads);        mPictureImageView =            (ImageView) findViewById(R.id.imageView_picture);    }    public void getPicture(View view) {        new LoadImageFromNetworkAsyncTask()            .execute("http://i.imgur.com/SikTbWe.jpg");    }}
  2. Add a nested class RetainedFragment that extends the Fragement class and doesn't have it's own UI. Add setRetainInstance(true) to the onCreate event of this Fragment. Provide procedures to set and get your data.

    public class ThreadsActivity extends Activity {    private ImageView mPictureImageView;    private RetainedFragment mRetainedFragment = null;    ...    public static class RetainedFragment extends Fragment {        private Bitmap mBitmap;        @Override        public void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            // The key to making data survive            // runtime configuration changes.            setRetainInstance(true);        }        public Bitmap getData() {            return this.mBitmap;        }        public void setData(Bitmap bitmapToRetain) {            this.mBitmap = bitmapToRetain;        }    }    private class LoadImageFromNetworkAsyncTask                    extends AsyncTask<String, Integer,Bitmap> {    ....
  3. In the outermost Activity class's onCreate() handle the RetainedFragment: Reference it if it already exists (in case the Activity is restarting); create and add it if it doesn't exist; Then, if it already existed, get data from the RetainedFragment and set your UI with that data.

    public class ThreadsActivity extends Activity {    ...    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_threads);        final String retainedFragmentTag = "RetainedFragmentTag";        mPictureImageView =                  (ImageView) findViewById(R.id.imageView_picture);        mLoadingProgressBar =                (ProgressBar) findViewById(R.id.progressBar_loading);        // Find the RetainedFragment on Activity restarts        FragmentManager fm = getFragmentManager();        // The RetainedFragment has no UI so we must        // reference it with a tag.        mRetainedFragment =          (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);        // if Retained Fragment doesn't exist create and add it.        if (mRetainedFragment == null) {            // Add the fragment            mRetainedFragment = new RetainedFragment();            fm.beginTransaction()                .add(mRetainedFragment, retainedFragmentTag).commit();        // The Retained Fragment exists        } else {            mPictureImageView                .setImageBitmap(mRetainedFragment.getData());        }    }
  4. Initiate the AsyncTask from the UI

    public void getPicture(View view) {    new LoadImageFromNetworkAsyncTask().execute(            "http://i.imgur.com/SikTbWe.jpg");}
  5. Add and code a determinate progress bar:

    • Add a progress bar to the UI layout;
    • Get a reference to it in the Activity oncreate();
    • Make it visible and invisble at the start and end of the process;
    • Define the progress to report to UI in onProgressUpdate.
    • Change the AsyncTask 2nd Generic parameter from Void to a type that can handle progress updates (e.g. Integer).
    • publishProgress at regular points in doInBackground().

All the code for the above procedure

Activity Layout.

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.example.mysecondapp.ThreadsActivity">    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical"        android:paddingBottom="@dimen/activity_vertical_margin"        android:paddingLeft="@dimen/activity_horizontal_margin"        android:paddingRight="@dimen/activity_horizontal_margin"        android:paddingTop="@dimen/activity_vertical_margin">        <ImageView            android:id="@+id/imageView_picture"            android:layout_width="300dp"            android:layout_height="300dp"            android:background="@android:color/black" />        <Button            android:id="@+id/button_get_picture"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentLeft="true"            android:layout_alignParentStart="true"            android:layout_below="@id/imageView_picture"            android:onClick="getPicture"            android:text="Get Picture" />        <Button            android:id="@+id/button_clear_picture"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignBottom="@id/button_get_picture"            android:layout_toEndOf="@id/button_get_picture"            android:layout_toRightOf="@id/button_get_picture"            android:onClick="clearPicture"            android:text="Clear Picture" />        <ProgressBar            android:id="@+id/progressBar_loading"            style="?android:attr/progressBarStyleHorizontal"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_below="@id/button_get_picture"            android:progress="0"            android:indeterminateOnly="false"            android:visibility="invisible" />    </RelativeLayout></ScrollView>

The Activity with: subclassed AsyncTask inner class; subclassed RetainedFragment inner class that handles runtime configuration changes (e.g. when the user rotates the screen); and a determinate progress bar updating at regular intervals. ...

public class ThreadsActivity extends Activity {    private ImageView mPictureImageView;    private RetainedFragment mRetainedFragment = null;    private ProgressBar mLoadingProgressBar;    public static class RetainedFragment extends Fragment {        private Bitmap mBitmap;        @Override        public void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            // The key to making data survive runtime configuration changes.            setRetainInstance(true);        }        public Bitmap getData() {            return this.mBitmap;        }        public void setData(Bitmap bitmapToRetain) {            this.mBitmap = bitmapToRetain;        }    }    private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,            Integer, Bitmap> {        @Override        protected Bitmap doInBackground(String... urls) {            // Simulate a burdensome load.            int sleepSeconds = 4;            for (int i = 1; i <= sleepSeconds; i++) {                SystemClock.sleep(1000); // milliseconds                publishProgress(i * 20); // Adjust for a scale to 100            }            return com.example.standardapplibrary.android.Network                    .loadImageFromNetwork(                    urls[0]);        }        @Override        protected void onProgressUpdate(Integer... progress) {            mLoadingProgressBar.setProgress(progress[0]);        }        @Override        protected void onPostExecute(Bitmap bitmap) {            publishProgress(100);            mRetainedFragment.setData(bitmap);            mPictureImageView.setImageBitmap(bitmap);            mLoadingProgressBar.setVisibility(View.INVISIBLE);            publishProgress(0);        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_threads);        final String retainedFragmentTag = "RetainedFragmentTag";        mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);        mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);        // Find the RetainedFragment on Activity restarts        FragmentManager fm = getFragmentManager();        // The RetainedFragment has no UI so we must reference it with a tag.        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(                retainedFragmentTag);        // if Retained Fragment doesn't exist create and add it.        if (mRetainedFragment == null) {            // Add the fragment            mRetainedFragment = new RetainedFragment();            fm.beginTransaction().add(mRetainedFragment,                                      retainedFragmentTag).commit();            // The Retained Fragment exists        } else {            mPictureImageView.setImageBitmap(mRetainedFragment.getData());        }    }    public void getPicture(View view) {        mLoadingProgressBar.setVisibility(View.VISIBLE);        new LoadImageFromNetworkAsyncTask().execute(                "http://i.imgur.com/SikTbWe.jpg");    }    public void clearPicture(View view) {        mRetainedFragment.setData(null);        mPictureImageView.setImageBitmap(null);    }}

In this example the library function (referenced above with the explicit package prefix com.example.standardapplibrary.android.Network) that does real work ...

public static Bitmap loadImageFromNetwork(String url) {    Bitmap bitmap = null;    try {        bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)                .getContent());    } catch (Exception e) {        e.printStackTrace();    }    return bitmap;}

Add any permissions that your background task requires to the AndroidManifest.xml ...

<manifest>...    <uses-permission android:name="android.permission.INTERNET" />

Add your activity to AndroidManifest.xml ...

<manifest>...    <application>        <activity            android:name=".ThreadsActivity"            android:label="@string/title_activity_threads"            android:parentActivityName=".MainActivity">            <meta-data                android:name="android.support.PARENT_ACTIVITY"                android:value="com.example.mysecondapp.MainActivity" />        </activity>