Let me begin by saying that AsyncTask
is an amazing piece of abstraction. With almost no additional effort, you can schedule a task to run in a background thread (or thread pool, just as easily) and have it post progress and results back to the calling thread.
It has, however, one tiny little problem. Or, in the words of a fellow StackOverflow contributor, it’s “massively flawed”.
Improper use
Whenever you fire an AsyncTask
from an Activity
, which is mostly the point, there’s a chance that by the time onPostExecute()
is called the original context for the task no longer exists.
If, during the background execution, the Activity
instance is destroyed and recreated in the event of a configuration change (which always happens by default), you’ll be left with a stale reference to a useless Activity
object. Take this, for example:
private Button mMyButton = new Button(); // ... protected void onPostExecute(Long result) { mMyButton.setText("Finished execution"); }
The call to mMyButton.setText()
is bound to the Activity
instance that fired the AsyncTask
. Say the user does something weird and unexpected, like rotating his phone, and the configuration change forces a full recreation: setText()
will be invoked upon a Button
object that only exists in your callback, doing nothing.
Working around this issue is, of course, entirely possible — a couple of workarounds are mentioned in the question I linked above. Most approaches, however, break the abstraction, and the magic of AsyncTask
is lost.
Fragments to the rescue
Using Android’s 3.0 Fragments API, available for versions 1.6+ in the Android Compatibility Package, we can create an extra layer around AsyncTask
that fixes the problem while preserving the abstraction.
A Fragment
can outlive its parent Activity
through configuration changes by way of setRetainInstance(true)
. Even better, overriding the proper Fragment
event callbacks, it’s possible to distinguish between a recreation and a real destruction, as well as knowing for sure when an Activity
is ready to receive events.
We’ll see how to create a Fragment
to manage our AsyncTask
instances, and ensure a live Activity
instance at the time onPostExecute()
is called. The mechanism is completely independent from the specifics of the background work, so it’s simple to create a full abstraction that gives a safe execution environment for callbacks and preserves the ease of use of AsyncTask
.
A TaskManagerFragment
The basic idea is as follows. I say basic because the following code eschews a lot of necessary precautions and stylish methods for the sake of brevity. Everything is (to my knowledge, at least) properly taken care of in the actual implementation.
- Create a
Fragment
with two additional members: aboolean
to reflect whether the parentActivity
is ready for events, and aList
to enqueue callbacks triggered when it’s not.protected Boolean mReady = true; protected List<Runnable> mPendingCallbacks = new LinkedList<Runnable>();
- Ensure our
Fragment
is retained, overridingonCreate()
:@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); }
- Track the parent
Activity
life-cycle withFragment
callbacks:@Override public void onDetach() { super.onDetach(); mReady = false; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mReady = true; }
- Provide a method that allows us to enqueue a
Runnable
to be executed whenever theFragment
deems appropiate (which could be right now):public void runWhenReady(Runnable runnable) { if (mReady) getActivity().runOnUiThread(runnable); else mPendingCallbacks.add(runnable); }
- Ensure all the
Runnable
objects that were marked as pending when we weren’t ready are executed the moment we are. To achieve that, we’ll rewrite our previousonActivityCreated()
callback:@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mReady = true; int pendingCallbacks = mPendingCallbacks.size(); while (pendingCallbacks-- > 0) getActivity().runOnUiThread(mPendingCallbacks.remove(0)); }
Simple, right? Packing our callbacks into Runnable
objects and using a Fragment
such as the above to schedule their execution, code that affects an Activity
will run only when the Activity
is ready for it. Let’s take a look at a properly managed callback:
public class MyActivity extends FragmentActivity { private TaskManagerFragment mManager; @Override public void onCreate(Bundle savedInstanceState) { // ... mManager = new TaskManagerFragment(); getSupportFragmentManager().beginTransaction() .add(mManager, "TaskManagerFragment") .commit(); // ... } public void fireTask() { new AsyncTask<Void, Void, Void>() { protected Void doInBackground(Void... params) { // ... } protected void onPostExecute(Void result) { mManager.runWhenReady(new Runnable() { @Override public void run() { mMyButton.setText("Finished execution"); } }); }; }.execute(); }
Works like a charm and looks… absolutely horrible. Who wants his front-end Activity
code littered with background execution management logic?
Preserving the abstraction
Since an Activity
‘s FragmentManager
is publicly accessible, it’s completely possible to move all of that logic outside. We can put it into a class that behaves exactly like AsyncTask
, but takes care of synchronizing its callbacks through our TaskManagerFragment
.
Unfortunately, extending the AsyncTask
class is not the way to go, since some of its methods are final
. We have to use composition rather than inheritance, losing a nice parking spot in the class hierarchy but gaining full control.
This post is already quite long as it is, so I will not go into detail here. A complete implementation of such a class, named ManagedAsyncTask
, is available in the repository. I tried to mimic the regular AsyncTask
interface as much as possible, so before we take a look at how you’d actually use this Fragment
in your code, let’s go back to an unsafe AsyncTask
for comparison:
public void fireTask() { new AsyncTask<Void, Void, Void>() { protected Void doInBackground(Void... params) { // ... } protected void onPostExecute(Void result) { mMyButton.setText("Finished execution"); }; }.execute(); }
Nothing out of the ordinary. Now, let’s take a look at a perfectly safe task managed with TaskManagerFragment
:
public void fireTask() { new ManagedAsyncTask<Void, Void, Void>(this) { protected Void doInBackground(Void... params) { // ... } protected void onPostExecute(Void result) { ((MyActivity) getActivity()).b.setText("Finished execution"); }; }.execute(); }
There we go! Thanks to the Fragment
API, this turned out to be incredibly unobtrusive, allowing us to fully preserve the abstraction layer around the background worker. There’s little more than a hint in our Activity
code of the underlying context book-keeping, seen in the two minor differences between the snippets:
ManagedAsyncTask
takes aFragmentActivity
as construction parameter.- Inside the callbacks,
getActivity()
is used instead ofMyActivity.this
(or direct member access).
It’s a pity that you have to cast the return of getActivity()
, but it was either that or using another object/adding a fourth generic parameter.
If you come up with any improvements for TaskManagerFragment
or ManagedAsyncTask
, or find bugs/errors, please contact me.
Saludos!
It seems to me that this approach replicates the functionality granted by the new AsyncTaskLoader library.
However, I am only today learning about android Fragments and could be incorrect.
You are right, the behavior is quite similar. The interface, however, is very different.
Personally, I find it uncomfortable to use the Loader interfaces for one-time background tasks. It feels like a lot of overhead, writing all the code around the LoaderManager, when all I want to communicate is “do this, don’t block me, and tell me when you’re done”. With AsyncTask, you can use an anonymous class right there where you need it, and be done with it.
I don’t think the Loader interface is better or worse. I think Loader and AsyncTask represent different things, semantically speaking, and their interfaces differ accordingly. I would use a Loader when I expect it to live as long as the Activity, and perform tasks during all that time, and an AsyncTask when I simply want something done.
Rule of thumb: if the real information you would get from your AsyncTask comes from progress updates, use a Loader. If you don’t care about progress updates, and want to fire a thread to do something and be notified when it’s done, use an AsyncTask.
Of course, it’s mostly personal taste. I would like to hear your thoughts on the matter!
I’ll have to work a bit more with Fragments and Loaders before I have any solid opinions.
At the moment, I do not entirely agree with your rule of thumb. Most of the Loader examples that I have seen (not that there are many) are typically used for simple actions that would otherwise block. Anything more simple than a single web request could probably be run directly on the UI thread.
The current example I am looking at: http://android.codeandmagic.org/2011/07/android-fragments-saving-state-and-screen-rotation/ – does not seem that much more cumbersome than an AsyncTask. However, I’m not really keen on using a handler to dismiss the dialog.
Thanks for this excellent tutorial. I was just switching from showDialog() to using fragments with dialogs. onRetainNonConfigurationInstance() is not possible in FragmentActivity. So I’m converting my background task to fragment based. And ManagedAsyncTask will be very handy.
I have one question. I’m missing the publishProgress() method for the ManagedAsyncTask. I tried to add it myself. But it is not allowed because it is a final method from AsynTask. Any ideas how to solve this?
I found another problem. When I want to display a progress dialog in onPreExecute() method I get a crash due to the fact that getActivity() is null. I solved this by replacing this:
protected void onPreExecute() {
mManager.runWhenReady(new Runnable() {
public void run() {
ManagedAsyncTask.this.onPreExecute();
}
});
return;
}
Same as all the other methods which should be running on ui thread.
This doesn’t work because onPreExecute() is executed after doInBackground()!
It most certainly is not executed after doInBackground.
[…] 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 […]
Thank you. This also seems to work fine with ActionBar Sherlock so far.
Thank you very much! This gives me a new idea on how to handle thread and activity life cycle properly.
A Great Tutorial! Thankyou! XD
Nice tutorial! Could you please give me an example on how to use this in Activity? Thank you.
Perfect article! Thank you very much
Excellent job!
Easy to understand and to implement rotation-stable progress bar. Thank you.
[…] Edit: Loaders are a 3.0 feature available in the compatibility library. If you’re not willing to add the dependency, you can always fall back to AsyncTask, in which case you could take a look at this. […]
[…] You could use a headless fragment, you’ll delegate the completion logic to the fragment (as a Runnable) which will run or hold until an Activity is attached. The headless Fragment could be thought of as a callback manager. A great example can be found here. […]
[…] brings in a whole new world of trouble. I recommend you take a look at […]
[…] to queue the tasks incase one of them is taking a lot of memory, so I researched and worked with Proper use of AsyncTask, but this had no effect and I still got the out of memory […]
[…] to queue the tasks incase one of them is taking a lot of memory, so I researched and worked with Proper use of AsyncTask, but this had no effect and I still got the out of memory […]
[…] See this blog post. Maybe you should try the […]
Awesome article! You took Vogella’s idea of headless Fragments and gave it meat and bones.
[…] headless fragments – Fragments without a UI. You basically have to re-implement the AsyncTaskLoader framework so that defeats the point. […]
Hello Mr. Lezica – Many thanks for posting this excellent use of AsyncTask with headless fragments!
One thing that isn’t clear to me about using this is how to cancel one of these tasks, especially after a configuration change. Clearly, if one keeps a reference to the ManagedAsyncTask derived object, it can be canceled using that object, but that isn’t a good idea, since the reference is destroyed on config change. One could retrieve the TaskManagerFragment object from the fragment manager, but it doesn’t provide a way to access tasks it is managing.
It appears that for this purpose, making the manager object derive from Fragment rather than the task itself makes canceling in this context impossible. Perhaps your classes aren’t designed to support cancel after a config change?
Thanks,
Mark Peters
d
g
Excellent post. I was checking continuously this
weblog and I am inspired! Extremely helpful information specially the closing section :)
I maintain such info much. I used to be seeking this certain info for a long time.
Thanks and good luck.
[…] a UI. You basically have to write your own AsyncTaskLoader so that defeats the point. (See here https://blogactivity.wordpress.com/2011/09/01/proper-use-of-asynctask/) AsyncTaskLoader – seems to be the way to […]