Background Tasks - Threads

Intro

Whenever your Android application is launched, a new thread is created.  This thread, known as the "main" thread, is where all user interface (UI) actions occur.  For this reason, it's also referred to as the UI thread.  Because there's only a single thread, all instructions are executed in sequence.  If one instruction takes a while to complete, the rest of the application must wait, which means that the user is stuck waiting.  As I hope you recall from your previous CS courses, this is referred to as "blocking" behavior.  This type of behavior usually results in apps that aren't very user friendly!  We fix this by running the task in another thread, allowing the use to still use the app while waiting for the task to complete.

Blocking Behavior

To demonstrate what it looks like to have a blocking behavior, let's create a simple app that simulates doing work that takes a non-trivial amount of time to complete.  Our demo app will have a single TextView and a Button.  When the button is clicked, the "task" runs and updates the TextView contents.

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/outputText"
        android:layout_width="fill_parent"
        android:textSize="24dp"
        android:gravity="center_horizontal"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="run background task"
        android:onClick="runTask"/>

</RelativeLayout>

In the MainActivity.java file, instantiate the TextView and create the runTask() method that will run when the Button is clicked:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    outputText = (TextView) findViewById(R.id.outputText);
}

public void runTask(View v){

    // VERSION 0
    // Running a task on the UI Thread

    Log.i(LOG_TAG, "Blocking Task Started");

    try {

        // simulate a long-running task
        Thread.sleep(5000); 
        
        outputText.setText("Task Complete");

    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    Log.i(LOG_TAG, "Blocking Task Finished");
}

When the user clicks on the Button, the runTask() method simulates running a task that takes 5 seconds to complete.  When you run the app, you'll notice that the app freezes (the most obvious visual indicator being the button state), and you are unable to do anything until the 5-second task is complete.  Not very user friendly!

Threads

So how can we fix this blocking problem?  Well, since Android is written in Java, we can use the Java Thread class for asynchronous task execution just like any other Java program.  In order to run our task in another thread, we create a new Thread object, and do our work inside the Thread's run() method:

Thread thread = new Thread(){
    public void run( ) {
      
        Log.i(LOG_TAG, "Thread Started");

        try {

            // simulated long-running task
            Thread.sleep(5000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } 

        Log.i(LOG_TAG, "Thread Finished");
    }
};
thread.start();

Modify the sample app by replacing the contents of the runTask method with the above code and run it again, being sure to monitor the LogCat output.  We've placed the code for the long-running task inside the run method, which is executed when the Thread's start() method is called.  While the background task is running, click the button again.  Notice that: 1) you are still able to use the app, and 2) clicking the button again causes a new Thread to be created.

Now, let's try to update the TextView from the new Thread.  To demonstrate this, we'll update our task so that it sets the TextView text in a loop, pausing for 2 seconds between each update:

