AsyncTask Missteps

AsyncTask comes up in conversation quite often around the office, usually because of a failed code review or some related bug that’s been discovered. It’s one of the most well understood classes on the Android platform, and yet it’s also one of the hardest classes to use correctly. Even advanced Android developers get it wrong.

In the following discussion, we’ll take a look at a few snippets of code, touch on some of their problems, and arrive (hopefully) at a pattern of AsyncTask usage that’s suitable for most cases.

Let’s start with a fairly typical example.

public class MyActivity extends Activity 
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        // Find views and assign them to member variables.
 
        new AsyncTask<void, void,="" string="">()
        {            
            @Override
            protected String doInBackground(Void... params)
            {
                // Do some long running task.
                return result;
            }
 
            @Override
            protected void onPostExecute(String result)
            {
                // Update UI
            }
        }.execute();
    }
    // Various View member variables.
}
</void,>

This seems fairly straightforward. When our Activity is created, we launch an AsyncTask to perform some background work, which is performed in doInBackground. When the background work is finished, doInBackground returns a String which is used by onPostExecute to safely update the UI.

We’re using the AsyncTask here exactly as I’ve seen it used in production code. UI elements are being updated safely. Work is being done in the background. We’re in no danger of an ANR message. So what exactly is the problem here?

The problem isn’t what happens inside the AsyncTask. The problem is what happens outside of it. Specifically, what happens if the Activity is destroyed while the AsyncTask is running?

Imagine this scenario – the Activity is created, the AsyncTask is instantiated and executed, and then the screen orientation changes. When screen orientation changes under normal circumstances, the original Activity is destroyed and a new one is created. This means another AsyncTask will be instantiated and the background work will start all over again.

But that’s not the worst of it. The original AsyncTask will continue to run until it’s finished. And once it’s finished, it’ll update UI elements that aren’t visible anymore. And because the original AsyncTask still has an implicit outerclass pointer to the original Activity, the original Activity can’t be garbage collected until the original AsyncTask is finished. Change the screen orientation a bunch of times on a resource intensive Activity and you risk running out of memory.

There’s also a limit on the number of AsyncTasks you can have running at once. For Activities with smaller footprints, you’ll most likely run out of AsyncTasks before you start to see memory problems appear.

Does that make you uncomfortable? It certainly should.

So how do we work around these issues? Let’s address these one at a time, starting with the simplest – the implicit pointer to the outer class. This can be solved by making the AsyncTask class static, which also means it can no longer be anonymous.

Removing Implicit Outerclass Pointers

public class MyActivity extends Activity 
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        // Find views and assign them to member variables.
 
        MyTask task = new MyTask();
        task.execute();
    }
 
    static class MyTask extends AsyncTask<void, void,="" string="">
    {
        @Override
        protected String doInBackground(Void... params)
        {
            // Do some long running task.            
            return result;
        }
 
        @Override
        protected void onPostExecute(String result)
        {
            // Need to update UI. But how?
        }
    }    
    // Various View member variables.
}
</void,>

So far so good. Our AsyncTask will no longer keep a reference to the Activity. But now we can’t update the UI when the task completes, which kind of defeats the purpose. How do we fix this? By giving back an Activity reference to the AsyncTask. “Wait, isn’t that what we tried to eliminate by making the class static?” Well, yes. But we wanted to remove the implicit reference, which is something we couldn’t change at runtime. With something a bit more explicit, we have direct control over when the reference comes and goes.

public class MyActivity extends Activity 
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        // Find views and assign them to member variables.
 
        m_task = new MyTask();
        m_task.m_activity = this;
        m_task.execute();
    }
 
    @Override
    public void onDestroy()
    {
        super.onDestroy();
        m_task.m_activity = null;
    }
 
    static class MyTask extends AsyncTask<void, void,="" string="">
    {
        @Override
        protected String doInBackground(Void... params)
        {
            // Do some long running task.            
            return result;
        }
 
        @Override
        protected void onPostExecute(String result)
        {
            if (m_activity != null)
            {
                // Update UI
            }
        }
 
        MyActivity m_activity = null;
    }
 
    private MyTask m_task = null;    
    // Various View member variables.
}
</void,>

That’s better. In the above example we remove the Activity reference in the AsyncTask when the Activity is destroyed. The AsyncTask can continue to run without preventing our Activity from being garbage collected.

Concurrent AsyncTask Limitations

Now let’s briefly discuss the problem of concurrent AsyncTasks. If you weren’t aware that there’s an upper limit to concurrent AsyncTasks, Google “AsyncTask limits” and you’ll find plenty of information about it. We need to control the lifetime of our AsyncTask instance. What that boils down to is canceling the task and letting the garbage collector deal with it. Canceling a task is a two-step process. First, we set the AsyncTask’s cancelled flag in the Activity’s onDestroy method.

m_task.cancel(false);

This alone doesn’t actually cancel the task. It has no effect on the background process if the task chooses to ignore it. So we also need to regularly check the return value of isCancelled() inside of our AsyncTask’s doInBackground method and bail out appropriately. This allows us to terminate that background process cleanly.

if (isCancelled())
{
    return null;
}

Notice that we passed false to the AsyncTask’s cancel method. If you pass true, it interrupts the thread and can leave things in a particular awkward state. See the Thread class’s interrupt method for details.

One AsyncTask To Rule Them All

We still have a problem of a new AsyncTask being created and executed every time our Activity is destroyed and recreated. In most cases, creating a new AsyncTask instance is unnecessary. We often only need the AsyncTask to query a database, make a web service request, or read some file once. So it’d be nice if we could leverage a single AsyncTask no matter how many times our Activity is temporarily destroyed/created.

I’ve seen some folks set the android:configChanges attribute in the Activity’s XML declaration inside the app manifest just to avoid the redundant AsyncTask issue. And while it’s certainly doable, it’s somewhat of an obscure solution. android:configChanges is meant to solve other problems. That approach may confuse other developers who will need to maintain your code. It also makes the UI a bit inflexible because the default behavior may actually be behavior you want.

I’ve also seen some folks address the issue using invisible Fragments. Again, it works. But it’s somewhat obscure if you’re using a Fragment oriented approach just to solve this issue. Make sure your Fragment usage is appropriate for your UI design. Never use a sledgehammer when a rubber mallet will do.

The approach I like best is mentioned in this StackOverflow thread and in Hervé Guihot’s book “Pro Android Apps Performance Optimization”.

public class MyActivity extends Activity 
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        // Find views and assign them to member variables.
 
        m_task = (MyTask) getLastNonConfigurationInstance();
        if (m_task != null)
        {
            m_task.m_activity = this;
        }
        else
        {
            m_task = new MyTask();
            m_task.m_activity = this;
            m_task.execute();
        }
    }
 
    @Override
    public void onDestroy()
    {
        super.onDestroy();
 
        m_task.m_activity = null;
 
        if (this.isFinishing())
            m_task.cancel(false);
    }
 
    @Override
    public Object onRetainNonConfigurationInstance() 
    {
        return m_task;
    }
 
 
    static class MyTask extends AsyncTask<void, void,="" string="">
    {
        @Override
        protected String doInBackground(Void... params)
        {
            // Do some long running task. We need to make sure
            // we peridically check the return value of isCancelled().
            return result;
        }
 
        @Override
        protected void onPostExecute(String result)
        {
            if (m_activity != null)
            {
                // Update UI
            }
        }
 
        MyActivity m_activity = null;
    }
 
    private MyTask m_task = null;
 
    // Various View member variables.
}
</void,>

The key piece here is the use of the onRetainNonConfigurationInstance and getLastNonConfigurationInstance methods to reuse an AsyncTask instance between build up and tear down of the Activity. Using this approach will always result in a single AsyncTask being created and used.

My code above differs a bit from that mentioned in the StackOverflow discussion and in Guihot’s book. One notable difference is that I check to see if the Activity is actually finishing in the onDestroy method. If it is, we cancel the task. isFinishing will return true if this Activity is being dismissed as a result of going backwards in the Activity stack.

Our Final Approach

There is still a bug here. Can you spot it? What happens if the Activity has been destroyed and recreated after the AsyncTask has completed? Our UI doesn’t get updated. The solution to this is fairly simple and illustrated in the final version of our Activity below.

public class MyActivity extends Activity 
{
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        // Find views and assign them to member variables.
 
