您的位置:首页 > 移动开发 > Android开发

Android Threads, Handlers and AsyncTask - Tutorial

2012-04-19 16:47 531 查看


1. Android Threads and Background Processing

Android modifies the user interface via one thread, the user interface 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 an file 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.


2. Android Basics

The following assumes that you have already basic knowledge in Android
development. Please check theAndroid development tutorial to
learn the basics.


3. Background Processing

3.1. Threads

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.

Only the user interface face is allow to update the user interface. If you need to update the user interface from another
Thread
, you need to synchronize with this user interface
Threads
.
You can use the class "android.os.Handler" for this or the class "AsyncTasks".

3.2. Handler

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 msg)
method or via the
sendEmptyMessage()
method.

3.3. AsyncTask

The class AsyncTask encapsulates the creation of Threads and Handlers. You must implement the method "doInBackground()", which defines what action should be done in the background. This method is be automatically run in a separate Thread. To update the UI
you can override the method "onPostExecute()". This method will be called by the framework once your action is done and runs within the UI thread. AsyncTask

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(), ProgressValueis used for progress
information and ResultValue must be returned from doInBackground() and is passed to onPostExecute() as parameter.

3.4. Feedback via ProgressBar

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.

3.5. Concurrency and lifecyle

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.


4. Tutorial: Handler

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="fill_parent"
android:layout_height="fill_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.


5. Tutorial: AsyncTask

In this example we will use AsyncTask to download the content of a webpage. We use Android
HttpClientfor this. Create a new Android project "de.vogella.android.asynctask" with the activity "ReadWebpageAsyncTask". Add the permission "android.permission.INTERNET". 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.


6. Tutorial: Activity lifecycle and Thread

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

<?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="fill_parent"
android:layout_height="fill_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 "CNTR+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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: