In this lesson we are just going to have fun messing around with threads. Threads are useful when your app needs to launch a time intensive task and you do not want to freeze up the user interface. Just for fun we will mimic this by calling our encrypt method and then sleep the main UI thread to mimic a time intensive task.
Here is our new "time intensive encrypt" method which just wraps the real encrypt method and pauses the UI thread:
// PRACTICE ONLY THREADED ENCRYPTION
private String encrypt(String key, String inString) {
String outString= "";
try {
try {
Thread.sleep(4000); // <== MIMIC a time intensive task
Log.d(TAG,"ThreadSleep");
}
catch (InterruptedException e){
Log.d(TAG,"encryt",e);
}
outString= model.encrypt(inString, key);
}
catch(GeneralSecurityException e){
Log.d(TAG,"Thread",e);
}
catch(Exception e){ // model could be null
Log.d(TAG,"Thread",e);
}
return outString;
}
Now we just call this method in the Confuse Text button handler as:
// CONFUSE TEXT HANDLER
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String inString= editTextPlainText.getText().toString();
String outString= encrypt(password,inString);
}
};
Now when we run the app and click on the Confuse Text button we get:
Better Karma with a Background Thread
To fix this, we will run our "long running task" in a separate thread using a new thread and a runnable method. Here is our threaded Confuse Text Button Handler:
// CONFUSE TEXT HANDLER
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String inString= editTextPlainText.getText().toString();
Thread thread= new Thread( new Runnable() {
public void run() {
String outString= encrypt(password,inString);
Log.d(TAG,outString);
}
});
thread.setDaemon(true); // <== I am a service provider. KILL ME if the non-daemon thread ConfuseText quits
thread.start();
}
};
Run the app and hit the "Confuse Text" button. Note that the thread does not return, yet you can still press the "Confuse Text" button again. After about 4 seconds the following is logged in LogCat:
Cool. And no "not responding" message.
Do Not Touch My View
Do NOT try to touch the main thread view elements in the background thread. "The Android UI toolkit is not thread-safe." If we add the call to a main thread view element in our encrypt method as:
// PRACTICE ONLY THREADED ENCRYPTION
private String encrypt(String key, String inString) {
String outString= "";
try {
try {
Thread.sleep(4000);
Log.d(TAG,"ThreadSleep");
}
catch (InterruptedException e){
Log.d(TAG,"encryt",e);
}
outString= model.encrypt(inString, key);
editTextConfusedText.setText(outString);
}
catch(GeneralSecurityException e){
Log.d(TAG,"Thread",e);
}
catch(Exception e){ // model could be null
Log.d(TAG,"Thread",e);
}
return outString;
}
We will catch the following exception, shown in the LogCat window.
More bad karma.
Communicating From Our New Thread Using Handler
So how DO we communicate between the main and background thread? Well, we can do this with Handler or AsyncTask. We are going to tackle Handlers here. In the next lesson we will tackle AsyncTask.
Handlers let you send messages from other threads to the main thread. We create a Handler in the main thread and it becomes bound to the main thread. The handler then processes messages sent to the Handler. Messages sent to the handler "queue" up. So in our main UI class we instantiate an instance of Handler as an inner class and override the handler method handleMessage. We then process messages in the handleMessage method. Here is our Handler code:
public class ConfuseText extends Activity {
private Handler handler= new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
ConfuseText.this.onThreadMessage(msg);
}
};
Here is our very complicated onThreadMessage code:
public void onThreadMessage(Message msg){
Bundle b= msg.getData();
String encryptedText="";
if (b != null){
encryptedText= b.getString("encryptedText");
}
Log.d(TAG,encryptedText);
}
OK. Now we just need to post a message from our background thread to the main thread when the time intensive task returns. We do this in the onClick handler as:
// CONFUSE TEXT HANDLER
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String inString= editTextPlainText.getText().toString();
Thread thread= new Thread( new Runnable() {
public void run() {
String outString= encrypt(password,inString);
Message msg= Message.obtain();
Bundle b= new Bundle();
b.putString("encryptedText",outString);
msg.setData(b);
handler.sendMessage(msg);
Log.d(TAG,outString);
}
});
thread.setDaemon(true);
thread.start();
}
});
Thats all folks! When the "time consuming" encrypt process returns, we post a message to the handler's message queue containing the encrypted text. When the message is processed we log it to LogCat.
This if fun stuff!
Updating the UI
The onThreadMessage method is in the main thread and so it CAN touch the UI elements. To update the UI just add a call to the UI element as in:
public void onThreadMessage(Message msg){
Bundle b= msg.getData();
String encryptedText="";
int length= 0;
if (b != null){
encryptedText= b.getString("encryptedText");
length= encryptedText.length();
}
Log.d(TAG,encryptedText);
editTextConfusedText.setText(encryptedText);
}
The text is displayed after a little delay. This is also a great place to display the results of the time intensive task in a separate Activity or Dialog.
Using myHandler.post(myRunnable)
In this lesson we are using the architecture provided by the API, sending stateful messages to a handler where the messages are processed in handleMessage. Another approach is to queue runnables using the call myHandler.post(myRunnable). That approach is not presented here. So we are using a queue of state-full messages, not a queue of Runnables. I think the use of state-full messages is easier to understand, especially when we are passing parameters and obtaining return values from a time intensive task. Secondly, since each message holds its own state, the likelihood of a race condition is decreased. As best I can tell using Runnables appears to rely on "Shared Memory Communication" as opposed to "Message Passing Communication" demonstrated here. According the wiki Concurrent computing:
"Message-passing concurrency tends to be far easier to reason about than shared-memory concurrency, and is typically considered a more robust form of concurrent programming."
Giving the User Feedback: Progress Clock
In the following section, we will create a timer that reports back every one second. Note: Android also supplies a Timer class. Other classes include a CountDownTimer and a ProgressDialog. Our unconstrained timer meets a slightly different need, providing a simple (non-blocking) way to update the UI every second until canceled. First we define two variables:
private boolean isDoneThinking= true;
private int thinkSeconds= 0;
Then we add a handler. Note that we have only one handler attached to the ConfuseText activity. This is accomplished by adding a switch statement to the method handleMessage. In this sample code, there is only one message set that we are interested in: [message.what = "0"]. The timer is implemented by setting up a message loop, consuming and sending delayed messages approximately every one second (1000 milliseconds).
private Handler myHandler= new Handler(){
@Override
public void handleMessage(Message msg){
switch(msg.what){
case 0:
if (!isDoneThinking){
editTextConfusedText.setText("Still Thinking "+new Integer(thinkSeconds).toString());
thinkSeconds++;
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0),1000); // <== Loop on delayed messages every second
}
else {
thinkSeconds= 0; // reset timer
}
break;
default:
super.handleMessage(msg);
break;
}
}
};
We launch the timer in onClick. We simply send an empty message with a what value of "0".
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
isDoneThinking= false;
myHandler.sendEmptyMessage(0); <== starts timer with what value of "0"
}
};
The timer is unconstrained and will continue to count until the flag isDoneThinking is set to true. Here is our timer in action:
This timer can be used to update the UI as above or it can be used to check on the progress of a UI update every second.
Note: The runtime timers are not very accurate and this architecture assumes that the calls in handle message execute in zero seconds! If you need to add precision to this counter, consider modifying the message delay time on each call to sendMessageDelayed (see System.currentTimeMilis()).
Using Best Practices
Our threaded code should comply with best practices:
1) There shall be only on handler per Activity.
2) Try to use the properties available in Messages, rather than sub-classing.
We use the "what" identifier to route messages. We use what "0" to loop our timer and what "1" to signal the end of our "time intensive task."
private Handler myHandler= new Handler(){
@Override
public void handleMessage(Message msg){
switch(msg.what){
case 0:
if (!isDoneThinking){
editTextConfusedText.setText("Still Thinking "+new Integer(thinkSeconds).toString());
thinkSeconds++;
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0),1000); // <== Loop on delayed messages every second
}
else {
thinkSeconds= 0; // reset timer
}
break;
case 1:
isDoneThinking= true;
onThreadMessage(msg); // you could launch a new Activity here to display results
break;
default:
super.handleMessage(msg);
break;
}
}
};
public void onThreadMessage(Message msg){
Bundle b= msg.getData();
String encryptedText="";
if (b != null){
encryptedText= b.getString("encryptedText");
}
editTextConfusedText.setText(encryptedText);
Log.d(TAG,encryptedText);
}
We need to slightly modify our calling code to include an identifying what value:
// CONFUSE TEXT HANDLER
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
final String inString= editTextPlainText.getText().toString();
isDoneThinking= false;
myHandler.sendEmptyMessage(0); // <=== START TIMER
Thread thread= new Thread( new Runnable() { // <== START THREADED RUNNABLE
public void run() {
String outString= encrypt(password,inString);
Message msg= myHandler.obtainMessage(1);
Bundle b= new Bundle();
b.putString("encryptedText",outString);
msg.setData(b);
myHandler.sendMessage(msg);
Log.d(TAG,outString);
}
});
thread.setDaemon(true);
thread.start();
}
};
Finally, here again is our "time intensive task":
// PRACTICE ONLY THREADED ENCRYPTION
private String encrypt(String key, String inString) {
String outString= "";
try {
try {
Thread.sleep(4000); // <== MIMIC a time intensive task
Log.d(TAG,"ThreadSleep");
}
catch (InterruptedException e){
Log.d(TAG,"encryt",e);
}
outString= model.encrypt(inString, key);
}
catch(GeneralSecurityException e){
Log.d(TAG,"Thread",e);
}
catch(Exception e){ // model could be null
Log.d(TAG,"Thread",e);
}
return outString;
}
Now when the "time intensive task" completes the timer is stopped!
Handler Safe
Using a single handler means that all messages should queue up and block in this single method. This should simplify concurrent programming logic using message based concurrency.
onPause
Even though we setDaemon(true), consider calling cancel() on running threads in onPause.
Writing Threaded Code
Writing threaded code is not easy. In this lesson we stuck to the API support for threaded code using the existing strategy of using a message handler, sending stateful messages to the message queue and then touching UI variables and views from the UI thread. If you really want to experiment with threads, check out the producer-consumer idiom. No reason to re-invent the wheel.
Using AsyncTask and ProgressDialog
In the next lesson we will produce a very similar program using AsyncTask and a ProgressDialog. Hopefully, seeing the two different approaches to the same task will be informative.
Just Be Glad
Be glad that you are writing in Android. Here is some of my code from Windows Forms single threaded apartment programming trying to interact with .NET free threading. Windows Forms programming has thread affinity so that "all function calls to it must occur on its creation thread."
Here is an example of code that marshals a call back to the WinForm thread:
public void Form1_OnThreadedProcessEvent(object worker, ThreadedProcessEventArgs e)
{
if (textBoxOut.InvokeRequired)
{
this.BeginInvoke(new ThreadedProcess.ThreadedProcessEventHandler(Form1_OnThreadedProcessEvent),
new object[]{worker,e});
}
else
// back in main thread
{
textBoxOut.Text= e.ConsoleOutput;
textBoxError.Text= e.ConsoleError;
textBoxExitCode.Text= e.ExitCode.ToString();
}
}
Aach.
JAL