        m_task = (MyTask) getLastNonConfigurationInstance();
        if (m_task != null)
        {
            m_task.m_activity = this;
            if (m_task.m_isFinished)
                m_task.updateUI();
        }
        else
        {
            m_task = new MyTask();
            m_task.m_activity = this;
            m_task.execute();
        }
    }
 
    @Override
    public void onDestroy()
    {
        super.onDestroy();
 
        m_task.m_activity = null;
 
        if (this.isFinishing())
            m_task.cancel(false);
    }
 
    @Override
    public Object onRetainNonConfigurationInstance() 
    {
        return m_task;
    }
 
 
    static class MyTask extends AsyncTask<void, void,="" string="">
    {
        @Override
        protected String doInBackground(Void... params)
        {
            // Do some long running task. We need to make sure
            // we peridically check the return value of isCancelled().
            return result;
        }
 
        @Override
        protected void onPostExecute(String result)
        {
            m_result = result;
            m_isFinished = true;
            updateUI();
        }
 
        public void updateUI()
        {
            if (m_activity != null)
            {
                // Update UI using m_result
            }            
        }
 
        // These should never be accessed from within doInBackground()
        MyActivity m_activity = null;
        boolean m_isFinished  = false;
        String m_result = null;
    }
 
    private MyTask m_task = null;
 
    // Various View member variables.
}
</void,>

I don’t claim that this pattern is a one-sized fits all solution. But I think it covers most cases quite well. There are a few things I avoided discussing here, such as dealing with progress dialogs and how to design tasks that can be used by multiple activities. I didn’t want to add too much complexity to the examples. And I think those sorts of things should be fairly intuitive once you’ve achieved a certain degree of comfort with AsyncTask

