Part 2 of 2
Intro
This article continues the discussion of using threads in Android activities started in the previous article;
Programming threaded processes in Android.
This article describes how to show a progress dialog and keep a thread and progress dialog working across screen rotation on Android.
Using a Progress Dialog in the UI
A progress dialog
is part of the Android API and can be used to display either a progress bar or a spinning progress symbol.
Dialogs are shown by
handling the activity’s onCreateDialog(int) event
, there is a handy tutorial on dialogs
on the Android developer website.
Continuing using our activity class from the last article, first thing we need to do is add a private constant to use to identify the dialog and a private field to keep a reference to the dialog.
|
[Code sample – constant and variable declarations]
|
// ***************************
// Private constants
// ***************************
private static final int DIALOG_PROGRESS = 1;
// ***************************
// Private variables
// ***************************
// Progress dialog for when posting to servers
private ProgressDialog moProgressDialog = null;
|
Secondly in the onClick handler just before we start the worker thread add some code to show the dialog.
|
[Code sample – how to show a dialog]
|
// Show the image dialog
showDialog(DIALOG_PROGRESS);
|
Finally add a handler for the onCreateDialog(int) event of the activity.
|
[Code sample – onCreateDialog event handler]
|
// Handle the event for creation of dialogs
@Override
protected Dialog onCreateDialog(int id)
{
switch(id)
{
case DIALOG_PROGRESS:
moProgressDialog = new ProgressDialog(MyMainUiActivity.this);
moProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
moProgressDialog.setTitle("Please Wait");
moProgressDialog.setMessage("Worker process is busy");
return moProgressDialog;
default:
return null;
}
}
|
This code actually creates the dialog instance.
Updating the Progress Dialog
Now that the progress dialog exists and is displayed we need to update it with the percentage of progress made so far in our worker thread and the close the dialog once our work is finished.
In the handleMessage() function when the message returned is MESSAGE_PROGRESS, add the following code to set the position of the progress bar to the percentage of progress so far.
|
[Code sample – update progress bar of dialog]
|
// Update the progress bar
moProgressDialog.setProgress(oMessage.arg1);
|
When the message returned is anything else, for example complete or error, the following code closes the dialog.
|
[Code sample – close progress dialog]
|
// Close the progress dialog
dismissDialog(DIALOG_PROGRESS);
|
The new handler code looks like this:
|
[Code sample – thread event handler]
|
// 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;
// Close the progress dialog
dismissDialog(DIALOG_PROGRESS);
break;
case MyThreadClass.MESSAGE_ERROR:
// Show the error message
txtOutput.setText("Process failed, error = "
+ oMessage.getData().getString("Message"));
// Close the progress dialog
dismissDialog(DIALOG_PROGRESS);
// Get rid of the sending thread
moMyThreadClass.StopThread();
moMyThreadClass = null;
break;
case MyThreadClass.MESSAGE_PROGRESS:
// Update the progress bar
moProgressDialog.setProgress(oMessage.arg1);
break;
default:
// This should never occur
txtOutput.setText("Unknown message type = "
+ String.valueOf(oMessage.what));
// Close the progress dialog
dismissDialog(DIALOG_PROGRESS);
// Get rid of the sending thread
moMyThreadClass.StopThread();
moMyThreadClass = null;
break;
}
}
};
|
Screenshot of Progress Dialog in use
Screen Rotation: Activity Lifecycle
When the screen orientation is changed the activity is closed and destroyed then created again. This means that all the variables and references are lost, this is no good if the worker thread is in the middle of a process because the activity will not be able to respond to its events.
To solve this problem a reference the worker thread needs to be kept and any state information from the activity that needs preserving needs to be stored. The kind of state information that you would want to preserve is say the text of a TextView or the checked status of a CheckBox.
There are two common places to store state information:
- The saved instance state bundle which can be written to in the handler of the
onSaveInstanceState(Bundle) activity event
and read out again in the
onCreate(Bundle) activity event.
- The SharedPreferences
for the application by writing to the in the
onPause() activity event
, or some other event, and reading them out again in the
onCreate(Bundle) activity event.
This article will not cover storing state information in any more detail but a future article with code examples might be considered.
Another option is to force the screen not to rotate; the topic is also out of scope for this article but there is another article on this website:
Lock Screen Orientation in Android
Screen Rotation: Keeping a Reference to the Thread
As mentioned in the previous section a reference to the worker process thread needs to be kept so that the progress dialog can continue to be updated and the final result acted up in the activity.
This is done by handling the
onRetainNonConfigurationInstance() activity event
to store the worker thread class instance when the form is destroyed.
|
[Code sample – how to retain a class instance after activity destruction]
|
// Store the sending thread between screen rotations
@Override
public Object onRetainNonConfigurationInstance()
{
removeDialog(DIALOG_PROGRESS);
// Check that there is a worker thread that
// needs preserving
if (moMyThreadClass != null)
{
// remove reference to this activity
// (important to avoid memory leak)
moMyThreadClass.HandlerOfCaller = null;
// Return the instance to be retained
return(moMyThreadClass);
}
return super.onRetainNonConfigurationInstance();
}
|
Screen Rotation: Restoring the Thread and Process Dialog
After rotation has changed the screen orientation and destroyed the activity the activity is created again and the thread instance needs to be restored and the progress dialog updated.
To get the thread class back again while the form is reloaded with the new orientation call
getLastNonConfigurationInstance()
in the
onCreate(Bundle) activity event
. Check the status of the thread and show/hide the progress dialog accordingly.
|
[Code sample – how to restore a class instance after activity creation]
|
/** 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);
// Get the last sending thread if it was still going
if (getLastNonConfigurationInstance() != null)
{
TextView txtOutput =
(TextView)findViewById(R.id.txtOutput);
// Check that it is still running
moMyThreadClass =
(MyThreadClass)getLastNonConfigurationInstance();
moMyThreadClass.HandlerOfCaller = moHandlerOfThis;
// Check that it is still running
switch (moMyThreadClass.GetStatus())
{
case MyThreadClass.STATE_RUNNING:
// Show the progress dialog again
showDialog(DIALOG_PROGRESS);
break;
case MyThreadClass.STATE_NOT_STARTED:
// Close the progress dialog in case it is open
dismissDialog(DIALOG_PROGRESS);
break;
case MyThreadClass.STATE_DONE:
// Check the value returned
txtOutput.setText("Process complete, result = "
+ String.valueOf(moMyThreadClass.Result));
moMyThreadClass = null;
// Close the progress dialog in case it is open
dismissDialog(DIALOG_PROGRESS);
break;
default:
// This should never occur
txtOutput.setText(
"Unknown MyThreadClass status type = "
+ String.valueOf(moMyThreadClass.GetStatus()));
// Close the progress dialog in case it is open
dismissDialog(DIALOG_PROGRESS);
// Get rid of the sending thread
moMyThreadClass.StopThread();
moMyThreadClass = null;
break;
}
}
}
|
It is also important to pass our worker thread the handler of the activity to its HandlerOfCaller field so that it can raise events for the activity to capture during the continuing execution.