Thread thread = new Thread(){
    public void run( ) {

        Log.i(LOG_TAG, "Thread Started");

        try {
            for(int i = 0; i < 5; i++){

                outputText.setText("Current Count = " + i);
                Thread.sleep(2000); //pause for 2 seconds

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.i(LOG_TAG, "Thread Finished");

    }
};
thread.start();

Before you run the app again, take a minute to decide what you expect to see.  Got it?  Good, now run the app

Did you get the results you expected?  Unless you anticipated a crash, I would guess the answer is No.  Looking at the code, it appears that the TextView element will be updated every 2 seconds, counting up to the number five.  But instead, your app crashed.   Look at the LogCat and you'll see something like this:

 

 

What happened?  Look at line 2 of the exception stacktrace for the clue:  we tried to modify an item running in the UI thread (specifically, the TextView) from another thread.  This is a no-no!  You are not allowed to modify the elements of the UI thread from another thread!  Attempting to do so will cause your app to throw a CalledFromWrongThreadException.  But there must be a way to update the UI thread from another thread.  We need some way to communicate between threads.  We need a Handler.

Using a Handler

One way to enable modifying the UI thread from another thread is by using a Handler (be sure to use android.os.Handler).  A Handler is a special object used to send messages to the message queue of its parent thread.  This is possible because the Handler is bound to the thread in which it was created. If we create a Handler in the UI thread, we can use it to pass data to that thread from another thread.  That data can, in turn, be used to update the user interface.  To implement the Handler, create a new field in the MainActivity class, declaring the new Handler object and creating an instance of it:

public Handler mHandler = new Handler() {

    public void handleMessage(Message msg) {

        outputText.setText("What: " + msg.what);

    }
};

Because we created our Handler as a field of the MainActivity class, it will get created when the class is created, which means that it will be created in the main thread.  Since the handler is created in the main thread, it is bound to that thread...which is also the thread that the UI runs in.  

What does that mean for us?  It means that we can now use the handler to pass messages to the main thread from another thread.  Any messages we send are handled by the Handler's handleMessage() method.  To send a message, modify the new thread (the one we created in runTask) so that instead of attempting to modify the TextView directly, we instead send a message to the Handler to do the modification for us:

for(int i = 0; i < 5; i++){
    // This will cause the app to throw a CalledFromWrongThreadException
    //  outputText.setText("Current Count = " + i);

    // Let the Handler take care of updating the TextView
    mHandler.obtainMessage(i).sendToTarget();

    Thread.sleep(2000); //pause for 2 seconds

}

Run your app again.  That's better!! 

The way we've currently written out app, we send a message by passing to the Handler through the obtainMessage method.  Specifically, we're sending a "what" message.  There are a few more options in terms of what can be passed via obtainMessage, and you should take the time to review them by clicking on the link provided.

Runnable

Creating a new Thread and calling it's run method is not the only way to run a background task.  Another option is to use a Runnable object to run our task.  A Runnable lets us define a task to be ran by a Thread.  To implement a Runnable requires a simple modification to our code:

Thread thread = new Thread( new Runnable(){
    public void run( ) {

        Log.i(LOG_TAG, "Runnable Started");

        try {
            for(int i = 0; i < 5; i++){

                // Let the Handler take care of updating the TextView
                mHandler.obtainMessage(i).sendToTarget();
                Thread.sleep(2000); //pause for 2 seconds

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.i(LOG_TAG, "Runnable Finished");

    }
});
thread.start();

Run the app again after implementing the Runnable and you'll see that it appears to work exactly the same.  So why would you choose to use one over the other?  Well it turns out, the Thread class actually implements the Runnable interface!  When you call  the start method of a Thread, it in turn calls the Runnable method that the Thread was required to implement because it is a Runnable.  We'll get into more details in a second, but suffice it to say that there are many benefits to using a Runnable that you should consider when making the choice.    

Sending Data Through the Handler

Let's turn back to the Handler for a moment, and examine more of its capabilities.  We can send a lot more than simple integers to our Handler. The obtainMessage() method of Handler returns a Message object.  To send non-trivial data to the Handler, we create a data Bundle, and add the Bundle to the Message...exactly the same way we used a Bundle to pass data between Activities with as part of an Intent!  

To demonstrate this, let's update the TextView from the background thread by sending a String to the UI thread using the Handler.  Modify your app so that the Runnable's run() method adds a Bundle object to the Message we will send to our Handler:

Thread thread = new Thread( new Runnable(){
    public void run( ) {

        Log.i(LOG_TAG, "Runnable Started");

        try {
            for(int i = 0; i < 5; i++){

                UUID id = UUID.randomUUID();

                // Let the Handler take care of updating the TextView
                // this time we'll send something in a bundle, like we did with Intents

                Message msg = mHandler.obtainMessage(i);
                Bundle bundle = new Bundle();
                bundle.putString("myID", id.toString());
                msg.setData(bundle);

                mHandler.sendMessage(msg);

                Thread.sleep(2000); //pause for 2 seconds

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.i(LOG_TAG, "Runnable Finished");

    }
});
thread.start();

Before continuing, let's examine the code.  We created a new Bundle object and placed a String key-value pair in the Bundle, just like did for an Intent extra.  We then set the data in the Message that was returned from the Handler's obtainMessage method.  Finally, we sent the message.

In order to retrieve the Bundle, we need to modify our Handler's handleMessage method.  We will retrieve the Bundle from the Message and extract our String value using it's key:

public Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {
        outputText.setText("What: " + msg.what);

        Bundle bundle = msg.getData();
        String receivedString = bundle.getString("myID", null);
        if(receivedString != null) {
            outputText.append("\nID: " + receivedString);
        }

    }
};

Run the app again to see our changes.  The Bundle can be used to pass any key-value pair just like we did with Intents, which means our background Thread can now communicate with the UI thread with minimal restrictions on the type of data it can send.

Thread vs Runnable

So which is better, a Thread or a Runnable?  Trick question...I've already told you that a Thread IS a Runnable!  But what would cause you to choose one over the other when it comes time to write your code?  Well there are a few things to consider.  As I mentioned earlier, when a Thread runs as a result of the start() method call, the run() method that the Thread has implemented is executed.  If you pass the Thread constructor a Runnable object,

Runnable myRunnable = new Runnable(){
    public void run() {
        // do stuff
    }
}

Thread thread = new Thread( myRunnable);
thread.start();

the run method of the Runnable is executed on Thread.start().  A single Runnable can be shared by multiple Threads, giving them all access to the same resource.

But the true reason why you want to use a Runnable vs a Thread has everything to do with how the two objects are created.  If you want to write a class that uses Thread, you would do this:

class MyThread extends Thread {
    public void run() {
        // do stuff here
    }
}

And run it like this:

MyThread thread = new MyThread();
thread.start();

In comparison, to write a class that uses Runnable, you would do something like this:

class MyRunnable implements Runnable {
    public void run() {
        // do stuff here
    }
}

And run it in you driver code like this:

MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();

Notice the big difference?  Hopefully you do, but just in case, I'll tell you: your class must EXTEND Thread, and IMPLEMENT Runnable.  Or, to put it another way, Thread is a Class, and Runnable is an Interface.  This becomes a very important distinction when writing non-trivial programs, because Java only allows you to extend a single class.  Using Runnable enables multiple inheritance.

If that's not a good enough reason for you, perhaps I can appeal to the "Good" Object Oriented programmer in you.  When employing good OO principles,

"...classes should not be subclassed unless the programmer intends on modifying or enhancing the fundamental behavior of the class"

-http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html

You should use Runnable if you are only planning on using the run() method and have no need to override any other Thread methods! 

TimerTask

As if you didn't already have enough options, here's another.  Instead of using a Thread or Runnable, we can accomplish the same thing using a TimerTask.  Like Thread, TimerTask implements the Runnable interface.  Instead of using a Thread to handle running the Runnable, the TimerTask relies on a Timer, which handles scheduling a task to run on a background thread.

To demonstrate the use of TimerTask, replace the contents of our app's runTask method with the following:

Timer timer = new Timer();
timer.schedule(new TimerTask(){
    public void run( ) {
        
        Log.i(LOG_TAG, "TimerTask Started");

        for (int i = 0; i < 5; i++){
            try {
                mHandler.obtainMessage(i).sendToTarget();
                Thread.sleep(2000); //pause for 2 seconds
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        Log.i(LOG_TAG, "TimerTask Finished");
    }
}, 2000); //2 sec delay before calling the run method

Run your app again to see the changes.  Also, notice that the Timer's schedule method takes two parameters: a TimerTask and a Long value representing how long to wait before calling the run() method of the TimerTask.  The Timer class provides many more options for scheduling tasks, and I encourage you to review them by clicking the Timer link above.