Part 1 of 2
Intro
After a period of time when executing a process that takes a long time on the UI thread in Android the
virtual machine will force the application to close since it is being unresponsive. The solution to this
problem is to move your process to a new thread and allow the UI thread to continue running.
This article describes one technique for creating and managing processes in new threads in Android apps.
Creating a Thread
In the technique described in this article we start by creating a new class which inherits (extends) from
the java.lang.Thread class.
|
[Code sample – the basic thread class]
|
public class MyThreadClass extends Thread
{
// **********************************
// Public methods
// **********************************
// This method is run when the thread
// is started by calling start().
@Override
public void run()
{
// Do something time consuming
for (int i = 0; i < 10; i++)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
|
When the thread is started the overridden run()
method will be called and its code executed. The thread ends once this method has been executed. The loop and
sleep code in the run() method is just to give the thread something to do since this is just
an example, in real life this would be replaced with the process that needs running.
To start this thread from the UI activity
do the following, perhaps in a click event handler for example.
|
[Code sample – xml layout for sample UI Activity]
|
|
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/btnTest"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Test"
/>
<TextView
android:id="@+id/txtOutput"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text=""
/>
</LinearLayout>
|
|
[Code sample – UI Activity code to start the thread]
|
public class MyMainUiActivity extends Activity implements OnClickListener
{
// ***************************
// Private variables
// ***************************
// Worker thread class
private MyThreadClass moMyThreadClass = null;
// ***************************
// Activity Lifetime Events
// ***************************
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button oButton = (Button)findViewById(R.id.btnTest);
oButton.setOnClickListener((OnClickListener) this);
}
// *****************************
// Event handling
// *****************************
/** Add the onClick event handler
* Implement the OnClickListener callback **/
public void onClick(View v)
{
// do something when the button is clicked
switch(v.getId())
{
case R.id.btnTest:
// Create instance of the worker thread class
moMyThreadClass = new MyThreadClass();
// Start the worker thread
moMyThreadClass.start();
break;
 }
}
}
|
The thread is started by calling the start()
method of the thread class, this starts the new thread and calls the overridden run() method.
Passing Data to and From thread
An easy way to pass data to and from the thread is via public properties this way the UI thread can access the
same variables as the worker thread.
|
[Code sample – public properties in thread class]
|
// *********************************
// Public properties
// *********************************
public double Latitude;
public String Name;
public String Email;
public int Quantity;
|
In the thread class add public properties which can be accessed from the UI activity thread as follows.
|
[Code sample – accessing the properties from the UI thread]
|
// Set values for use in the thread
// These could come from anywhere
// eg user input fields
moMyThreadClass.Email = "jim@eigo.co.uk";
moMyThreadClass.Name = "James T. Kirk";
moMyThreadClass.Latitude = -3.2155899;
moMyThreadClass.Quantity = 9;
|
This code would probably go in the on click event just before calling the .start() method.
Managing the Process
When the thread is started the run() method will be called and its code executed. To track if
the thread is still running or stopped we can add a method and an internal property to the thread class.
|
[Code sample – stopping + starting the thread]
|
public class MyThreadClass extends Thread
{
// ***************************
// Public Constants
// ***************************
public final static byte STATE_NOT_STARTED = 0;
public final static byte STATE_RUNNING = 1;
public final static byte STATE_DONE = 2;
// *********************************
// Private properties
// *********************************
// Used to stop a running threaded process
private boolean mblnStop;
// Keep track of the running status of the thread
private byte mbytStatus = STATE_NOT_STARTED;
// *********************************
// Public properties
// *********************************
public double Latitude;
public String Name;
public String Email;
public int Quantity;
// **********************************
// Public methods
// **********************************
// This method is run when the thread
// is started by calling start().
@Override
public void run()
{
// Flag that process does not need stopping
mblnStop = false;
mbytStatus = STATE_RUNNING;
// Do something time consuming
for (int i = 0; i < 10; i++)
{
// Check if we need to stop executing this code
if (mblnStop)
break; // exit the loop to end the process
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
mbytStatus = STATE_DONE;
}
// Set internal flag to stop threaded process
public void StopThread()
{
mblnStop = true;
}
// Get the running status of the threaded process
public byte GetStatus()
{
return mbytStatus;
}
}
|
In the code sample above the public constants help us to easily enumerate the running state of the thread. The
StopThread() method gives us a controlled way to bring the thread to a halt via the internal
Boolean property mblnStop. The code being executed from the run() method can
check the value of mblnStop between chunks of work and end its execution in a controlled way.
|
[Code sample – how to check status of thread and stop it from UI activity]
|
// Check if thread is running
if (moMyThreadClass.GetStatus() == MyThreadClass.STATE_RUNNING)
moMyThreadClass.StopThread(); // Stop the thread
|
Raising Events to UI Thread
Events can be raised from the thread and handled in the UI thread
to cause updates to the UI perhaps to update a graph, a news feed or as we will see later a progress bar.
To raise events in the UI the worker thread will need a reference to a handler from the UI Activity, this can
be used to generate messages.
The UI Activity class will need to create the handler and pass its reference to the thread class when instantiating
it. Data can be added to the messages sent to the UI, for example the results of a calculation so far.
|
[Code sample – thread class with event raising code]
|
public class MyThreadClass extends Thread
{
// ***************************
// Public Constants
// ***************************
public final static byte STATE_NOT_STARTED = 0;
public final static byte STATE_RUNNING = 1;
public final static byte STATE_DONE = 2;
public final static int MESSAGE_COMPLETE = 0;
public final static int MESSAGE_ERROR = 1;
public final static int MESSAGE_PROGRESS = 2;
// *********************************
// Private properties
// *********************************
// Used to stop a running threaded process
private boolean mblnStop;
// Keep track of the running status of the thread
private byte mbytStatus = STATE_NOT_STARTED;
// *********************************
// Public properties
// *********************************
public double Latitude;
public String Name;
public String Email;
public int Quantity;
// Event handle from calling Activity
public Handler HandlerOfCaller;
// ***************************
// Constructors
// ***************************
// Thread constructor
MyThreadClass(Handler oHandler)
{
HandlerOfCaller = oHandler;
}
// **********************************
// Public methods
// **********************************
// This method is run when the thread
// is started by calling start().
@Override
public void run()
{
try
{
// Flag that process does not need stopping
mblnStop = false;
mbytStatus = STATE_RUNNING;
// Do something time consuming
for (int i = 0; i < 10; i++)
{
// Check if we need to stop executing this code
if (mblnStop)
break; // exit the loop to end the process
// Update the progress of the file upload
Message oMessage = HandlerOfCaller.obtainMessage();
int intPercentageComplete = i*10;
oMessage.arg1 = intPercentageComplete;
oMessage.what = MESSAGE_PROGRESS;
HandlerOfCaller.sendMessage(oMessage);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
mbytStatus = STATE_DONE;
// Return the value calculated in our process
// Hardcoded to 1654 for demonstration purposes
Message oMessage = HandlerOfCaller.obtainMessage();
oMessage.arg1 = 1654;
oMessage.what = MESSAGE_COMPLETE;
HandlerOfCaller.sendMessage(oMessage);
}
catch (Exception ex)
{
// Return the error via the handler
Message oMessage = HandlerOfCaller.obtainMessage();
Bundle oBundle = new Bundle();
String strMessage = ex.getMessage();
oBundle.putString("Message", strMessage);
oMessage.setData(oBundle);
oMessage.what = MESSAGE_ERROR;
HandlerOfCaller.sendMessage(oMessage);
}
}
// Set internal flag to stop threaded process
public void StopThread()
{
mblnStop = true;
}
// Get the running status of the threaded process
public byte GetStatus()
{
return mbytStatus;
}
}
|
In the code above the messages what
integer property is used to flag the meaning the of the message, similar to the way the thread running status was
enumerated before here the message type is enumerated using public constants defined in the thread class. A value
must be set for the what property of the message but this can be any integer you wish to use.
Other useful properties of the message are arg1
and arg2
which can hold integer values, if you need to pass more data or other datatypes, eg a string, use the setData()
method to pass the message an instance of a Bundle. Obviously the more data you pass and the more complex the
datatypes then the more costly it is in processor and memory use.
|
[Code sample – UI activity class with event handling code]
|
public class MyMainUiActivity extends Activity implements OnClickListener
{
// ***************************
// Private variables
// ***************************
// Worker thread class
private MyThreadClass moMyThreadClass = null;
// ***************************
// Activity Lifetime Events
// ***************************
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button oButton = (Button)findViewById(R.id.btnTest);
oButton.setOnClickListener((OnClickListener) this);
}
// *****************************
// Event handling
// *****************************
/** Add the onClick event handler
* Implement the OnClickListener callback **/
public void onClick(View v)
{
// do something when the button is clicked
switch(v.getId())
{
case R.id.btnTest:
// Create instance of the worker thread class
moMyThreadClass = new MyThreadClass(moHandlerOfThis);
// Set values for use in the thread
// These could come from anywhere
// eg user input fields
moMyThreadClass.Email = "jim@eigo.co.uk";
moMyThreadClass.Name = "James T. Kirk";
moMyThreadClass.Latitude = -3.2155899;
moMyThreadClass.Quantity = 9;
/* Example code - how to stop thread
// Check if thread is running
if (moMyThreadClass.GetStatus() == MyThreadClass.STATE_RUNNING)
moMyThreadClass.StopThread(); // Stop the thread
*/
// Start the worker thread
moMyThreadClass.start();
break;
}
}
// Define the Handler that receives messages
// from the thread and update the progress
// Event handler for this Activity
final Handler moHandlerOfThis = new Handler()
{
public void handleMessage(Message oMessage)
{
TextView txtOutput = (TextView)findViewById(R.id.txtOutput);
// Decide what to do
switch (oMessage.what)
{
case MyThreadClass.MESSAGE_COMPLETE:
// Check the value returned
txtOutput.setText("Process complete, result = "
+ String.valueOf(oMessage.arg1));
moMyThreadClass = null;
break;
case MyThreadClass.MESSAGE_ERROR:
// Show the error message
txtOutput.setText("Process failed, error = "
+ oMessage.getData().getString("Message"));
// Get rid of the sending thread
moMyThreadClass.StopThread();
moMyThreadClass = null;
break;
case MyThreadClass.MESSAGE_PROGRESS:
// Show the progress message
txtOutput.setText("Thread progress = "
+ String.valueOf(oMessage.arg1) + "%");
break;
default:
// This should never occur
txtOutput.setText("Unknown message type = "
+ String.valueOf(oMessage.what));
// Get rid of the sending thread
moMyThreadClass.StopThread();
moMyThreadClass = null;
break;
}
}
};
}
|
In this code sample the Handler is passed to the worker thread on instantiation. The messages returned from the
worker thread cause the handleMessage() function to be called. In this function the what property of the message is
checked to decide how/if to respond to the event.
End of Part 1
The
second part of this article describes how to show a progress dialog while running a thread and how to
keep both worker thread and progress dialog alive during screen rotations (which destroy and recreate Activities).