Progress Bar Example
When we must wait for some task to complete in a modern graphical user interface the interface normally provides visual feedback that something is happening, often with an indication of how much progress has been made on the task. The widgets providing this information are commonly termed progress bars (though they need not be bars), and are essential to providing a sense of application responsiveness for the user.
Implementing a progress bar also provides an opportunity to illustrate two other important Android concepts: running processes on threads separate from the main user interface (UI) and communicating between threads. In this example we shall demonstrate how to implement progress bars and to update them from processes running on a separate thread. Parts of this exercise have been adapted from the Android progress bar example.
A progress bar could be created using the Android ProgressBar class but the simplest approach for a generic implementation is to use the ProgressDialog class, which subclasses AlertDialog and has methods to open and manage a dialog window with a progress bar embedded in it that floats over the dimmed original window. There are two basic types of progress bars that we can implement using the ProgressDialog class:
In this example we shall illustrate how to use ProgressDialog to implement both types of progress bars, and will also illustrate a way to update a progress bar from a thread that is separate from the main UI thread.
Threads are instances of the Thread class that are concurrent units of execution. A thread has its own call stack for methods being invoked, their arguments and local variables. When an app starts, it launches in its own virtual machine. Each such virtual machine instance has at least one main thread running when it is started, there typically will be others invoked by the system for housekeeping, and the application may create additional threads for specific purposes. A common use of threads in Android applications is to move time-consuming and potentially blocking operations such as computationally-intensive loops and network operations off the main UI thread so that they do not compromise responsiveness of the user interface.
There are two standard ways of implementing threads:
In either case, the resulting thread is not executed until its start() method is invoked. We shall give an example of the second approach in the progress bar example below and an example of the first approach in the project Animator Demo.
As we have seen, to promote UI responsiveness it is often desirable to offload tasks to new threads that the app spawns. Usually this means that at some stage the main UI thread and the new thread must communicate, but there are some strict rules about communication between threads that must be respected. The issue is particularly acute if the task on the new thread wishes to modify views associated with main UI thread, because it is strictly forbidden to do so directly.
In particular, a common task in animation programming is to modify the parameters defining some graphics on the screen implemented through a View within a repetitive loop (with some delay to control speed of change), and then to call the invalidate() method of View to request that the View redraw itself as soon as possible each time through the loop. If the graphics parameters for a View defined on the main UI thread are changed in a loop running on a separate thread, the non-UI thread is forbidden to call invalidate() on the View because it is on a different thread. Since the View cannot change unless it is redrawn, we must circumvent this restriction if we are to animate something from a separate thread. There are three common ways to do this.
We shall discuss these alternative approaches in more depth in the Animator Demo project. In the present example we shall use the second approach (a Handler), and in subsequent exercises we shall illustrate use of the other two approaches.
Create a new project in Eclipse with the following particulars.
First define some strings and set the entry-screen layout. Edit res/values/strings.xml to read
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">ProgressBarExample</string>
<string name="app_name">Threaded Progress Bar Example</string>
<string name="Button01Text">Show Spinner Progress Bar</string>
<string name="Button02Text">Show Horizontal Progress Bar</string>
</resources>
and edit res/layout/main.xml to read
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<Button android:text="@string/Button01Text" android:id="@+id/Button01"
android:layout_width="fill_parent" android:layout_height="wrap_content">
</Button>
<Button android:text="@string/Button02Text" android:id="@+id/Button02"
android:layout_width="fill_parent" android:layout_height="wrap_content">
</Button>
</LinearLayout>
Then open src/<namespace>/ProgressBarExample.java and edit it to read
package com.lightcone.progressbarexample;
import android.app.Activity;
import android.app.Dialog;
import android.os.Message;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.widget.Button;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
public class ProgressBarExample extends Activity {
ProgressThread progThread;
ProgressDialog progDialog;
Button button1, button2;
int typeBar; // Determines type progress bar: 0 = spinner, 1 = horizontal
int delay = 40; // Milliseconds of delay in the update loop
int maxBarValue = 200; // Maximum value of horizontal progress bar
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Process button to start spinner progress dialog with anonymous inner class
button1 = (Button) findViewById(R.id.Button01);
button1.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
typeBar = 0;
showDialog(typeBar);
}
});
// Process button to start horizontal progress bar dialog with anonymous inner class
button2 = (Button) findViewById(R.id.Button02);
button2.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
typeBar = 1;
showDialog(typeBar);
}
});
}
// Method to create a progress bar dialog of either spinner or horizontal type
@Override
protected Dialog onCreateDialog(int id) {
switch(id) {
case 0: // Spinner
progDialog = new ProgressDialog(this);
progDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progDialog.setMessage("Loading...");
progThread = new ProgressThread(handler);
progThread.start();
return progDialog;
case 1: // Horizontal
progDialog = new ProgressDialog(this);
progDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progDialog.setMax(maxBarValue);
progDialog.setMessage("Dollars in checking account:");
progThread = new ProgressThread(handler);
progThread.start();
return progDialog;
default:
return null;
}
}
// Handler on the main (UI) thread that will receive messages from the
// second thread and update the progress.
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
// Get the current value of the variable total from the message data
// and update the progress bar.
int total = msg.getData().getInt("total");
progDialog.setProgress(total);
if (total <= 0){
dismissDialog(typeBar);
progThread.setState(ProgressThread.DONE);
}
}
};
// Inner class that performs progress calculations on a second thread. Implement
// the thread by subclassing Thread and overriding its run() method. Also provide
// a setState(state) method to stop the thread gracefully.
private class ProgressThread extends Thread {
// Class constants defining state of the thread
final static int DONE = 0;
final static int RUNNING = 1;
Handler mHandler;
int mState;
int total;
// Constructor with an argument that specifies Handler on main thread
// to which messages will be sent by this thread.
ProgressThread(Handler h) {
mHandler = h;
}
// Override the run() method that will be invoked automatically when
// the Thread starts. Do the work required to update the progress bar on this
// thread but send a message to the Handler on the main UI thread to actually
// change the visual representation of the progress. In this example we count
// the index total down to zero, so the horizontal progress bar will start full and
// count down.
@Override
public void run() {
mState = RUNNING;
total = maxBarValue;
while (mState == RUNNING) {
// The method Thread.sleep throws an InterruptedException if Thread.interrupt()
// were to be issued while thread is sleeping; the exception must be caught.
try {
// Control speed of update (but precision of delay not guaranteed)
Thread.sleep(delay);
} catch (InterruptedException e) {
Log.e("ERROR", "Thread was Interrupted");
}
// Send message (with current value of total as data) to Handler on UI thread
// so that it can update the progress bar.
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
b.putInt("total", total);
msg.setData(b);
mHandler.sendMessage(msg);
total--; // Count down
}
}
// Set current state of thread (use state=ProgressThread.DONE to stop thread)
public void setState(int state) {
mState = state;
}
}
}
If you compile and execute this code, you should see a display like the left figure below if the top button is pressed and a display like the right figure below if the bottom button is pressed,
![]() |
![]() |
where in the right figure we have implemented a variation where the progress bar counts down rather than up.
The comments in the file ProgressBarExample.java outline the functionality but we give a somewhat more expansive description here.
| Beginning with Android 2.2, showDialog(int id) has been deprecated in favor of showDialog(int id, Bundle args) and onCreateDialog(int id) has been deprecated in favor of onCreateDialog(int, Bundle args), where args are arguments you can pass through to the dialog. The older implementation continues to work since the default implementation calls through to onCreateDialog(int) for compatibility and showDialog(int id) calls showDialog(int id, Bundle args) with null args. Note: the current Android documentation describes these methods under the newer forms. |
| A Handler sends and processes Message and Runnable objects associated with a thread's MessageQueue. When a new Handler is created, it is bound to the thread that creates it and its message queue; subsequently, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue. In the present example handler is created on the main UI thread and so is bound to the UI thread and its message queue. |
In this example the updating task (counting down integers) was a trivial one and it wasn't really essential to move the update to a separate thread. But this simple case serves as a prototype of how to update progress to the UI thread from a secondary thread for cases where the task on the second thread might not be so trivial.
| The complete project for the application described above is archived at the link ProgressBarExample. |