Brain Flush

April 8, 2009

Android In-Sync: Handling concurrent tasks in Google Android

UPDATE 15/12/2009: While the problem described here still applies to the latest version of Android, I discourage anyone from using the code linked here. I’ve bundled a better solution with my Droid-Fu library, which is a little more intrusive, but leads to much leaner code for the application developer.

Today I want to talk a little bit about concurrency in Android applications, and the problems it poses on the developer. If you have used Android on your phone before, it’s likely that you have stumbled upon applications which load data off the internet, or perform other time consuming operations. The problem with time consuming operations is that, well, they consume time, and if they aren’t perfomed concurrently to Android’s user interface thread (the main thread), then the UI will lock up — certainly not a good user experience. So, it’s pretty obvious that on internet phones like those based on Android, highly concurrent applications are more the rule than the exception.

I guess I don’t have to mention that developing concurrent applications is everything but simple. Keeping threads that share data in sync is not a trivial task and prone to errors. What makes it even more difficult in Android is the fact that while your application is loading data, it may suddenly be interrupted by an incoming phone call or because the user decided to flip the screen into a different orientation. You may think that your thread will get paused while the activity that created it is brought to the background (or even gets destroyed). That’s not what happens though: any thread will continue running until it completes, even if your activity or service is not alive anymore. And that’s where the pain starts.

Problem Statement

Consider the following news reader application (just as an example). To keep things simple, the application only supports one operation: Showing the latest news articles on the screen using pagination. When started the first time, the application loads the first 10 articles off the internet in a separate thread and renders a nifty spinner visual to keep the user happy. When the user wants to read the next 10 articles, a button is pressed, the spinner appears again, and the next 10 articles are loaded, etc. pp. We have two separate threads running concurrently in this application: The main UI thread that renders all the article items and the spinner graphic, and the thread that downloads data off the web and posts that data back to the UI thread upon completion, so the UI can update itself accordingly.

Now, what happens if a phone call comes in while we see the spinner graphic? What happens is that Android will pause your application (probably even destroy it) in order to launch the call activity on top of it, but your download thread will continue running in the background. It’s difficult to tell what is going to happen when that thread tries to post its result to an activity that doesn’t even exist anymore, but most probably that application will crash (for example when the download result handler in that activity tries to display a toast or dialog, which will be attached to a window that has already been destroyed). If that thread doesn’t terminate and still holds a reference to the calling activity, it may even produce a memory leak. Long story short, you will definitely want to introduce some mechanism that keeps the communication between activities, services, and any threads they run, in sync.

Solution

Lucky for you, it just so happens that I have written a module that does exactly that. Based on my former work on this problem and an excellent article by Eric Burke (who already presented an almost-working solution), I have come up with a Task class that you can use to dispatch long running operations from your activities and services and which also handles all the inconveniences arising from situations like resuming from an interruption (phone calls and the likes).

And here is how you use it:

public class Concurrency extends Activity implements TaskListener<String> {

    private static final int TASK1 = 0;

    private static final int TASK2 = 1;

    private Task<String> task1, task2;

    private Callable<String> callable1 = new Callable<String>() {

        public String call() throws Exception {
            try {
                System.out.println("task1 starting");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                System.out.println("task1 finished");
            }
            return "task1 result";
        };
    };

    private Callable<String> callable2 = new Callable<String>() {

        public String call() throws Exception {
            try {
                System.out.println("task2 starting");
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                System.out.println("task2 finished");
            }
            return "task2 result";
        };
    };

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

    @Override
    protected void onPause() {
        super.onPause();

        task1.unregisterCallback();
        task2.unregisterCallback();
    }

    @Override
    protected void onResume() {
        super.onResume();

        task1 = Task.getOrCreate(this, TASK1);
        task2 = Task.getOrCreate(this, TASK2);

        switch (task1.state()) {
        case NOT_STARTED:
            task1.run(this, callable1);
            break;
        case RUNNING:
            System.out.println("task1 still running");
            break;
        case COMPLETED:
            System.out.println("task1 completed in background, result: "
                    + task1.getResult());
            break;
        }

        switch (task2.state()) {
        case NOT_STARTED:
            task2.run(this, callable2);
            break;
        case RUNNING:
            System.out.println("task2 still running");
            break;
        case COMPLETED:
            System.out.println("task2 completed in background, result: "
                    + task2.getResult());
            break;
        }

    }

