ANDROID THREADS, HANDLERS AND ASYNCTASK - TUTORIAL
ANDROID THREADS, HANDLERS AND ASYNCTASK - TUTORIAL
Version 1.9
Copyright © 2011, 2012 Lars Vogel
03.03.2012
Revision History | ||
---|---|---|
Revision 0.1 | 21.10.2010 | Lars Vogel |
Created | ||
Revision 0.2 - 1.9 | 21.12.2010 - 03.03.2012 | Lars Vogel |
bug fixes and updates |
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 the Android
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
your Activity
.
You thread can post
messages via the sendMessage(Message
method or via the
msg) sendEmptyMessage()
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.
returns
getLastNonConfigurationInstance()
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 "de.vogella.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 de.vogella.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 de.vogella.android.asynctaskwith
an Activity
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 de.vogella.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.vogella.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 "de.vogella.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="de.vogella.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 de.vogella.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.vogella.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 "de.vogella.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 de.vogella.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();
}
}
}
Traceview is a graphical
viewer to see logs created by an Android application. Via Traceview you can
find errors in your application and measure its performance.
Eclipse supports
tracing directly in the DDMS perspective. In the devices view select
your application process and select "Start Method Profiling".
Use your application
and re-press the same button to stop profiling. This will open a new
editor which shows you the result of traceview.
You can zoom into the
graphic to get more details.
To start tracing some
code put the following code snippet around it.
android.os.Debug.startMethodTracing("yourstring");
// ... your code is here
android.os.Debug.stopMethodTracing();
The parameter
"yourstring" tells the system that it should store the data under
"/sdcard/yourstring.trace". To save data on the sdcard your application
needs the WRITE_EXTERNAL_STORAGE permission. After running your
application you can use Traceview via adb .
adb pull /sdcard/yourstring.trace
traceview yourstring
This will start
Traceview which allow you to analyse your performance data via a
graphical way. The DDMS view has also a trace button available. This
will trace the running application and does not require an additional
authorization.
Before posting questions,
please see the vogella
FAQ. If you have questions or find an error in this article please use
the www.vogella.com
Google Group. I have created a short list how
to create good questions which
might also help you.
Introduction to Android Development
Android Location API and Google Maps
Android concurrency tutorial from Michael Galpin
vogella Training Android
and Eclipse Training from the vogella team
Android Tutorial Introduction
to Android Programming
GWT Tutorial Program in
Java and compile to JavaScript and HTML
Eclipse RCP Tutorial Create
native applications in Java
JUnit Tutorial Test your
application
Git Tutorial Put
everything you have under distributed version control system
آخر اخبارنا
اهم الفاعليات لهذا الشهر
شهادات العملاء
من بين عدة انظمة للمحاسبة وقع اختيارنا على آفاق للمحاسبة لانه مناسب لطبيعة نشاطنا وانتشار فروعنا في مناطق مختلفة
آفاق للحسابات العامة نشكركم على هذا المنتج الرائع والي مزيد من التقدم والرقي لهذا المنتج الرائع
نقاط البيع واصدار الفواتير عبر نظام آفاق بالفعل هو الافضل مع تجربتنا لاكثر من نظام محاسبي كان آفاق الافضل من بينها
الشبكات الإجتماعية
الوسوم
تغريدات تويتر
-
web3jba @web3jba 4h
"برنامج محاسبة: برنامج محاسبة الى كل الشركات التى تقوم بمشاريع ذات تكاليف كثيرة لديك الان… https://t.co/Y9aiYAxLTN"
عرض التغريدة -
alkhateeb_groub @alkhateeb_groub 4h
"شاركنا باسم افضل برنامج محاسبة تعمل عليه ؟؟ مجموعة الخطيب للمحاسبة والتدقيق والتحكيم المالي والاستشارات الضريبية"
عرض التغريدة -
edara_arabia @edara_arabia 4h
"مجانا : برنامج #محاسبة متكامل هذا البرنامج يمكنك من تسجيل الدخل والمصروفات وعمل #ميزانية الشهرية والسنوية... https://t.co/hiWH94zgk3"
عرض التغريدة -
egydrem @egydrem 4h
"#إيجي_موب : برنامج حسابات رائع قد تدفع الكثير من المال فى برنامج معقد لن يفيدك فى شئ وقد تدفع القليل فى برنامج... http://t.co/mcFDTffZ7R"
عرض التغريدة