16 thoughts on “AsyncTask Missteps”

  1. Nice presentation of the problem. There’s still a subtle race condition in your last version of AsyncTask. It’s in this code:


    m_task.m_activity = this;
    if (m_task.m_isFinished)
    m_task.updateUI();

    The problem is that the task can finish in between the first two lines. The result will be two calls to the task’s updateUI() method: once by the task itself and once from the activity’s onCreate() method. Depending on what updateUI() does, the effects may be anything from totally innocuous to catastrophic.

    1. Indeed, the AsyncTask can finish during the onCreate method. However, there’s no a race condition on m_isFinished because it’s being set to true/false on the UI thread…the same thread onCreate executes on. onPostExecute can’t execute concurrently with onCreate. If the task completes at any point during the execution of onCreate, onPostExecute won’t actually be executed until after onCreate returns.

      Does that make sense?

  2. First off, thanks for taking the time to read my article on AsyncTask!

    I like the fact that your approach avoids the use of Fragments (an overly complicated API, for my simpler taste), but the real advantage of my design using them was encapsulation. A single line in the front-end code hooked up the callback scheduler to the Activity.

    That seems to be lost here: the technique is simpler, which is always a plus, but it clutters front-end code with back-end concerns. It also forces Activities to either extend from MyActivity (losing the ability to extend other classes), or (much worse) duplicate code.

    Overall, I think I still prefer the Fragment approach, even if more complex. The complexity is hidden behind the scenes, and is completely reusable, so there’s no per-use overhead.

  3. I like this approach, but what about the fact that onRetainNonConfigurationInstance() has been officially deprecated? Are you currently working around that somehow, or are you moving to Fragments?

    Thanks for a great article!

  4. Well, let me say a huge thanks for this article. Far the best one to understand on the Net. I also like the “m_” prefixes that I use too (I also use “p_” for function arguments and “l_” for local variables).

    I would also be interested in an update of this article which describes what to change to use current design patterns without deprecated methods. Thank you again.

  5. Thanks, Yany! But I probably won’t be updating this article covering newer methods any time soon. It’s partly a time management thing. I have a lot on my plate at the moment. And revisiting AsyncTask best practices is kind of low on my list. Another reason is that I’m maintaining software that has to work on older APIs. So that’s the world I’m still kind of living in at the moment.

    I highly recommend you take a look at Santiago Lezica’s Fragment approach. He uses newer APIs to accomplish the same thing. The link is in the article above.

  6. I like your idea of getting back the idea using the onRetainNonConfigurationInstance() callback, but I remember reading about an issue similar to this one and Romain Guy himself mentioned the use of Java SoftReference to make the AsyncTask’s reference to the Activity not an issue when getting the latter GC. You obviously don’t need this with your solution, only if you wanted to make a new AsyncTask and kill the old one when changing configuration, but I’m just curious about how no one remembers about Soft References… myself included. I remembered them because I’ve gone through a lot of old and new Google I/O sessions recently.

  7. Shane,

    Can you please update this post to address the deprecated warning?

    The method getLastNonConfigurationInstance() from the type Activity is deprecated

    1. @SuppressWarnings(“deprecation”)

      🙂 I kid, I kid. Kind of.

      It’s tough because if you want to support older APIs, these are the tools you have at your disposal. getLastNonConfigurationInstance was deprecated in API 13 (Honeycomb MR2). I keep getting flack for using deprecated APIs, but if Gingerbread users are part of your market (they’re still ~40% of the overall market), then that API level should be your base. As a developer, you have three options.

      1) @SuppressWarnings(“deprecation”)

      2) Use the fragment approach described in the article I link to in the above text. This involves including the Android support library as part of your project.

      3) Forget about older devices entirely and use a different approach only supported in the newer APIs.

  8. Hello. First of all thanks for good article!

    I understand everything except final approach. How this can be happed?
    Lets imagine that our asynctask finished before onRetainNonConfigurationInstance() call, so callback onPostExecute() will be posted on main messages queue, and after onRetainNonConfigurationInstance() , onDestroy(), onCreate() , our onPostExecute() still there, on the main messages queue, and it will be executed.

  9. hello

    Thanks pour this tip. There s something i don t understand. How is it possible to update UI ? asynchTask is a static class so you can t access to the view which is defined in MyActivity, and this one is not a static class ? how do you do this ?

    1. There are a couple of ways to do this.

      One that comes to mind is to create a public method in your Activity class that updates the UI appropriately. For example, say it’s called “updateViews()”. If you look at my last code snippet above, you’ll notice my AsyncTask has an updateUI() method and a member variable called m_activity. From within there, you can just call m_activity.updateViews(). That should do what you want.

      Does that make sense?

      1. Hi Shane,

        Thank you so much. It works ….. well. I have had some difficulties with a callback on a spinner running without any reasons, but it seems to be an Android bug, and I ve solved it with a boolean. Perhaps do you know a website where i could find some templates for tasks that can be used by multiple activities ? I can t find a good one !

        thx

        1. I might be misunderstanding your question, but what you’re asking for sounds more like something you’d want to accomplish using a Service. If you’re unfamiliar with Services, they’re non-UI things that have lifecycles independent of Activities. Take a look at the API docs. I think this is probably the direction you want to go.

  10. Does not work in my case… It always start new task and never use the existing one
    Here is the code:

    in onCreate method:
    z_task = (ZipTask) getLastNonConfigurationInstance();
    if (z_task != null)
    {
    Log.d(“ZIPTask”,”Use existing task”);
    z_task.m_activity = this;
    if (z_task.m_isFinished)
    z_task.updateUI();
    }
    else
    {
    z_task = new ZipTask();
    z_task.m_activity = this;
    z_task.execute();
    Log.d(“ZIPTask”,”Start zip task”);
    }

    in Activity Class:
    @Override
    public void onDestroy()
    {
    super.onDestroy();

    z_task.m_activity = null;

    if (this.isFinishing())
    z_task.cancel(false);
    }

    @Override
    public Object onRetainNonConfigurationInstance()
    {
    return z_task;
    }

    static class ZipTask extends AsyncTask
    {
    @Override
    protected ZipFile doInBackground(Void… params)
    {
    // Do some long running task.
    File zipFile = new File(sdcard_path + “MFCache”, “MFMap.zip”);
    try {
    return new ZipFile(zipFile);
    } catch (IOException E) {
    Log.d(“MapTiles”, “Map file not found”);
    }
    return null;
    }

    @Override
    protected void onPostExecute(ZipFile result)
    {
    m_result = result;
    m_isFinished = true;
    Log.d(“ZIPTask”,”End zip task”);
    updateUI();
    }

    public void updateUI()
    {
    if (m_activity != null)
    {
    m_activity.setMapFile(m_result);
    }
    }

    // These should never be accessed from within doInBackground()
    MyActivity m_activity = null;
    boolean m_isFinished = false;
    ZipFile m_result = null;
    }

    private ZipTask z_task = null;

    1. Hi, Daniel!

      I’m not entirely sure what’s going on for you. I’m going to have to carve out some time and research this. It’s been a while since this article was written (4 years as of this comment) and I’m not sure what’s changed since then.

      Just curious, which version(s) of Android are you targeting?

      If you figure out what’s happening before I do, please leave a comment here so others can know as well. Thank you!

      -Shane

Comments are closed.