Having started what we know to be a potentially long-running task, we probably want to let the user know that something is happening. There are a lot of ways of doing this, but a common approach is to present a dialog displaying a relevant message.
A good place to present our dialog is from the onPreExecute
method of AsyncTask
, which executes on the main thread. Hence, it is allowed to interact with the user interface.
The modified PrimesTask
will need a reference to a Context
, so that it can prepare a ProgressDialog
, which it will show and dismiss in onPreExecute
and onPostExecute
respectively. As doInBackground
has not changed, it is not shown in the following code, for brevity:
public class PrimesTask extends AsyncTask<Integer, Void, BigInteger>{ private Context ctx; private ProgressDialog progress; private TextView resultView; public PrimesTask(Context ctx, TextView resultView) { this.ctx = ctx; this.resultView = resultView; } @Override protected void onPreExecute() { progress = new ProgressDialog(ctx); progress.setTitle(R.string.calculating); progress.setCancelable(false); progress.show(); } // … doInBackground elided for brevity … @Override protected void onPostExecute(BigInteger result) { resultView.setText(result.toString()); progress.dismiss(); } }
All that remains is to pass a Context
to the constructor of our modified PrimesTask
. As Activity
is a subclass of Context
, we can simply pass a reference to the host Activity
:
goButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { new PrimesTask( PrimesActivity.this, resultView).execute(500); } });
Knowing that something is happening is a great relief to our users, but they might be getting impatient and wondering how much longer they need to wait. Let's show them how we're getting on by adding a progress bar to our dialog.
Remember that we aren't allowed to update the user interface directly from doInBackground
, because we aren't on the main thread. How, then, can we tell the main thread to make these updates for us?
AsyncTask
comes with a handy callback method for this, whose signature we saw at the beginning of the chapter:
protected void onProgressUpdate(Progress… values)
We can override onProgressUpdate
to update the user interface from the main thread, but when does it get called and where does it get its Progress… values
from? The glue between doInBackground
and onProgressUpdate
is another of AsyncTask's methods:
protected final void publishProgress(Progress... values)
To update the user interface with our progress, we simply publish progress updates from the background thread by invoking publishProgress
from within doInBackground
. Each time we call publishProgress
, the main thread will be scheduled to invoke onProgressUpdate
for us with these progress values.
The modifications to our running example to show a progress bar are quite simple. First, we must change the class declaration to include a Progress
type. We'll be setting progress values in the range 0
to 100
, so we'll use Integer
:
public class PrimesTask
extends AsyncTask<Integer, Integer, BigInteger> {
Next, we need to set the style and the bounds of the progress bar. We can do that with the following additions to onPreExecute
:
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); progress.setProgress(0); progress.setMax(100);
We also need to implement the onProgressUpdate
callback to update the progress bar from the main thread:
@Override protected void onProgressUpdate(Integer... values) { progress.setProgress(values[0]); }
The final modification is to calculate the progress at each iteration of the for
loop, and invoke publishProgress
so that the main thread knows to call back onProgressUpdate
:
protected BigInteger doInBackground(Integer... params) { int primeToFind = params[0]; BigInteger prime = new BigInteger("2"); int progress = 0; for (int i=0; i<primeToFind; i++) { prime = prime.nextProbablePrime(); int percent = (int)((i * 100f)/primeToFind); if (percent > progress) { publishProgress(percent); progress = percent; } } return prime; }
It is important to understand that invoking publishProgress
does not directly invoke the main thread, but adds a task to the main thread's queue, which will be processed at some time in the near future by the main thread.
Notice that we're being careful to publish progress only when the percentage actually changes, avoiding any unnecessary overhead.
The delay between publishing the progress and seeing the user interface update will be extremely short, and the progress bar will update smoothly, provided we are careful to follow the golden rule of not blocking the main thread from any of our code.
3.14.245.221