Previous  | Next  | Home

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.

 

Progress Bars

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:

  1. A horizontal bar that fills to show a quantitative amount of progress. This style of progress bar is specified by the class constant ProgressDialog.STYLE_HORIZONTAL.

  2. A spinning symbol that indicates progress but of an indeterminate amount. This style of progress bar is specified by the class constant ProgressDialog.STYLE_SPINNER.

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

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:

  1. Create a new class that extends Thread and override (provide your own implementation of) its run() method.

  2. Provide a new Thread instance with a Runnable object during its creation using the Runnable interface. When the Runnable interface is invoked, you must provide an implementation of its run() method. This method will be called whenever a thread is started that was created with a class implementing the Runnable interface.

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.

 

Communication Between Threads

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.

  1. Instead of invoking the View method invalidate(), which can be called only from the UI thread, invoke the View method postInvalidate(), which can be called from a non-UI thread. Whereas invalidate() requests a redraw as soon as possible, postInvalidate() causes a redraw only during a subsequent iteration of the loop, which is why it is safe to invoke it from a separate thread.

  2. The second thread cannot touch a View on the main UI thread, but we can define a Handler on the main UI thread (typically by defining an inner class---inside the class defining the View---that subclasses Handler), send messages with data to it from the second thread, and use those messages to cause the Handler to invalidate the View on its own (UI) thread.

  3. The preceding two approaches define redraws on a Canvas supplied by the View through its onDraw method (which we override to define our drawing tasks). An alternative approach is to draw using a SurfaceView with a SurfaceHolder interface defined on a dedicated thread. The advantage of this approach is that it generally can handle faster animation because graphical changes can be rendered to the screen as fast as the thread is running instead of requesting a screen redraw using invalidate() or postInvalidate() that will happen on a timescale controlled by Android. The disadvantage is that in this case the programmer must obtain and manage the drawing Canvas, so its implementation is somewhat more complex.

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.

 

Creating the Project

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;
            }
        }
    }

Trying it Out

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.

 

How It Works

The comments in the file ProgressBarExample.java outline the functionality but we give a somewhat more expansive description here.

  1. The files main.xml and strings.xml are used to lay out an initial screen with two buttons in a manner that should be familiar from earlier examples.

  2. We then use findViewById to identify the buttons and attach clickListeners to them, with code to process the button events using two anonymous inner classes. The integer typeBar distinguishes whether we will launch an indeterminate progress bar (spinning symbol) or a horizontal progress bar. In both cases we initiate the progress dialog using the Activity method showDialog(int id) . The first execution of this method causes the Activity method onCreateDialog(int id) to be executed with the same value of id that was passed to showDialog(int id)

  3. 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.

  4. Since the Activity method onCreateDialog(int id) will be executed with id = showType when showDialog(int id) is called the first time by our code, we next override the onCreateDialog(int id) method.

  5. In our implementation of onCreateDialog(int id) we use a switch statement to distinguish whether we are to launch an indeterminate spinning progress bar (id = typeBar = 0) or a horizontal progress bar (id = typeBar = 1).

  6. Define the Handler object handler using an inner class. This uses the Handler method handleMessage(Message msg), where Message defines a message containing a description and arbitrary data object that can be sent to a Handler.

    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.

  7. The inner class ProgressThread implements our thread to update the progress bar by extending Thread and overriding its run() method.

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.


Previous  | Next  | Home