Responsiveness
One issue that all mobile developers must consider is the responsiveness of their applications. It is critical for the user experience that a mobile app's UI remain responsive even if the application itself is doing expensive, i.e. time-consuming, processing. One major place where this occurs is when the application accesses resources via the network particularly the Internet. When the application attempts to access a network resource, e.g. downloading a large image, we do not want the UI to appear "frozen" while the resource is being retrieved as it will not only create a poor user experience, but may even trigger an "Application Not Responding" dialog from the system. These dialogs are part of the Android OS to ensure that no application unnecessarily consumes system resources.
Network Access
To address the issue of maintaining UI responsiveness, any significant time consuming task should be relegated to a background task (or even a separate process/thread). Beginning with Android 3.0 (Honeycomb) but recommended for all versions, Android prevents any network access on the main UI thread and will throw a NetworkOnMainThreadException if one is attempted. The Android SDK makes it simple to handle this situation through delegating the network tasks to a worker thread via subclassing the AsyncTask class.
AsyncTask
The AsyncTask class works by utilizing three generic types called Params, Progress, and Result to implement four methods onPreExecute(), doInBackground(), onProgressUpdate(), and onPostExecute(). Of these four, only doInBackground() is required to be implemented.
A subclass of AsyncTask can be created as
public class MyTask extends AsyncTask<ParamsType, ProgressType, ResultType> {
// Reference to main UI thread
private MainActivity context;
public MyTask(MainActivity context) {
this.context = context;
}
@Override
protected ResultType doInBackground(Params...params) {
// Do main work here
return something of type ResultType;
}
@Override
protected void onProgressUpdate(ProgressType...progparams) {
// Update UI progress indicator
}
@Override
protected void onPostExecute(ResultType result) {
// Do something when task is completed
// such as update UI widget
context.doSomethingWithResult(result);
}
}
The three generic type parameters can be specified as needed (or even Void if they are not used. In the doInBackground() method, the params parameter is an array of values of type ParamsType (and similarly for the onProgressUpdate() method).
Note that often if we wish to do something on the main UI thread once the task completes, e.g. in onPostExecute(), we can store a reference to the calling activity through the constructor.
Then to start the background task, e.g. in the click handler when a button is pressed, simply create a new instance of the class and call the execute() method passing any needed values of type ParamsType. For example
ParamsType p1, p2, p3;
MyTask mt = new MyTask(this);
mt.execute(p1, p2, p3);
Note that the execute() call will return immediately thus not blocking the UI thread. However as in any multithreaded program, synchronization may be necessary if further behavior is dependent on the background task completing.
The result of the task can then be used in the onPostExecute() method to perform an operation in the UI thread (via the context reference).
Network Example
To illustrate using AsyncTask we will consider the network portion of the geocoding lab (Lab05b). Since the network access is part of the controller class, we can simply have the controller subclass AsyncTask as follows
public class MobileController extends AsyncTask<String, Void, Result> {
// Reference to UI activity
private MainActivity context;
public MobileController(MainActivity context) {
this.context = context;
}
@Override
protected Result doInBackground(String... params) {
return getGeoDistance(params[0],params[1],params[2],params[3]);
}
@Override
protected void onPostExecute(Result result) {
// Display distance when finished
context.displayDistance(result);
}
private Result getGeoDistance(String firstStreet, String firstZip, String secondStreet, String secondZip) {
// Make get requests for addresses
// PostalCode p1 = makeGeoGetRequest(firstStreet, firstZip);
Result r = null;
// Compute result if both requests returned valid info, null otherwise
// ...
return r;
}
private PostalCodes makeGeoGetRequest(String street, String zip)
{
try {
// Create HTTP client
HttpClient client = new DefaultHttpClient();
// Create list of request paramater/value pairs
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("postalcode", zip));
params.add(new BasicNameValuePair("placeName", street));
params.add(new BasicNameValuePair("country", "US"));
params.add(new BasicNameValuePair("username","ycpcs_cs496"));
// Construct URI
URI uri;
uri = URIUtils.createURI("http", "api.geonames.org", -1, "/postalCodeSearchJSON",
URLEncodedUtils.format(params, "UTF-8"), null);
// Make get request
HttpGet request = new HttpGet(uri);
HttpResponse response;
response = client.execute(request);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// Copy the response body to a string
HttpEntity entity = response.getEntity();
// Parse JSON
return JSON.getObjectMapper().readValue(entity.getContent(), PostalCodes.class);
}
}
catch (Exception e) {
// Handle any exceptions
e.printStackTrace();
}
// Return null if invalid response
return null;
}
}
Note that in this case the controller will require four parameters of type String and returns a value of type Result (since we are not using the progress method it has type Void). We then simply extract the four strings from the params array and pass them to a helper method.
The click handler then becomes
// Instantiate controller and compute distance
MobileController mc = new MobileController(this);
mc.execute(firstStreet.getText().toString(), firstZip.getText().toString(),
secondStreet.getText().toString(), secondZip.getText().toString());
Alternatively, the result can be obtained from the task object via the get() method as
Result r = mc.get();
however this will result in a blocking call thus potentially making the UI unresponsive if the web service is slow or unavailable.