Android Saga: AsyncTask, Advantages and Limitations
07/08/2012 § 2 Comments
This is an utility class available in the Android SDK that abstracts a worker thread. An AsyncTask object can be compared to the NSOperation object from iOS.
The NSOperation object represents an operation of some kind and has to be added to a NSOperationQueue in order to execute. This queue can either be the main queue (UI thread) or another one that you create to execute operations in background. iOS doesn’t schedule an operation if it is not possible, neither throws exceptions when it is not possible to schedule an operation. Instead, iOS waits for the right moment to schedule the operation.
The difference between AsyncTasks and NSOperation lies on how they are scheduled. An AsyncTask is automatically scheduled when the method execute() is called. From API Level 11 onwards, execute() can have some parameters passed in to specify the queue on which to schedule it.
AsyncTask provides a simple API for doing work in the background and re-integrating the result with the main thread. Here’s what it looks like:
new AsyncTask<Param, Progress, Result>() { protected void onPreExecute() { // do something before the hard work, like tell the user what you are going to do } protected Result doInBackground(Param... aParams) { // do some expensive work } protected void onPostExecute(Result aResult) { // background work is finished, // so update the UI here } }.execute();
Both methods onPreExecute() and onPostExecute(Result) are invoked on the main thread, so that you can safely update the UI.
If you would like to show the user progress is being made, there is a fourth template method – onProgressUpdate(Progress[]) – that you can implement to update the UI. Notice that you need to regularly invoke publishProgress(Progress[]) from within doInBackground(Param[]) so that onProgressUpdate can be called for you.
The type variables of AsyncTask you saw in the code above are:
- Params is the argument type for the array passed in to doInBackground.
- Progress is the argument type for the array passed in to onProgressUpdate.
- Result is the return type of doInBackground, which in turn is the argument type passed in to onPostExecute.
When execute(Object.. params) is invoked the task is executed in a background thread. Depending on the API Level AsyncTasks may be executed serially, or concurrently. The only way to be sure is by invoking executeOnExecutor(Executor executor, Object.. params) method instead, and supply an executor. But this can only be done from API Level 11 onward, as I said previously.
This is a very useful abstraction of a Thread/Runnable combination. As it provides the hooks needed to keep focus only on what you application needs to do.
However you must keep in mind that it is not all flowers. If you want to support the vast majority of Android users, you must develop for API Level 8 (currently if you install Eclipse with ADT, ADT will tell you that about 80% of Android users are running API Level 8. I am not sure if this is valid only for Brazil or not. Anyways, even it is only within Brazil, that counts already hundreds of thousands users – Brazilian population is approximately 194 million people).
And API Level 8 doesn’t support executeOnExecutor. API Level 8 AsyncTask supports 10 threads and a work queue depth of 10. In theory, that would support 20 threads if nothing else is using AsyncTask as well. This limitation easily crashes your app if you are using AsyncTask to fetch thumbnails from some remote URL for a list view to present in each row.
If you are consuming a web service, the right approach would be to use a Service instead of a worker thread. So you could use the same service to request the image, store it locally and fetch it somehow from your local filesystem so that your ListActivity can present it. But if you wish to keep things simple and don’t handle image caching at all, the solution is to implement a queue and execute your requests serially.
Personally I think this should be provided by Google in the Support Library. But it isn’t, so the solutions is to copy the newly version of AsyncTask (from API Level 11) into our own AsyncTask implementation and make it work with earlier Android SDK (down to API Level 3). The modified source code is available here.
If you decided to grab that code, you should start using your.package.AsyncTask instead of android.os.AsyncTask, call executeInExecutor(Executor executor) instead of execute() and create your own Executor:
private static final BlockingQueue<Runnable> sWorkQueue = new LinkedBlockingQueue<Runnable>(Integer.MAX_VALUE); public static final ThreadPoolExecutor SERIAL_EXECUTOR = new ThreadPoolExecutor(1, Integer.MAX_VALUE, 30, TimeUnit.SECONDS, sWorkQueue);
Note: It is important to keep in mind that this alternative works very well (since it doesn’t really matter what API Level your user is running), but it is also considered to be bad practice since you don’t get updates on AsyncTask when a new API Level is released unless you manually apply any changes to your class.
Another thing to keep in mind is that AsyncTasks are there for you to void blocking the main thread, but you have to properly handle them considering the Activity lifecycle and also that your application runs in an environment where users switch contexts frequently and quickly.
For example, if your AsyncTask retains a reference to the Activity, not cancelling the task when the Activity dies wastes CPU resources on work that cannot update its UI, and creates a memory leak. Don’t forget Activities are destroyed and re-created when some configuration changes (ie: device orientation). So these are not edge cases.
AsyncTask is good, but also has its limitations – depending on the API Level – and constraints. Having those in mind, think about your task before coding with AsyncTasks. Maybe they are not the best approach. Maybe they are. And maybe you should grab Google’s code, modify it and use it instead.
Android Saga: Pull to Refresh revisited
06/27/2012 § 3 Comments
Previously on Android Saga..
“I find the solution very bizarre, but it worked out and that is what I am using right now. The only thing that still upsets me, is that the feature is not polished enough. It is still sluggish to pull, flicks once in a while, and lacks animation.”
It happens I was wrong. Yesterday I tested the app on the device – precisely a Samsung Galaxy i5500 running API Level 8 – and I was surprised when it didn’t work since all this time I was testing on an emulator running the very same API Level. I know the emulator is different from the device. This is true for the iOS simulator as well. But not THAT different.
Anyways, Johan’s implementation doesn’t work either on Samsung Galaxy i5500 running API Level 8 nor Samsung Galaxy S2 running 4.0.3 (Yes I tried a newer version to see if I was pushing to hard by using an old API version).
I got to a point where I started to think that maybe pull to refresh wasn’t the answer for Android. Actually my girlfriend asked me if I was not trying to put an iOS app into Android. And she had a good point: Android doesn’t support bouncing in the list view, so Android users are not used to pull the list hence they don’t easily discover how to pull to refresh.
Discussing this matter with some co-workers, I was presented with a list of well known Android apps (Twitter, Facebook, LinkedIn…) that actually do a very good pull to refresh. This convinced me that Android apps can be as good as iOS apps.
So I kept looking for other pull to refresh implementations. In the end, I got to Chris Banes’s library.
This time as soon as it worked on the emulator, I tried on both devices I have here and it worked pretty well. In fact, it is as good as Twitter’s pull to refresh.
Now, the interesting fact is: Chris Bane’s implementation needed 6 Java classes (and they are not tiny), 8 XML files and 2 images. His implementation is very interesting and truly had outstanding results. BUT this is TOO MUCH WORK FOR SUCH A SIMPLE FEATURE!
Knowing there are developers and developers (although this guy deserves respect since he is the only one that got a – open source – pull to refresh to properly work on Android), I tried not to think: “On iOS it would take a class for the header view and another to use it. Really.”.
Instead I googled for the Twitter APK file, downloaded it and decompiled it (a pretty painful process – by the way – that requires decompressing the APK, converting smil files to dex, from dex to jar and finally to Java source files). Of course, I wasn’t able to just grab and use their implementation, but that was enough readable code to see that they use just about the same number of files that Chris Bane’s does.
I am sure every file has its meaning and need, but still too much work for this feature. And just to be sure we are in the same page, I am not counting any i18n files neither assets for supporting multiple screen densities whatsoever.
Anyways, I learned two things on all of this:
1. Android apps can be as good as iOS apps
2. #1 requires a lot more work than it takes on iOS (and I am not talking about device fragmentation)
For those that like references, here are the best pull to refresh libraries I tried on Android:
https://github.com/chrisbanes/Android-PullToRefresh
https://github.com/johannilsson/android-pulltorefresh
https://github.com/woozzu/RefreshableListView.git
https://github.com/erikwt/PullToRefresh-ListView
And the tools I used for decompiling Twitter:
http://code.google.com/p/android-apktool/
http://code.google.com/p/smali/
http://code.google.com/p/dex2jar/
http://www.varaneckas.com/jad/