AsyncTask is bad and you should feel bad

There are a lot of mixed feelings about AsyncTask. All this stems from the fact that developers new to Android jump to AsyncTask when they need to do something on a thread, without fully understanding the Android lifecycle and how to properly handle it. This often leads to problems with leaked activities, state not updating, and a variety of other issues. Because of this you’ll likely get told from experienced developers that you should not be using AsyncTask.

In this article I’ll give an example of how you can use AsyncTask and avoid these issues.

I know that the general consensus is that AsyncTask isn’t meant for long-running tasks, but sometimes you just want a simple way to perform a simple task (no matter how long it might take). In my opinion, AsyncTask is perfect for this.

You’re doing it wrong

A common way for new developers to use AsyncTask could be something like the following:

public class MainActivity extends Activity {

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }

  // Somewhere the AsyncTask is started

  public class MyAsyncTask extends AsyncTask<Void, Void, String> {

    @Override protected String doInBackground(Void... params) {
      // Do work
      return result;
    }

    @Override protected void onPostExecute(String result) {
      Log.d("MyAsyncTask", "Received result: " + result);
    }
  }
}

The problem with this is that the AsyncTask has an implicit reference to the enclosing Activity. If a configuration change happens the Activity instance that started the AsyncTask would be destroyed, but not GCd until the AsyncTask finishes. Since Activities are heavy this could lead to memory issues if several AsyncTasks are started. Another issue is that the result of the AsyncTask could be lost, if it’s intended to act on the state of the Activity.

This leads to two issues we have to fix:

There’s some examples out there that uses Activity#onRetainNonConfigurationInstance to pass the AsyncTask instance between Activities on configuration changes, and then set a callback field on the AsyncTask with the current Activity instance. I’m not particularly fond of that approach since then you have to keep track of your AsyncTasks to make sure they’re all passed properly to the new instance. In a Fragment you’d use Fragment#setRetainInstance to achieve the same, but you don’t necessarily want to do that.

Otto to the rescue

My favorite approach is to use an event bus - I prefer Otto.

Basically what we’re going to do is

Using Otto is quite simple. We have to use it as a singleton, so create a small helper class for this

public class MyBus {

  private static final Bus BUS = new Bus();

  public static Bus getInstance() {
    return BUS;
  }
}

For this example the result will just be a String, so the event we post is as simple as this

public class AsyncTaskResultEvent {

  private String result;

  public AsyncTaskResultEvent(String result) {
    this.result = result;
  }

  public String getResult() {
    return result;
  }
}

Then we have to create an AsyncTask that does some work, then posts the result to the event bus. In this case it’ll sleep for a random amount of time.

public class MyAsyncTask extends AsyncTask<Void, Void, String> {

  @Override protected String doInBackground(Void... params) {
    Random random = new Random();
    final long sleep = random.nextInt(10);
    try {
      Thread.sleep(sleep * 1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return "Slept for " + sleep + " seconds";
  }

  @Override protected void onPostExecute(String result) {
    MyBus.getInstance().post(new AsyncTaskResultEvent(result));
  }
}

Our activity layout will just be a button the starts an AsyncTask

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

  <Button
      android:id="@+id/button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Start AsyncTask"/>
</LinearLayout>

And finally we have our Activity that starts the AsyncTask and later receives the result. It’s important to remember to unregister the Activity once it’s destroyed, otherwise it’ll be leaked as well.

public class MainActivity extends Activity {

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {
        new MyAsyncTask().execute();
      }
    });

    MyBus.getInstance().register(this);
  }

  @Override protected void onDestroy() {
    MyBus.getInstance().unregister(this);
    super.onDestroy();
  }

  @Subscribe public void onAsyncTaskResult(AsyncTaskResultEvent event) {
    Toast.makeText(this, event.getResult(), Toast.LENGTH_LONG).show();
  }
}

And that’s it. The AsyncTask has no hard reference to the Activity, so there’s no memory leaking. If a configuration change happens we’ll automatically be notified of the result in the new Activity instance since it’s registered to the event bus.

Now, you might ask: What happens if the AsyncTask finishes between onDestroy of on Activity and onCreate of the next? While doInBackground can certainly finish at this time, onPostExecute is called by posting a message to the main thread Looper (or message queue). Since onDestroy and onCreate happens within the same message, the onPostExecute message can only be handled after both has happened - solving the issue.

Closing words

AsyncTask is not the only place where an event bus can come in handy. If you use services, it can be a hassle to get results back to the activity. An event bus solves this issue as well.