A Basic Overview of Android Threads and Thread handlers
Previous | Table of Contents | Next |
Android Broadcast Intents and Broadcast Receivers | An Overview of Android Started and Bound Services |
The next chapter will be the first in a series of chapters intended to introduce the use of Android Services to perform application tasks in the background. It is impossible, however, to understand fully the steps involved in implementing services without first gaining a basic understanding of the concept of threading in Android applications. Threads and thread handlers are, therefore, the topic of this chapter.
An Overview of Threads
Threads are the cornerstone of any multitasking operating system and can be thought of as mini-processes running within a main process, the purpose of which is to enable at least the appearance of parallel execution paths within applications.
The Application Main Thread
When an Android application is first started, the runtime system creates a single thread in which all application components will run by default. This thread is generally referred to as the main thread. The primary role of the main thread is to handle the user interface in terms of event handling and interaction with views in the user interface. Any additional components that are started within the application will, by default, also run on the main thread.
Any component within an application that performs a time consuming task using the main thread will cause the entire application to appear to lock up until the task is completed. This will typically result in the operating system displaying an “Application is unresponsive” warning to the user. Clearly, this is far from the desired behavior for any application. In such a situation, this can be avoided simply by launching the task to be performed in a separate thread, allowing the main thread to continue unhindered with other tasks.
Thread Handlers
Clearly one of the key rules of application development is never to perform time-consuming operations on the main thread of an application. The second, equally important rule is that the code within a separate thread must never, under any circumstances, directly update any aspect of the user interface. Any changes to the user interface must always be performed from within the main thread. The reason for this is that the Android UI toolkit is not thread-safe. Attempts to work with non thread-safe code from within multiple threads will typically result in intermittent problems and unpredictable application behavior.
In the event that the code executing in a thread needs to interact with the user interface, it must do so by synchronizing with the main UI thread. This is achieved by creating a handler within the main thread, which, in turn, receives messages from another thread and updates the user interface accordingly.
A Basic Threading Example
The remainder of this chapter will work through some simple examples that provide a basic introduction to threads. The first step will be to highlight the risks involved in not performing time-consuming tasks in a separate thread from the main thread. Begin, therefore, by creating a new Android project named ThreadExample containing a single blank activity named ThreadExampleActivity with the Layout and Fragment names set to activity_thread_example and fragment_thread_example.
Load the fragment_thread_example.xml file for the project into the Graphical Layout tool. Right-click on the TextView component, select the Assign ID… menu option and change the ID for the view to myTextView. Next, click and drag the TextView so that it is positioned in the center of the display canvas.
Add a Button view to the user interface, positioned directly beneath the existing TextView object as illustrated in Figure 29-1:
Figure 29-1
Right-click on the button view and select the Edit Text… menu option. Create a new string resource named button_text containing the text “Press Me”. Right click on the button view a second time, this time selecting the Other Properties -> All by Name -> onClick… menu option. In the resulting dialog, enter buttonClick as the method name.
Once completed, the XML file content should be similar to the following listing:
<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=".ThreadExampleActivity" > <TextView android:id="@+id/myTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="@string/hello_world" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/myTextView" android:layout_centerHorizontal="true" android:layout_marginTop="48dp" android:onClick="buttonClick" android:text="@string/button_text" /> </RelativeLayout>
Save the user interface design file before proceeding.
Next, load the ThreadExampleActivity.java file into an editing panel and add code to implement the buttonClick() method which will be called when the Button view is touched by the user. Since the goal here is to demonstrate the problem of performing lengthy tasks on the main thread, the code will simply pause for 20 seconds before displaying different text on the TextView object:
package com.example.threadexample; import android.app.Activity; import android.app.ActionBar; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.os.Build; public class ThreadExample extends Activity { . . . public void buttonClick(View view) { long endTime = System.currentTimeMillis() + 20*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } TextView myTextView = (TextView)findViewById(R.id.myTextView); myTextView.setText("Button Pressed"); } }
With the code changes complete, run the application on either a physical device or an emulator. Once the application is running, touch the Button, at which point the application will appear to freeze. It will, for example, not be possible to touch the button a second time and in some situations the operating system will, as demonstrated in Figure 29-2, report the application as being unresponsive:
Figure 29-2
Clearly, anything that is going to take time to complete within the buttonClick() method needs to be performed within a separate thread.
Creating a New Thread
In order to create a new thread, the code to be executed in that thread needs to be placed within the Run() method of a Runnable instance. A new Thread object then needs to be created, passing through a reference to the Runnable instance to the constructor. Finally, the start() method of the thread object needs to be called to start the thread running. To perform the task within the buttonClick() method, therefore, the following changes need to be made:
public void buttonClick(View view) { Runnable runnable = new Runnable() { public void run() { long endTime = System.currentTimeMillis() + 20*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime – System.currentTimeMillis()); } catch (Exception e) {} } } } }; Thread mythread = new Thread(runnable); mythread.start(); }
When the application is now run, touching the button causes the delay to be performed in a new thread leaving the main thread to continue handling the user interface, including responding to additional button presses. In fact, each time the button is touched, a new thread will be created, allowing the task to be performed multiple times concurrently.
A close inspection of the updated code for the buttonClick() method will reveal that the code to update the TextView has been removed. As previously stated, updating a user interface element from within a thread other than the main thread violates a key rule of Android development. In order to update the user interface, therefore, it will be necessary to implement a Handler for the thread.
Implementing a Thread Handler
Thread handlers are implemented in the main thread of an application and are primarily used to make updates to the user interface in response to messages sent by another thread running within the application’s process.
Handlers are subclassed from the Android Handler class and can be used either by specifying a Runnable to be executed when required by the thread, or by overriding the handleMessage() callback method within the Handler subclass which will be called when messages are sent to the handler by a thread.
For the purposes of this example, a handler will be implemented to update the user interface from within the previously created thread. From within Eclipse, load the ThreadExampleActivity.java file into the editing panel and modify the code to add a Handler instance to the activity:
package com.example.threadexample; import android.app.Activity; import android.app.ActionBar; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.os.Build; import android.view.View; import android.widget.TextView; import android.os.Handler; import android.os.Message; public class ThreadExample extends Activity { Handler handler = new Handler() { @Override public void handleMessage(Message msg) { TextView myTextView = (TextView)findViewById(R.id.myTextView); myTextView.setText("Button Pressed"); } }; . . . }
The above code changes have declared a handler and implemented within that handler the handleMessage() callback which will be called when the thread sends the handler a message. In this instance, the code simply displays a string on the TextView object in the user interface.
All that now remains is to modify the thread created in the buttonClick() method to send a message to the handler when the delay has completed:
public void buttonClick(View view) { Runnable runnable = new Runnable() { public void run() { long endTime = System.currentTimeMillis() + 20*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) {} } } handler.sendEmptyMessage(0); } }; Thread mythread = new Thread(runnable); mythread.start(); }
Note that the only change that has been made is to make a call to the sendEmptyMessage() method of the handler. Since the handler does not currently do anything with the content of any messages it receives it is not necessary to create and send a message object to the handler.
Compile and run the application and, once executing, touch the button. After a 20 second delay, the new text will appear in the TextView object in the user interface.
Passing a Message to the Handler
Whilst the previous example triggered a call to the handleMessage() handler callback, it did not take advantage of the message object to send data to the handler. In this phase of the tutorial, the example will be further modified to pass data between the thread and the handler. Firstly, the updated thread in the buttonClick() method will obtain the date and time from the system in string format and store that information in a Bundle object. A call will then be made to the obtainMessage() method of the handler object to get a message object from the message pool. Finally, the bundle will be added to the message object before being sent via a call to the sendMessage() method of the handler object:
package com.example.threadexample; import java.text.SimpleDateFormat; import java.util.Date; import android.app.Activity; import android.app.ActionBar; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.os.Build; import android.view.View; import android.widget.TextView; import android.os.Handler; import android.os.Message; . . . public void buttonClick(View view) { Runnable runnable = new Runnable() { public void run() { Message msg = handler.obtainMessage(); Bundle bundle = new Bundle(); SimpleDateFormat dateformat = new SimpleDateFormat("HH:mm:ss MM/dd/yyyy", Locale.US); String dateString = dateformat.format(new Date()); bundle.putString("myKey", dateString); msg.setData(bundle); handler.sendMessage(msg); } }; Thread mythread = new Thread(runnable); mythread.start(); }
Next, update the handleMessage() method of the handler to extract the date and time string from the bundle object in the message and display it on the TextView object: Handler handler = new Handler() {
@Override public void handleMessage(Message msg) { Bundle bundle = msg.getData(); String string = bundle.getString("myKey"); TextView myTextView = (TextView)findViewById(R.id.myTextView); myTextView.setText(string); } };
Finally, compile and run the application and test that touching the button now causes the current date and time to appear on the TextView object.
Summary
The goal of this chapter has been to provide an overview of threading within Android applications. When an application is first launched in a process, the runtime system creates a main thread in which all subsequently launched application components run by default. The primary role of the main thread is to handle the user interface, so any time consuming tasks performed in that thread will give the appearance that the application has locked up. It is essential, therefore, that tasks likely to take time to complete be started in a separate thread.
Because the Android user interface toolkit is not thread-safe, changes to the user interface should not be made in any thread other than the main thread. User interface changes can be implemented by creating a handler in the main thread to which messages may be sent from within other, non-main threads.
Previous | Table of Contents | Next |
Android Broadcast Intents and Broadcast Receivers | An Overview of Android Started and Bound Services |