In this brief tutorial I will discuss how to retrieve a JSON object using HTTP GET that will work on Android 2, 3, 4, and follows Android’s threading best practices.
First, all operations involving internet access should be performed on a background thread. But why perform even simple operations on background threads? Well, on Android the Main and UI Threads are the same, so blocking the main thread while downloading data means that the Activity you just loaded might not be displaying or animating anything. (Comments from Bob Lee and Dianne Hackborn regarding Android UI Thread Performance)
Preventing animation or rendering is a big User Experience problem.
Unfortunately the proper handling, debugging, and even understanding of Threads is a headache for many programmers – especially when compounded by thread-unsafe UI operations. Thus, Android recommends using the AsyncTask helper class for quick threading operations (lasting no more than a few seconds). If you need to keep threads running for long periods of time (for downloading large files or datasets), it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask. If you haven’t already, familiarize yourself with the Android Thread & Processes guide.
First, extend the AsyncTask.
public class HttpTask extends AsyncTask
An Asynchronous Task is defined by three generic types: . Here, since I’m implementing a simple HTTP RESTful operation, I’ve chosen to use a generic HttpUriRequest as the input parameter instead of a specific HttpGet. Since the task I’m executing is short (only one HttpUriRequest), the progress type is left as Void. The result is a JSONObject instead of an HttpResponse, as the lengthy operation of reading the InputStream and converting to JSON should also be done in the background method.
Next, create the Worker Thread.
@Override protected String doInBackground(HttpUriRequest...params) { // Performed on Background Thread HttpUriRequest request = params[0]; HttpClient client = new DefaultHttpClient(); try { // The UI Thread shouldn't be blocked long enough to do the reading in of the stream. HttpResponse response = client.execute(request); // TODO handle bad response codes (such as 404, etc) ...
The doInBackground() method is the worker thread operation of the AsyncTask. Here, it receives a list of HttpUriRequests – since this is a simple example, I’m assuming only one operation per task – hence:
param[0]
I’ve also chosen to use the DefaultHttpClient() for ease of use. You can use HttpsURLConnection if you need to load your own Keystore for self-signed HTTPS certifications.
For release software, you’ll also want to handle bad http response codes (which may not result in exceptions) such as 401, 404, etc.
... BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); StringBuilder builder = new StringBuilder(); for (String line = null; (line = reader.readLine()) != null ; ) { builder.append(line).append("\n"); } JSONTokener tokener = new JSONTokener(builder.toString()); JSONObject json = new JSONObject(tokener); return json; ...
Here we run the context of the HttpResponse through a String Builder to create the plain text string of the response and then tokenize the string into a JSON object. Because these operations can be lengthy ( > 16ms), they should be performed in the background worker thread (not on the UI Thread).
Next, in order to encapsulate the HTTP/Thread/JSON logic from the business logic of our activity, we create a Nested Interface which will handle the UI Thread operation.
public static interface HttpTaskHandler { void taskSuccessful(JSONObject json); void taskFailed(); } HttpTaskHandler taskHandler; public void setTaskHandler(HttpTaskHandler taskHandler) { this.taskHandler = taskHandler; }
Finally, call the taskHandler’s methods onPostExecute (UI Thread).
@Override protected void onPostExecute(JSONObject json) { // Done on UI Thread if(json != null) { taskHandler.taskSuccessful(json); } else { taskHandler.taskFailed(); } }
Calling the HttpTask is done like this.
HttpTask task = new HttpTask(); task.setTaskHandler(new HttpTaskHandler() { public void taskSuccessful(JSONObject json) { // Just put the JSONObjects into an array list so we can use a ListAdapter activity.data = new ArrayList(); // Ingest Data JSONArray nodes = json.getJSONArray("nodes"); Log.d(TAG, "Total Nodes: "+nodes.length()); for (int i = 0; i < nodes.length(); i++ ) { activity.data.add(nodes.getJSONObject(i); ); } Toast.makeText(getBaseContext(), "Refreshed ("+data.size()+")", Toast.LENGTH_SHORT).show(); // TODO update the list } public void taskFailed() { // handler failure (e.g network not available etc.) Log.e(TAG,"Task Failed"); Toast.makeText(getBaseContext(), "Failed", Toast.LENGTH_SHORT).show(); } }); task.execute(new HttpGet(“http://www.mywebsite.com/api/articles/”));
Here we initialize the HttpTask, set the handler to cover the business logic of the return data, and finally execute the task with an HttpGet.
Gotchas
These are a some of the exceptions or hidden errors that may be thrown which can be confusing to you, especially if you’re coming from iOS.
- Permission
- HTTPS
- Strict Mode
- Serial or Parallel?
Don’t forget to put this in your Android Manifest file.
<uses-permission android:name="android.permission.INTERNET"/>
I know, obvious for Android programmers, but on iOS there’s no need to ask for permission.
When trying to GET data from HTTPS you may find that the code works great on Android 3 & 4, but fails on Android 2 with some kind of “Certificate” error like:
- No Peer Certificate
- Not trusted server certificate
In response to these errors, you should never (ever!) trust all certificates. It is likely that one of the following is true:
- Your server uses Server Name Indication (which Android 2 does not support)
- Your certificate chain on the server is out-of-order. Most browsers handle out-of-order certificate chains but Android 2 does not.
- You are using a CA not recognized by Android 2 or self-signed certificate.
If you are using a CA not recognized by Android 2 or self-signed certificate, you’ll need to roll your own HttpClient, SSLSocketFactory, and keystore which is detailed here. Keep in mind that if you’re rolling your own HttpClient to use the
getApplicationContext()
which will keep your cookies managed properly (not the current activity’s context). You’ll also need to pass the MyHttpClient in as a parameter to the HttpTask as it has no Conext information.
Otherwise, you’ll need to reconfigure your server to be compatible with Android 2.
On Android, threads can have custom permissions which, if they violate, can result in an exception (this is known as Strict Mode). Something you may run into is the following error:
- android.os.NetworkOnMainThreadException
- android.os.StrictMode$AndroidBlockGuardPolicy
This means that you’re taking too long in the UI Thread to perform an action. This is why we make sure to read the String from the InputStream and create the JSONObject in the background worker thread before returning to the UI Thread.
In addition, the AsyncTask operates differently depending on the OS. Initially (API 3), AsyncTasks were executed serially on a single background thread. If you’re running API 4 – 10 this was changed to a pool of threads allowing multiple tasks to operate in parallel. This caused a lot of common application errors (developers not being careful enough with parallel execution) so starting with API 11 the tasks are executed on a single thread again.
If you truly need parallel execution, you can invoke executeOnExecutor( java.util.concurrent.Executor, Object[] ) with THREAD_POOL_EXECUTOR on API 11 or later.
An iOS Comparison
If you’re familiar with AFNetworking on iOS then the above should look relatively familiar to you.
- HttpTask is similar to AFHTTPRequestOperation.
- HttpTaskHandler is similar to the CompletionBlock of the Operation.
- HttpClient is similar to AFHTTPClient.
Major differences is that the HttpTaskHandler is called on the Main/UI Thread whereas the Completion Block is not. Also, the AFHTTPClient can run parallel operations in its queue, whereas HttpTask is serial or parallel depending on the API version (see above). If you need parallel operation, you’ll want to look at Android’s ThreadPoolExecutor (available from API level 1).
Sample Code
The complete HttpTask Class:
public class HttpTask extends AsyncTask { private static final String TAG = "HTTP_TASK"; @Override protected String doInBackground(HttpUriRequest...params) { // Performed on Background Thread HttpUriRequest request = params[0]; HttpClient client = new DefaultHttpClient(); try { // The UI Thread shouldn't be blocked long enough to do the reading in of the stream. HttpResponse response = client.execute(request); // TODO handle bad response codes (such as 404, etc) BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); StringBuilder builder = new StringBuilder(); for (String line = null; (line = reader.readLine()) != null; ) { builder.append(line).append("\n"); } JSONTokener tokener = new JSONTokener(builder.toString()); JSONObject json = new JSONObject(tokener); return json; } catch (Exception e) { // TODO handle different exception cases Log.e(TAG,e.toString()); e.printStackTrace(); return null; } } @Override protected void onPostExecute(JSONObject json) { // Done on UI Thread if(result != null) { taskHandler.taskSuccessful(json); } else { taskHandler.taskFailed(); } } public static interface HttpTaskHandler { void taskSuccessful(JSONObject json); void taskFailed(); } HttpTaskHandler taskHandler; public void setTaskHandler(HttpTaskHandler taskHandler) { this.taskHandler = taskHandler; } }
4 Responses
Cool sample, like that best practice.
Thank you.
Where and how is activity defined in the line “activity.data = new ArrayList();”?
Nice explanation & example.
Thanks – glad we could help.