    @Override
    public void onTaskFinished(Task<String> task) {

        if (task.failed()) {
            System.err.println("task" + task.getTaskId() + " failed. Reason: "
                    + task.getError().getMessage());
        } else {
            System.out.println("task" + task.getTaskId() + " finish handler: "
                    + task.getResult());
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if (keyCode == KeyEvent.KEYCODE_BACK) {
            Task.cancelAll(this);
        }

        return super.onKeyDown(keyCode, event);
    }
}

Let’s walk through that code bit by bit. First, our activity defines two task objects, task1 and task2, with IDs TASK1 and TASK2. What those tasks are supposed to do is defined using two Callable objects, callable1 and callable2. Everything that happens inside the call() methods of those objects will be executed in a separate thread. We also have to tell those tasks what will happen should they complete. We do this by implementing the TaskListener interface, which currently only defines a single method: onTaskFinished(Task). We can check in that handler whether the task succeeded or not by calling its failed() method. If any exception was thrown during the execution of call(), this method will return true and the exception can be retrieved by calling its getError() method. Otherwise, the return value of getResult() is guranteed to be whatever you return in the callable. The Task class is generic: You instantiate it using the return type of the callable you pass to it. This ensures type safety when working with the result object. The same holds for TaskListener.

A closer look to onResume() reveals that everytime our activity is resumed, those task objects are either already in memory or will be created for us by calling Task.getOrCreate(). If we already started that task during a previous life-cycle of our activity, we can poll its status to check whether it has already completed or if it’s still running. The former is the case if the thread had terminated while our activity was paused or even completely destroyed; in that case, we can simply pick up whatever result the task came up with in the meantime. We also call Task.unregisterCallback() in onPause() in order to avoid being called back by the task when the activity goes poof (not doing so may result in memory leaks, as described above).

Right now, the activity will pick up any results of a task with a certain ID when being restarted, even when explicitly restarted by the user. If that’s not what you want, you can make a call to Task.cancelAll() in the key handler for the ‘back’ key. That way you can ensure that all tasks (or their results) are discarded when explicitly exiting the activity. You can also cancel a single task using task.cancel(). A canceled task will never post any result or error data back to the caller.

How it works

Internally, task state is maintained in a static hash mapping callers to their list of tasks. This assures that tasks are kept in memory as long as the Task class itself (or until they terminate of course). The Task class does all the locking, state updates and callback invocations for us; we can even create a ProgressDialog and assign it to a Task using Task.setProgressDialog(). The dialog will then automatically be displayed when the task starts running, and will close when the task finishes. A task (or more precisely: a list of tasks) is bound to its caller (the calling activity or service) by the caller’s ComponentName. That means, you can think of tasks being associated to a calling class, rather than a calling object. The task class will take care of removing tasks that finished and which have been posted back to the caller, but it will preserve all completed (uncanceled) tasks until the caller claims its results, in case the caller wasn’t reachable while the task was finishing.

You can download the Task module for free here.

Advertisements

16 Comments »

  1. Thanks for this article. It really helps a lot.
    I have a question for you. Is there anyway to integrate these mechanisms within a service so that the tasks have an higher priority and they don’t get killed by the system in case of low memory. Does it make sense to investigate in this direction?

    Comment by jabu — April 10, 2009 @ 7:03 pm

    • You can spawn threads from services, so yes, you can use the Task class from services as well. There is no mechanism that I know of that allows tasks to run at a certain priority (a thread runs in the same system process as the application, thus has the same priority on the OS level), but the whole point of the article was to show that threads will not terminate together with the context that created them (be it an activity or service), so your thread would only be killed if Android kills the whole application. I’m not sure how likely this is to happen, but you should probably develop your program in a way that anticipates this (e.g. by making write operations transactional).

      Comment by Matthias Käppler — April 11, 2009 @ 9:12 am

  2. Hi Matthias,

    I do believe there is a tiny bug in your task class that generates an issue when the caller claims its result (getOrCreate method). Once the caller claimed its result, the task is not correctly removed from the tasks list.
    Here is the portion of the code which is concerned:

    else if (task.state == State.COMPLETED)
    {
    // the task exists, has completed and has not yet been claimed
    // by the caller (because the caller was paused); remove it
    // from the caller’s task map
    activeTasks.remove(task); <= Here; I do believe it should be taskId
    }

    Once again, thanks a lot for your work. It really helped me a lot.
    Jabu

    Comment by jabu — July 14, 2009 @ 7:36 pm

    • Hi jabu!

      correct me if I’m wrong, but I don’t think that’s true — although I admit this code snippet looks confusing and should be changed as you suggested. The reason why I think it still works correctly is that Task.hashCode() is implemented in terms of taskId, so activeTasks.remove(task) should be semantically equivalent to activeTasks.remove(taskId), since task.hashCode() is equivalent to task.taskId. But yeah, that was sloppy programming, and I am not even sure if I was aware of that when I coded that so… thanks for the good eye!

      By the way: Since I coded that I found a couple of other (minor) issues with that class, which I fixed in our source control but didn’t came back here to update the post. I don’t quite recall what those problems were to be frank, but I can certainly update that archive or send you a diff in case you’re interested.

      Cheers,
      Matthias

