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.
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.
To provide a good user experience all potentially slow running operations in an Android application should run asynchronously.
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.
Basic Thread
Android supports the usage of the Thread
class to perform asynchronous processing. Let's create simple Activity
that will start Thread
on Button
click.
public class MainActivity extends AppCompatActivity { private String TAG = "TAG"; private Activity activity = MainActivity.this; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onClick(View v) { Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); Log.d(TAG, "Thread: DONE"); } catch (InterruptedException e) { Log.d(TAG, e.getMessage()); } } }); thread.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.
Updating a Layout from a Separate Thread
As discussed before when a time-consuming activity is being run, care must be taken to ensure that the UI thread stays responsive. This is done by creating a separate thread for the time-consuming task and letting the UI thread continue at high priority. If the separate thread subsequently needs to update the UI, a handler can be used to post updates to the UI thread.
This snippet uses a Button
to trigger a time-consuming computation in two parts and updates to the screen when each part is done. The layout, represented by the XML, consists of status text called tvStatus
and a trigger button called btnAction
.
Layout from res/layout/main.xml file.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"> <TextView android:id="@+id/tvStatus" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Status" android:textSize="36sp" android:textColor="#000" /> <Button android:id="@+id/btnAction" android:text="Start" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
The steps to update the UI from a background thread follow:
The activity created by these steps is shown below.
public class MainActivity extends AppCompatActivity { private String TAG = "TAG"; private Activity activity = MainActivity.this; TextView tvStatus; String status = "Idle"; int backgroundColor = Color.DKGRAY; final Handler handler = new Handler(); final Runnable updateResults = new Runnable() { public void run() { tvStatus.setText(status); tvStatus.setBackgroundColor(backgroundColor); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvStatus = (TextView) findViewById(R.id.tvStatus); Button btnAction = (Button) findViewById(R.id.btnAction); btnAction.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { doWork(); } }); } //example of a computationally intensive action with UI updates private void doWork() { Thread thread = new Thread(new Runnable() { public void run() { status = "Start"; backgroundColor = Color.DKGRAY; handler.post(updateResults); computation(); status = "Status 1"; backgroundColor = Color.BLUE; handler.post(updateResults); computation(); status = "Status 2"; backgroundColor = Color.GREEN; handler.post(updateResults); } }); thread.start(); } private void computation() { try { Thread.sleep(2000); Log.d(TAG, "Thread: DONE"); } catch (InterruptedException e) { Log.d(TAG, e.getMessage()); } } }
A Handler
object registers itself with the thread in which it is created. It provides a channel to send data to this thread, for example the main thread. The data which can be posted via the Handler
class can be an instance of the Message
or the Runnable
class. A Handler
is particular useful if you have want to post multiple times data to the main thread.
Sometimes when a component is finished or killed, the developer wants the threads it spawns to also be killed. For example, take a thread defined in an activity:
private Thread thread;
The thread.stop()
method is deprecated because it might leave the application in an unpredictable state. Instead, use the following when needed, such as in the onStop()
method of the parent component:
// use to stop the thread if(thread != null) { Thread dummy = thread; thread = null; dummy.interrupt(); }
At the application level, there is another way to do this. Declare all spawned threads as daemon threads using the setDaemon(true)
method, as in the following example. This ensures that threads associated with that application are killed when the application’s main thread is killed.
// use when initially starting a thread thread.setDaemon(true); thread.start();