In this lecture, we discuss threads and in particular the use if AsyncTask.
Your UI runs on a single thread to interact with the user -- this is the main thread. Therefore all code will run in this thread which might result in poor performance if you have a computationally intensive operation that could be run in another thread; for example, if your code is loading a file over the Internet you UI is completely blocked. This is not smart design.
The solution to this is simple: If you have computationally demanding functions or slow running operations the best solution if to run those tasks asynchronously from the UI threads.
Note, we will want to off load processing for inserting and querying the database for MyRuns3. We'll use an AsyncTask to insert and a loader (i.e., specifically AsyncTaskLoader) get database entries. For more on Loaders read the course book or:
We have discussed a number of AsyncTask examples in class:
AsyncTaskCountDownDemo.zip app to demonstrate how to create an AsyncTask. As discussed below this simple example counts down in the background thread and posts the current count to the UI thread. We disuss this in the notes below.
AsyncTaskListViewOfProfs.zip as shown in class updates the UI threads ListView by adding to the adapter in the onPostExecute() method. It emulates, what looks like, a terribly janky UI. onProgressUpdate() updates the UI.
DownloadImageFromWeb.zip downloads a large image from a webserver in the doInBackground() thread then when the image is completely downloaded onPostExecute() set of the image on the UI (a scaled version) using imageView.setImageBitmap(bitmap).
DatabaseWithLoader.zip uses an AsyncTaskLoader to offload getAllComments() fom the UI thread to a worker thread. We update the DatabaseDemo code we looked at last week to do this.
The notes below relate specifically to the AsyncTaskCountDownDemo example, but more generally to all the code examples listed above.
Some excellent references.
In this lecture, we discuss AsyncTask: the AsyncTask class encapsulates the creation of Threads and Handlers. An AsyncTask is started via the execute() method. AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.)
An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread. An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute. The method calls the doInBackground() and the onPostExecute() method. The doInBackground() method contains the coding instruction which should be performed in a background thread. This method runs automatically in a separate Thread. The onPostExecute() method synchronize itself again with the user interface thread and allows to update it. This method is called by the framework once the doInBackground() method finishes.
To use AsyncTask you must subclass it. AsyncTask uses generics and varargs. The parameters are the following AsyncTask
The demo app include in the lecture includes an activity that starts a AsyncTask to first count down from 15, as shown below. Different text is rewritten out to the UI in different thread components of the UI and background thread, as shown below.
A service needs to be defined in the manifest as shown below.
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Starts the CountDownTask
new CountDownTask().execute();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
In the code below we first see how the AsyncTask operates.
AsyncTask's generic types The three types used by an asynchronous task are the following:
Not all types are always used by an asynchronous task. To mark a type as unused, simply use the type Void:
private class MyTask extends AsyncTask<Void, Void, Void> { ... }
When an asynchronous task is executed, the task goes through 4 steps:
We use AsyncTasks in MyRuns:
For MyRun5, you can use an AsyncTask to run the activity classifier at the background because it is computationally intensive and you do not want to block the UI thread.
For MyRuns6, you can use another AsyncTasks to upload history entries to the cloud. Again, if you did this in the main UI thread the user experience would be poor.
Now let's look at the code for the AsyncTask demo app example.
In the first snippet of code text for starting the count down is displayed using on preExecute(), which is invoked on the UI thread before the task is executed. This step is normally used to setup the task, by showing START in the user interface.
private class CountDownTask extends AsyncTask<Void, Integer, Void>{
// A callback method executed on UI thread on starting the task
@Override
protected void onPreExecute() {
// Getting reference to the TextView tv_counter of the layout activity_main
TextView tvCounter = (TextView) findViewById(R.id.tv_counter);
tvCounter.setText("*START*");
}
This is the main worker thread of the task. doInBackground() is invoked on the background thread (which is different from the UI thread) immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. Typically the result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step.
In the case of the code example, we do not return anything but we publish the count value to the UI thread using publishProgress(i) which invokes onProgressUpdate() as shown below in the snippet of code.
// A callback method executed on non UI thread, invoked after
// onPreExecute method if exists
// Takes a set of parameters of the type defined in your class implementation. This method will be
// executed on the background thread, so it must not attempt to interact with UI objects.
@Override
protected Void doInBackground (Void... params) {
for(int i=15;i>=0;i--){
try {
Thread.sleep(1000);
publishProgress(i); // Invokes onProgressUpdate()
} catch (InterruptedException e) {
}
}
return null;
}
As mentioned above onProgressUpdate(Progress...) is invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. In our case publishProgress(Progress...) displays the current count to the UI layout in large font.
// A callback method executed on UI thread, invoked by the publishProgress()
// from doInBackground() method
// Overrider this handler to post interim updates to the UI thread. This handler receives the set of parameters
// passed in publishProgress from within doInbackground.
@Override
protected void onProgressUpdate (Integer... values) {
// Getting reference to the TextView tv_counter of the layout activity_main
TextView tvCounter = (TextView) findViewById(R.id.tv_counter);
// Updating the TextView
tvCounter.setText( Integer.toString(values[0].intValue()));
}
The final method of the AsyncTask is the onPostExecute(Result), which is invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter. In our coded example there is no result passed back from the doInBackground() method. Here we simple display DONE to the UI layout.
// A callback method executed on UI thread, invoked after the completion of the task
// When doInbackground has completed, the return value from that method is passed into this event
// handler.
@Override
protected void onPostExecute(Void result) {
// Getting reference to the TextView tv_counter of the layout activity_main
TextView tvCounter = (TextView) findViewById(R.id.tv_counter);
tvCounter.setText("*DONE*");
}
}
}