      Comment by Matthias Käppler — July 15, 2009 @ 9:54 am

      • Hi Matthias,

        I hadn’t noticed that you overrided the hashCode() method.
        Although it should work fine, curiously when I use activeTasks.remove(task) the task is not removed. The consequence is that each time I call getOrCreate method to check the state of the task, the task is retrieved and not created and its state remains COMPLETED. This is weird!

        Also, I am really interested by the update of this archive. You really did an amazing job.

        Cheers,
        Jabu

        Comment by jabu — July 15, 2009 @ 6:14 pm

  3. That’s really odd. Anyhow, I’ll fix it and upload a new archive along with the other changes I mentioned. Thanks!

    Comment by Matthias Käppler — July 16, 2009 @ 8:27 am

  4. Hello Matthias,

    Can you also send me the updated archive or let me know from where can I download it?

    Thanks
    Vikas

    Comment by Vikas — July 30, 2009 @ 8:28 pm

  5. Hi Matthias,

    Is there any reason you would not use AsyncTask?

    http://developer.android.com/reference/android/os/AsyncTask.html

    ./C

    Comment by Carl — August 4, 2009 @ 10:15 am

    • Hi Carl,

      yes and no.

      First of all, AsyncTask suffers from the flaw that I address in my post: as soon as your activity goes poof (e.g. during screen rotation), any result from a background computation becomes useless (since there is no activity to call back to). How would you ever get the task result if your activity gets destroyed?

      My Task class solves this by being oblivious to activity *instances*: it maps task results to component names, which is why results remain useable beyond dropping and recreating activity objects of the same kind (the drawback is that you have to poll for the task result once you recreate your activity).

      But, maybe it would have been better to implement that behavior in terms of Android’s AsyncTask. That class didn’t come to my attention before I wrote the Task class, maybe it hasn’t even been around before 1.5, although I’m not sure about that. So you certainly have a point there. Since Task has worked pretty well for me, however, I don’t feel like reimplementing it in terms of AsyncTask. What I’d rather like to have is an ABC wrapping tasks that takes care of the state checks in the lifecycle methods, because that is just reoccurring, duplicate boilerplate code.

      Comment by Matthias Käppler — August 4, 2009 @ 12:15 pm

      • I had a similar issue when using Matthias’ Task Interface. My project required both the functionality of the Task interface, and the ability to update results mid-task (before everything was done). I went ahead and modified Task and TaskListener slightly, as well as extended the java Callable interface.

        The code is probably fairly sloppy as I was learning how to do everything as I went along, but I am using it currently in my project at work. If you’d be interested in the source, let me know! :)

        Thanks for the Task interface Matthias, I’d still be in the stone-age without your help!

        Comment by jhoffman — September 15, 2009 @ 1:02 am

      • Hi,

        I just wanted to let you know that I am about to complete a completely revised version of this whole Task thing (called BetterAsyncTask) as part of a bigger Android utility library I am currently developing.

        The new version will be slightly more intrusive because it requires your activity to inherit from a special activity in that library (there are customized versions for all stock Android activity types though, e.g. BetterDefaultActivity or BetterListActivity), but it comes with the benefit of not having to poll for task results anymore at all. You simply create your tasks and execute them, there will be no boilerplate “if-resumed-then-ask-for-result”-style code anymore. Also, BetterAsyncTask is based on Android 1.5’s AsyncTask instead of being written from scratch. This will make its interface more familiar to developers.

        It also takes care of triggering progress dialogs and/or title progress for you.

        If you’re interested, check back here soon for the whole story. There will be more added benefits by using BetterActivity, plus a couple of widgets and adapters readily usable, e.g. ImageLoader, a class which lets you asynchronously download images directly to ImageViews (or elsewhere) or ListAdapterWithProgress, a list adapter which has a progress wheel as the last list element, which is automatically triggered when running a job.

        Cheers,
        Matthias

        Comment by Matthias Käppler — September 15, 2009 @ 8:47 am

  6. […] from brainflush blog No Comment var addthis_pub="izwan00"; BOOKMARK This entry was posted on Sunday, August […]

    Pingback by Android In-Sync: Handling concurrent tasks in Google Android - MSINO | Mobile Sino — August 30, 2009 @ 1:03 pm

  7. […] written about this two times now. I think with this third approach, which made it into this library, I finally found a decent […]

    Pingback by Introducing Droid-Fu for Android: BetterActivity, BetterService and BetterAsyncTask « Brain Flush — November 16, 2009 @ 9:55 pm

  8. Hey Matthias
    Interestingly while I had coded my application using AsyncTask, I stumbled upon your article.

    Great article…well written.

    When I went to download the code, the link did not work. (http://infodump.de/task.zip) Can you please update the link or send me the code via email?

    Amit

    Comment by Amit — June 1, 2010 @ 4:28 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: