Android Threads and Handlers equals to AsyncTask
Android Threads,
Handlers AsyncTask
This tutorial describes
the usage of Threads, Handlers and AsyncTask in your application. It also
covers how to handle the application lifecycle together with threads. It also
describes Traceview to trace an application for performance problems. It is
based on Eclipse 3.7, Java 1.6 and Android 4.0 (Ice Cream Sandwitch)
Table of Contents
Android modifies the
user interface and handles input events from one single user interface
thread which is also called the main thread.
If the programmer does
not use any concurrency constructs, all code of an Android application runs in
this thread.
If you perform a long
lasting operation, e.g. loading a file or accessing data from the Internet, the
user interface of your Android Application will block until the corresonding
code has finished.
To provide a good user
experience all potentially slow running operations in an Android application
should run asynchronously, e.g. via some way of concurrency constructs of the
Java language or the Android framework. This includes all potential slow
operations, like network, file and database access and complex calculations.
Android enforced that
with an Application not responding (ANR) dialog if an Activity does not react
within 5 seconds to user input. From this dialog the user can choose to stop
the application.
The following assumes
that you have already basic knowledge in Android development. Please check theAndroid development tutorial to
learn the basics.
Android supports the
usage of the Threads class to perform asynchronous processing.
Android also supplies
the java.util.concurrent package to perform something in the
background, e.g. using the ThreadPools and Executor classes.
If you need to update
the user interface from a new Thread, you need to synchronize with the user
interface thread.
You can use the android.os.Handler class
or the AsyncTasks class for this.
The Handler class can update
the user interface. A Handler provides methods for receiving instances
of the Message or Runnable class.
To use a handler you
have to subclass it and override the handleMessage() to process messages. To process a Runnable you can use the post() method. You only
need one instance of a Handler in yourActivity.
You thread can post
messages via the sendMessage(Message msg) method or via thesendEmptyMessage() method.
The AsyncTask class encapsulates
the creation of Threads and Handlers. An AsyncTask is started via the execute() method.
The execute() 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 <TypeOfVarArgParams , ProgressValue , ResultValue> .
TypeOfVarArgParams is
passed into the doInBackground() method as input, ProgressValue is used for
progress information and ResultValue must be returned from doInBackground() method
and is passed to onPostExecute() as parameter.
For providing feedback
to the user you can use the ProgressBar dialog, which allow to display progress to
the user. The Javadoc of ProgressBar gives a nice example of its usage.
Alternatively you can
provide progress feedback in the activities title bar.
One challenge in using
threads is to consider the lifecycle of the application. The Android system may
kill your activity or trigger a configuration change which also will restart
your activity.
You also need to handle
open dialogs, as dialogs are always connected to the activity which created
them. In case the activity gets restarted and you access an existing dialog you
receive an "View not attached to window manager" exception.
To save an object your
can use the method onRetainNonConfigurationInstance() method. This method allows to save one
object if the activity will be soon restarted.
To retrieve this object
you can use the getLastNonConfigurationInstance() method. This way can you can save an
object, e.g. a running thread, even if the activity is restarted.
getLastNonConfigurationInstance() returns null if the Activity is started the
first time or if it has been finished via the finish() method.
If more then one object
should be saved then you can implement the class "Application". This
class can be used to access object which should be cross activities or
available for the whole application lifecycle. In the onCreate() and
onTerminate() you can create / destroy the object and make them available via
public fields or getters. To use your application class assign the classname to
the android:name attribute of your application.
<application android:icon="@drawable/icon"
android:label="@string/app_name"
android:name="MyApplicationClass">
<activity
android:name=".ThreadsLifecycleActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
You can acess the
Application via the getApplication() method in your activity.
In this example we use
the Handler class to update a ProgressBar view in a
background Thread.
Create a new Android
project called "tyagi.robin.android.handler" with the Activity "ProgressTestActivity".
Create the following layout main.xml. This layout contains the ProgressBar and sets its appearance via
a style.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ProgressBar
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:max="10"
android:padding="4dip" >
</ProgressBar>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startProgress"
android:text="Start
Progress" >
</Button>
</LinearLayout>
Change your Activity to the following:
package tyagi.robin.android.handler;
import
android.app.Activity;
import
android.os.Bundle;
import
android.os.Handler;
import
android.view.View;
import
android.widget.ProgressBar;
public class
ProgressTestActivity extends Activity {
private Handler handler;
private ProgressBar progress;
/** Called when the activity
is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
progress =
(ProgressBar) findViewById(R.id.progressBar1);
handler = new Handler();
}
public void startProgress(View view) {
// Do something long
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i
<= 10; i++) {
final int value = i;
try {
Thread.sleep(2000);
} catch
(InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
progress.setProgress(value);
}
});
}
}
};
new
Thread(runnable).start();
}
}
Run your application.
Once you press your button the ProgressBar will get updated from the background
thread.
In this example we will
use an instance of the AsyncTask class to download the content of a
webpage. We use Android HttpClient for
this. Create a new Android project called tyagi.robin.android.asynctask with
anActivity called ReadWebpageAsyncTask. Add the android.permission.INTERNET permission to your <filenname>AndroidManifest.xml</filenname> file. .
Create the following
layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/readWebpage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="readWebpage"
android:text="Load
Webpage" >
</Button>
<TextView
android:id="@+id/TextView01"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Example
Text" >
</TextView>
</LinearLayout>
Change your activity to
the following:
package tyagi.robin.android.asynctask;
import
java.io.BufferedReader;
import
java.io.InputStream;
import java.io.InputStreamReader;
import
org.apache.http.HttpResponse;
import
org.apache.http.client.methods.HttpGet;
import
org.apache.http.impl.client.DefaultHttpClient;
import
android.app.Activity;
import
android.os.AsyncTask;
import
android.os.Bundle;
import
android.view.View;
import
android.widget.TextView;
public class
ReadWebpageAsyncTask extends Activity {
private TextView textView;
/** Called when the activity
is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView = (TextView) findViewById(R.id.TextView01);
}
private class
DownloadWebPageTask extends AsyncTask<String, Void,
String> {
@Override
protected String
doInBackground(String... urls) {
String response = "";
for (String url
: urls) {
DefaultHttpClient client = new
DefaultHttpClient();
HttpGet httpGet = new
HttpGet(url);
try {
HttpResponse execute =
client.execute(httpGet);
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new
BufferedReader(new InputStreamReader(content));
String s = "";
while ((s =
buffer.readLine()) != null) {
response += s;
}
} catch (Exception
e) {
e.printStackTrace();
}
}
return response;
}
@Override
protected void
onPostExecute(String result) {
textView.setText(result);
}
}
public void readWebpage(View view) {
DownloadWebPageTask task = new
DownloadWebPageTask();
task.execute(new String[] { "http://www.robin.com" });
}
}
If you run your
application and press your button then the content of the defined webpage
should be read in the background. Once this process is done your TextView will be updated.
The following example
will download an image from the Internet in a thread and displays a dialog
until the download is done. We will make sure that the thread is preserved even
if the activity is restarted and that the dialog is correctly displayed and
closed.
For this example create
the Android project "tyagi.robin.android.threadslifecycle" and the
Activity "ThreadsLifecycleActivity". Also add the permission to use
the Internet to your app. Details for this can found here: Networking with Android.
You should have the
following AndroidManifest.xml file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="tyagi.robin.android.threadslifecycle"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<uses-permission android:name="android.permission.INTERNET" >
</uses-permission>
<application
android:icon="@drawable/icon"
android:label="@string/app_name" >
<activity
android:name=".ThreadsLifecycleActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Change the layout main.xml to the following.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="downloadPicture"
android:text="Click to
start download" >
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="resetPicture"
android:text="Reset
Picture" >
</Button>
</LinearLayout>
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/icon" >
</ImageView>
</LinearLayout>
Now adjust your
activity. In this activity the thread is saved and the dialog is closed if the
activity is destroyed.
package tyagi.robin.android.threadslifecycle;
import java.io.IOException;
import
org.apache.http.HttpEntity;
import
org.apache.http.HttpResponse;
import
org.apache.http.StatusLine;
import
org.apache.http.client.HttpClient;
import
org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import
org.apache.http.impl.client.DefaultHttpClient;
import
org.apache.http.util.EntityUtils;
import
android.app.Activity;
import
android.app.ProgressDialog;
import
android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import
android.os.Bundle;
import
android.os.Handler;
import
android.view.View;
import
android.widget.ImageView;
public class
ThreadsLifecycleActivity extends Activity {
// Static so that the thread access the latest attribute
private static
ProgressDialog dialog;
private static ImageView
imageView;
private static Bitmap
downloadBitmap;
private static Handler
handler;
private Thread downloadThread;
/** Called when the activity
is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Create a handler to update
the UI
handler = new Handler();
// get the latest imageView
after restart of the application
imageView = (ImageView) findViewById(R.id.imageView1);
// Did we already download
the image?
if
(downloadBitmap != null) {
imageView.setImageBitmap(downloadBitmap);
}
// Check if the thread is
already running
downloadThread = (Thread) getLastNonConfigurationInstance();
if
(downloadThread != null && downloadThread.isAlive()) {
dialog = ProgressDialog.show(this, "Download", "downloading");
}
}
public void resetPicture(View view) {
if
(downloadBitmap != null) {
downloadBitmap = null;
}
imageView.setImageResource(R.drawable.icon);
}
public void downloadPicture(View view) {
dialog = ProgressDialog.show(this, "Download", "downloading");
downloadThread = new MyThread();
downloadThread.start();
}
// Save the thread
@Override
public Object onRetainNonConfigurationInstance() {
return
downloadThread;
}
// dismiss dialog if activity is destroyed
@Override
protected void onDestroy()
{
if (dialog !=
null && dialog.isShowing()) {
dialog.dismiss();
dialog = null;
}
super.onDestroy();
}
// Utiliy method to download image from the internet
static private Bitmap downloadBitmap(String
url) throws IOException {
HttpUriRequest request = new
HttpGet(url.toString());
HttpClient httpClient = new
DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
StatusLine statusLine = response.getStatusLine();
int statusCode =
statusLine.getStatusCode();
if (statusCode
== 200) {
HttpEntity entity = response.getEntity();
byte[] bytes =
EntityUtils.toByteArray(entity);
Bitmap bitmap =
BitmapFactory.decodeByteArray(bytes, 0,
bytes.length);
return bitmap;
} else {
throw new IOException("Download
failed, HTTP response code "
+ statusCode + " -
" + statusLine.getReasonPhrase());
}
}
static public class MyThread extends Thread {
@Override
public void run() {
try {
// Simulate a slow network
try {
new
Thread().sleep(5000);
} catch
(InterruptedException e) {
e.printStackTrace();
}
downloadBitmap = downloadBitmap("http://www.robin.com/img/lars/LarsVogelArticle7.png");
handler.post(new
MyRunnable());
} catch (IOException
e) {
e.printStackTrace();
} finally {
}
}
}
static public class MyRunnable implements Runnable {
public void run() {
imageView.setImageBitmap(downloadBitmap);
dialog.dismiss();
}
}
}
Run your application and
press the button to start a download. You can test the correct lifecycle
behavior in the emulator via pressing "Ctrl+F11" as this changes the
orientation.
It is important to note
that the Thread is a static inner class. It is important to use a static inner
class for your background process because otherwise the inner class will
contain a reference to the class in which is was created. As the thread is
passed to the new instance of your activity this would create a memory leak as the
old activity would still be referred to by the Thread.
StrictMode is available
as of API 9, therefore make sure to use Android 2.3.3. As discussed you should
avoid performing long running operations on the UI thread. This includes file
and network access. It is sometimes difficult to remember to make all the right
things in your application during development. That is were StrictMode comes
in. It allows to setup policies in your application to avoid doing incorrect
things. For example the following setup will crash your application if it
violates some of the Android policies. StrictMode should only be used during
development and not in your live application.
Create the project
called "tyagi.robin.android.strictmode" with the Activity called TestStrictMode.
The following will set strict rules for your application. As the activity
violates these settings you application will crash.
package tyagi.robin.android.strictmode;
import
java.io.BufferedWriter;
import
java.io.OutputStreamWriter;
import
android.app.Activity;
import
android.os.Bundle;
import
android.os.StrictMode;
public class
TestStrictMode extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
StrictMode.setThreadPolicy(new
StrictMode.ThreadPolicy.Builder()
.detectAll().penaltyLog().penaltyDeath().build());
StrictMode.setVmPolicy(new
StrictMode.VmPolicy.Builder().detectAll()
.penaltyLog().penaltyDeath().build());
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String eol =
System.getProperty("line.separator");
try {
BufferedWriter writer = new
BufferedWriter(new OutputStreamWriter(openFileOutput("myfile",
MODE_WORLD_WRITEABLE)));
writer.write("This is a test1." + eol);
writer.write("This is a test2." + eol);
writer.write("This is a test3." + eol);
writer.write("This is a test4." + eol);
writer.write("This is a test5." + eol);
writer.close();
} catch (Exception
e) {
e.printStackTrace();
}
}
}
Comments
Post a Comment