Android Progress Bar and Thread Updating
Android Progress Bar and Thread Updating
Android Progress Bar and Thread Updating
There are two text fields. One to set the maximum value of the progress bar and one to set the increment value. When pushing the Start button a progress bar is displayed and in this example it will just update the progress bar every half a second (500ms) to demonstrate the result.
public class progressthread extends Activity implements OnClickListener { ProgressDialog dialog; int increment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button startbtn = (Button) findViewById(R.id.startbtn); startbtn.setOnClickListener(this); } public void onClick(View view) { // get the increment value from the text box EditText et = (EditText) findViewById(R.id.increment); // convert the text value to a integer increment = Integer.parseInt(et.getText().toString()); dialog = new ProgressDialog(this); dialog.setCancelable(true); dialog.setMessage("Loading..."); // set the progress to be horizontal dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // reset the bar to the default value of 0 dialog.setProgress(0); // get the maximum value EditText max = (EditText) findViewById(R.id.maximum); // convert the text value to a integer int maximum = Integer.parseInt(max.getText().toString()); // set the maximum value dialog.setMax(maximum); // display the progressbar dialog.show(); // create a thread for updating the progress bar Thread background = new Thread (new Runnable() { public void run() {
try { // enter the code to be run while displaying the progressbar. // // This example is just going to increment the progress bar: // So keep running until the progress value reaches maximum value while (dialog.getProgress()<= dialog.getMax()) { // wait 500ms between each update Thread.sleep(500); // active the update handler progressHandler.sendMessage(progressHandler.obtainMessage()); } } catch (java.lang.InterruptedException e) { // if something fails do something smart } } }); // start the background thread background.start(); } // handler for the background updating Handler progressHandler = new Handler() { public void handleMessage(Message msg) { dialog.incrementProgressBy(increment); } }; }
To present the progress of loading in most of the applications, we often use progress bars.
Introduction
Most of the applications (mobile, desktop...) need to charge some resources for a further use, from the local system or a remote one (like for web apps). A standard way to present the progress of this loading is the progress bar. The progress bar may have different forms. The most used ones are the circular one (for an undermined duration or amount of resources) or linear one for the other case. In this tutorial, well try to use the XML layout file, to present those two types of presenting the progress of loading.
The HowTo
To implement a ProgressBar, a Runnable Thread is used transmitt a message to a Handle to move the progress forward in the ProgressBar. To do it we'll have to modify the XML layout file main.xml (under res/layout) to add those two ProgressBar.
<?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"
> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/progressbar_default" /> <ProgressBar android:layout_width="fill_parent" android:layout_height="wrap_content" style="?android:attr/progressBarStyleHorizontal" android:id="@+id/progressbar_Horizontal" android:max="100" /> </LinearLayout>
Once finished with the layout file, we modify the main java file (MainActivity here) like this:
package com.wikinut.android; import import import import import android.app.Activity; android.os.Bundle; android.os.Handler; android.os.Message; android.widget.ProgressBar;
public class MainActivity extends Activity { ProgressBar myProgressBar; int myProgress = 0; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); myProgressBar=(ProgressBar)findViewById(R.id.progressbar_Horizontal); new Thread(myThread).start(); } private Runnable myThread = new Runnable(){ @Override public void run() { // TODO Auto-generated method stub while (myProgress<100){ try{
myHandle.sendMessage(myHandle.obtainMessage()); Thread.sleep(1000); } catch(Throwable t){ } } } Handler myHandle = new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub myProgress++; myProgressBar.setProgress(myProgress); } }; }; }
Run that as usual and you should have something similar to this:
ANOTHER EXAMPLES
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,
A7, Stephanos Tower, Eachamukku, Kakkanadu,Kochi
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.
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.
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>
public class ProgressBarExample extends Activity { ProgressThread progThread; ProgressDialog progDialog; Button button1, button2;
// Determines type progress bar: 0 = spinner, 1 // Milliseconds of delay in the update loop // 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 UI thread // so that it can update the progress bar. total as data) to Handler on
Message msg = mHandler.obtainMessage(); Bundle b = new Bundle(); b.putInt("total", total); msg.setData(b); mHandler.sendMessage(msg); total--; } } // Set current state of thread (use state=ProgressThread.DONE to stop thread) public void setState(int state) { mState = state; } } } // Count down
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)
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.
3. 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.
4. 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).
For the first case we create an instance progDialog of ProgressDialog using its constructor (passing to it the reference this to our Activity for the Context).
We then invoke the ProgressDialog method setProgressStyle(int style) to set the style using the class constant ProgressDialog.STYLE_SPINNER, and use the method setMessage(CharSequence message) to set a message for the dialog box. A7, Stephanos Tower, Eachamukku, Kakkanadu,Kochi
We shall define below an inner class ProgressThread to handle update of the progress bar from a separate thread. So we create a new instance progThread of ProgressThread, passing to its constructor the Handler object handler that we will define below.
Finally we start this thread by issuing the start() method that progThread inherits from the Thread class.
For the second case we do the same as for the first case, except we use the class constant ProgressDialog.STYLE_HORIZONTAL to specify a horizontal progress bar, and use the setMax(int) method to set a maximum value for the progress bar (the default is 100 if this parameter isn't set).
5. 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.
6.
o
The chained expression msg.getData().getInt("total") first uses the method getData() that msg inherits from Message to return a Bundle of data associated with the message (that is sent by the class ProgressThread discussed below), and then the Bundle method getInt (String key) extracts from the Bundle the integer associated with the key "total" and assigns it to the variable total.
We then use the ProgressDialog method setProgress(int value) to set the progress bar to a value of total. Note that we don't have to do anything to cause the progress bar to reflect this update on the screen. ProgressDialog is managing that.
Finally we do some logic on total to see if it has counted down to zero and close the progress dialog and stop the update thread if it has.
7. The inner class ProgressThread implements our thread to update the progress bar by extending Thread and overriding its run() method.
After introducing some variables we define a constructor ProgressThread(Handler h) that allows the Handler defined on the creating thread (see the invocation of the constructor in the onCreateDialog(int id) method above) to be passed to the instance of ProgressThread as the argument h, and we set the local Handler variable mHandler in the instance of ProgressThread equal to h.
We then override the run() method of Thread, setting the integer variable mState equal to RUNNING and implementing a while-loop that executes as long as mState = RUNNING.
In the while-loop we count the variable total down incrementally from its initial value of maxBarValue.
We control the speed of this countdown (approximately) by calling within the loop the static method Thread.sleep(long delay), where delay is the requested delay in milliseconds. The actual amount of delay is under system control, so the delay each time through the loop will not be precise but is likely to be close to the requested amount. Since the sleep(long delay) method throws InterruptedException, the Thread.sleep(delay) request must be wrapped in a try-catch clause to catch the exception.
Each time through the loop, we send a message to the Handler object on the main thread. We place in the data Bundle of the message any data that we wish to communicate to the main thread. In this simple example we send only the current value of total, referenced by the key "total".
Although the Message class has a public constructor, it is more efficient to create new messages by using either the static Message.obtain() method or the Handler method obtainMessage() to get one. These methods will allocate new messages from a pool of recycled objects, which is more efficient than creating and allocating new instances. In this case mHandler.obtainMessage() is used to create the new Message msg.
We create a new Bundle b and use the Bundle method putInt (String key, int value) to insert the value of total with a key "total", and use the Message method setData(Bundle b) to add the data bundle b to msg.
Then, we use the Handler method sendMessage(Message msg) to push the message onto the end of the message queue that will be received by the Handler object handler on the main thread through its handleMessage(Message msg) method. This message will cause the progress bar defined on the main UI thread to update each time though the while-loop that is executing on the second thread.
Finally, we define the method setState(int state) to change the value of mState and thus to stop execution of the while-loop in the second thread when we are finished.
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.