In the previous lesson we investigated using threads to run a "time intensive task." The Android OS provides a class that simplifies GUI background tasks. The provided AsyncTask class uses the magic of generics to generate a concrete class based on the types you pass in the class definition. You must pass the types for the three required "formal type parameters" <Params. Progress, Result> in the class definition. Here is the description of the three generic types from the docs:
Params
Progress
Result
The type of the parameters sent to the task upon execution.
The type of the progress units published during the background computation.
The type of the result of the background computation.
Note: The Result seems to be the most misunderstood and overlooked formal parameter. If your threaded task returns data, declare the data type here as the Result type! The AsyncTask will handle passing the result from doInBackground to onPostExecute for you. That is the design architecture and you cannot use the architecture as designed if you do not declare the Result type.
Since we do not need a "constrained" progress bar we will pass the "actual type arguments" as <String,Void,String> in the class definition.
When the AsynchTask executes it will call doInBackground. Since we declared Params as type String, doInBackground takes one or more Strings in a parameter list. Since we declared Result as String, doInBackground returns an array of Strings. When the AsyncTask is done, it will call onPostExecute returning the Result from doInBackground. So here is our AsyncTask class.
private class MyAsynch extends AsyncTask<String, Void, String>{
@Override
protected String doInBackground(String...strings) { // run time intensive task in separate thread
// TODO Auto-generated method stub
String key= strings[0];
String inString= strings[1];
return encrypt(key, inString); // pass String result to onPostExecute
}
protected void onPostExecute(String result){
editTextConfusedText.setText(result); // time intensive task is done --> so update UI or launch dialog
}
};
You should place your "time intensive task" in doInBackground. Do not touch the UI here as code in doInBackground is running in a separate thread from the UI thread. When the time intensive task completes, the return value from doInBackground is passed to onPostExecute.
We launch our task in the Confuse Text button handler. Since we declared the Params as String, we may pass an unconstrained list of strings to doInBackground. In this case we pass two strings as Params, password and inString.
// CONFUSE TEXT HANDLER
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String inString= editTextPlainText.getText().toString();
new MyAsynch().execute(password,inString); // <== takes Params as String
}
};
When the user clicks the confuse text button, doInBackground will be called in separate thread from the UI thread. So our encrypt method will be called in a separate thread. Here again is our "time intensive" task from the previous lesson.
// PRACTICE ONLY THREADED ENCRYPTION
private String encrypt(String key, String inString) {
String outString= "";
try {
try {
Thread.sleep(4000); // <= MIMIC TIME INTENSIVE TASK
Log.d(TAG,"ThreadSleep");
}
catch (InterruptedException e){
Log.d(TAG,"encryt",e);
}
outString= model.encrypt(inString, key);
}
catch(GeneralSecurityException e){
Log.d(TAG,"Thread",e);
}
catch(Exception e){ // model could be null
Log.d(TAG,"Thread",e);
}
return outString;
}
Thats all folks. The "time intensive" encrypt task runs in the background. Upon completion it returns the encrypted text of type String in onPostExecute where the GUI is updated.
Returning Stateful Objects from the Background Thread
The design of AsycnTask may not tolerate the throwing of exceptions in the background thread. One approach is to use a "No Throw" policy for code running in the background thread and then wrapping any thrown exceptions as an error in a custom result class. Here again is our custom String return class BoolString:
//a utility class to signal success or failure, return an error message, and return a useful String value
//see Try Out in C#
public final class BoolString {
public final boolean success;
public final String err;
public final String value;
public BoolString(boolean success, String err, String value) {
this.success= success;
this.err= err;
this.value= value;
}
}
Here is skeleton code that uses the no throw in background thread strategy, return a BoolString on Exception or on Success:
private class EncryptAsynch extends AsyncTask<String, Void, BoolString>{
protected void onPreExecute() {
progress.show();
}
@Override
protected BoolString doInBackground(String...strings) { // <== DO NOT TOUCH THE UI VIEW HERE
// TODO Auto-generated method stub
...
return model.tryEncrypt(plainText,password); // <== return value BoolString result is sent to onPostExecute
}
protected void onPostExecute(BoolString result){
progress.dismiss();
if (result.success){
...
editTextCipherText.setText(result.value);
}
else {
editTextPassword.setError(result.err);
editTextPassword.requestFocus();
}
}
};
Reporting Progress as Percentage
If your long running process can be broken into chunks or bites, then you can report on the progress of the process. So if the long running process could be broken down into 10 equal chunks, then you could iterate over the ten chunks in doInBackground, calling publishProgress(Integer %10*iter) after each iteration. Then you could update your "constrained" progress bar in the UI in onProgressUpdate.
Using the ProgressDialog
In the previous lesson on threading, we wrote our own unconstrained timer. In this lesson, we will use the ProgressDialog class to display an unconstrained spinning ball. We begin by telling the compiler where to find the code for ProgressDialog as:
import android.app.ProgressDialog;
Then we declare an instance variable:
private ProgressDialog progress;
In onCreate, we create an instance of our ProgressDialog class and set some properties such as a message. We setIndeterminate as true since we do not know when the time intensive task will return:
progress= new ProgressDialog(this);
progress.setIndeterminate(true);
progress.setMessage("I am thinking");
Thats it! We show the ProgressDialog in onPreExecute and we dismiss it in onPostExecute. onPreExecute really is just a helper method to formalize the pattern: onPreExecute/ doInBackground/ onPostExecute.
private class MyAsynch extends AsyncTask<String, Void, String>{
protected void onPreExecute() {
progress.show();
}
@Override
protected String doInBackground(String...strings) { // <== DO NOT TOUCH THE UI VIEW HERE
// TODO Auto-generated method stub
String key= strings[0];
String inString= strings[1];
return encrypt(key, inString); // <== return value String result is sent to onPostExecute
}
protected void onPostExecute(String result){
progress.dismiss();
editTextConfusedText.setText(result); // you could launch results dialog here
}
};
Here is our indeterminate or unconstrained ProgressDialog in action. Displaying this dialog effectively blocks the UI from handling any further user interaction until our time intensive task returns. Interestingly, ProgressDialog does not block the UI thread itself, so the UI can be updated while the ProgressDialog is spinning in the foreground.
Note: The user can dismiss the ProgressDialog by hitting the back button, giving them access to the UI!
Pretty neat. In this sample, we just used the information, returned in onPostExecute, to update the UI (an edit text box) in onPostExecute. There is no reason you could not launch a new Activity or Dialog Box to display the results of the time intensive task in onPostExecute. So you can take the information returned from the background thread in onPostExecute to either update the UI or launch a new screen to display the information.
cancel() in onPause
Unlike our thread/handler sample where we setDaemon to true, AsyncTask does not seem to have a setDaemon option. So, again it may be wise to call cancel() in onPause. At least one author implies that AsyncTask runs doInBackground in a daemon thread. According to the docs, AsyncTask can currently launch multiple threads which is an advantage and a source of errors. Also, if the user rotates the phone while the ProgressDialog is showing, an exception can be thrown.
So it may be wise to call myProgressDialog.cancel() in onPause(). So here is our onPause() method where we cancel any running threads and ProgessDialogs.
protected void onPause() {
super.onPause();
if (asynch != null) {asynch.cancel(true);}
if (progress != null){progress.cancel();}
}
Reset Progress in onCreate
If your activity may call progress.show() more than once, you may need to reset the ProgressDialog in onCreate as in:
private void resetProgress() { // avoid frozen progress dialog on soft kill
if (progress != null && progress.isShowing()){
progress.cancel();
}
progress= new ProgressDialog(this);
progress.setIndeterminate(true);
progress.setMessage("I am thinking.");
}
Remember, we canceled the progress in onPause.
Persisting Threads on a Soft Kill
CommonsWare has posted code that suggest that it is possible to continue threaded code on orientation change. Here is the code:
@Override
public Object onRetainNonConfigurationInstance() {
task.detach();
return(task);
}
and
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bar=(ProgressBar)findViewById(R.id.progress);
task=(RotationAwareTask)getLastNonConfigurationInstance();
if (task==null) {
task=new RotationAwareTask(this);
task.execute();
}
else {
task.attach(this);
updateProgress(task.getProgress());
if (task.getProgress()>=100) {
markAsDone();
}
}
}
where task is an instance of a static inner class:
static class RotationAwareTask extends AsyncTask<Void, Void, Void> {
Mark M. emphasizes "that you want doInBackground() of your AsyncTask to be completely decoupled from the Activity." I see no reason why this would not work for all types of soft kills, but on a hard kill, well, you get killed. Dead is dead :)
Reporting on Progress
In this sample, we used an unconstrained indeterminate progress dialog. Again, if your time intensive task can be broken up into sub tasks, then you can notify the progress dialog in onProgressUpdate by calling publishProgress. It is not wise to try to touch the progress dialog from doInBackground. Bad karma!
Threads/Handler or AsyncTask
Well we examined two approaches to time intensive tasks and reporting progress. The AsyncTask class hides some of the complexity of threads and handler, but it introduces the complexity of understanding generics. I think it is a matter of taste. I would liken the AsyncTask to an automatic transmission on a car; it simplifies the task, but you lose some flexibility. I would liken the use of threads and handler to a manual transmission on a car; it takes more time to learn how to use it, but it is more flexible.
Service
Well, just when you were getting comfortable, we introduce another solution, the Service. Here is the description of a Service from the Android docs:
A Service is an application component that can perform long-running operations in the background and does not provide a user interface. Another application component can start a service and it will continue to run in the background even if the user switches to another application. Additionally, a component can bind to a service to interact with it and even perform interprocess communication (IPC). For example, a service might handle network transactions, play music, perform file I/O, or interact with a content provider, all from the background.
Possible GOTHCA, onPostExecute
If you declare the result type as Void and declare onPostExecute as:
protected void onPostExecute()
or as:
protected void onPostExecute(Void... result)
The code will compile, but onPostExecute will not be called. The proper signature for a Void result is:
protected void onPostExecute(Void result)
JAL