The AsyncTask is one of the handiest classes available in an Android developer’s toolbox. The simple background threading it can add to your app is invaluable, especially for short lived work. However, it is often a source of pain for new and experienced developers alike. This is due in part to not understanding Android’s threading mechanisms, lifecycle events, and understanding what AsyncTask does and does not do for you. I’m going to start with some background then explain AsyncTasks to help you better leverage them in your code.
Before we dive off into the details of AsyncTask, let’s take a quick look at Android’s threading model. Android applications (generally) run in their own separate process space. This is done for security isolation and stability and allows each app to have its own instance of the Dalvik or ART virtual machine. However, it doesn’t stop there. Every application has several threads running by default.
The screen cap above is from Android Studio’s debug view when attached to a very simple “Hello World” type application. You can quickly see in the debugger pane’s “Threads” tab at the bottom there are a total of 11 threads running in this simple application! For the new Android developer, this is likely very unexpected. The Android folks weren’t kidding when they said there’s no Java or C style “main” entry point. These threads are all managed by the system, some of which are owned by the virtual machine (e.g. the GC, or garbage collector thread) and other parts owned and managed by the Android framework itself. Note thread number 1, the “main” thread. You may have seen or heard of this in your reading or development. It’s also commonly referred to as your “UI thread.” This leads us into our next topic: lifecycle events.
Android Component Lifecycles
The core Android components (Activity, Service, ContentProvider and BroadcastReceiver) are base classes that you the developer use when creating your own app. Each of these components has their own documented lifecycles and methods which are called during the transitions to give you an opportunity to take some action. I’m not going to go through the lifecycles or callbacks for each of these, there is plenty of good documentation on it at the Android developer website. However, what I am going to point out is that in each of these components the lifecycle callbacks are called on the main thread of the application. This tends to trip people up, especially with regards to the Service component, but that’s a topic for another post. Your app is notified via the framework about these events via one of the Binder threads which are running behind the scenes. These includes user interactions, such as key inputs and touch inputs.
Since your Activity is being called back in its lifecycle transitions (onCreate(), onResume(), onPause(), onDestroy(), etc.) as well as view specific user interaction (onKeyDown(), onTouchEvent(), etc.), it makes sense that this thread is also called the UI thread. Android is constructed to be event driven: you are called back when something interesting happens for your app to handle. This illustrates why it is critical that your event handling is done as quickly as possible and there is nothing long running going on, such as network communications. We need our own threads to do this kind of work. Here’s where AsyncTask can be applied.
In short, AsyncTask is a class used to perform some work in the background but is built for being able to update the UI. There are two confusing things which trip up most devs:
- AsyncTask callback methods are not all run in the same context of execution
- AsyncTask is not aware of core component lifecycle events
Once you get your arms around both of these, you’ll have much better control over usage.
Context of Execution
We know the entire purpose of using an AsyncTask is to perform work in the background. But, unlike the core components, the callbacks run under different thread contexts. If you are scratching your head wondering, “why?” the explanation is actually simple. AsyncTask is more than just a background thread: it provides you with a way to update your UI components without having to build in messaging or other infrastructure. Here’s a quick rundown of the callback methods, whether they are mandatory and in which context they run. Callbacks executing on the main (UI) thread can update UI components, assuming the Activity is still in the running state.
|Method Name||Description||Mandatory?||Context of Execution|
|Perform the background operation(s).||Yes||Arbitrary background thread|
|Perform cleanup due to cancel.||No||Main thread|
|Perform cleanup due to cancel. It's preferred to override ||No||Main thread|
|Perform operations after successfully completing ||No||Main thread|
|Perform setup operations or UI updates needed before background operation begins.||No||Main thread|
|Perform any UI or other updates based on progress data provided back the background operations via a call to ||No||Main thread|
Another common source of issues is when multiple AsyncTasks are started simultaneously. This specific situation has been handled differently as Android has evolved, causing issues and confusion. There’s a good writeup on the history on the AsyncTask documentation, so I won’t go into that. The thing to remember is that by default your AsyncTasks are run serially in a single thread. If you need parallel operation, you can override this by specifying the AsyncTask.THREAD_POOL_EXECUTOR when you call the execute method. With this explanation hopefully you can see something else: AsyncTask instances are one-shot type objects. You create one, execute it then you are done with it. You cannot re-use an AsyncTask after it has been executed.
All of this sounds great: we have the ability to kick off background operations which can update our main thread and simplify our lives. But, it is still missing one key component: lifecycle awareness. This subtle nuance causes more bugs than just about everything I’ve mentioned so far. Remember how our onPostExecute() runs in the main thread and can directly update UI components? Think about what would happen if the Activity was destroyed when this happens. You guessed it, the app crashes. Fortunately, there’s an easy way to fix this: call the cancel() method of your AsyncTask instance from within your onPause() or other cleanup type callback. Once done, the onPostExecute() will not get called. You can even instruct Android to interrupt your doInBackground() during the cancel rather than wait for it to complete. There are, of course, more advanced use cases where you may not want this (such as orientation change). In those cases it may be useful to manage your own threads or even still use an AsyncTask hosted by a UI-less Fragment.
Hopefully you now have a better idea of what can be accomplished with AsyncTask, how it can be used and some of its limitations. Have you encountered other situations in which this helpful class was the problem and something wasn’t clear? If so, I’d love to hear about your experience.