異步任務AsyncTask
1、Android 的UI線程主要負責處理用戶的按鍵事件、用戶觸屏事件及品目繪圖事件等,不能處理耗時操作,否則UI界面會停止響應。(Android UI想成超過20秒就會出現ANR,但是讓用戶等6秒鐘就會非常反感,所以這裏就不要記時間了)
2、爲了避免失去響應,我通常是新開啓個線程去處理耗時任務,但是問題又來了,我們往往需要在執行完耗時任務時需要更新UI,例如在網上獲取個網頁,顯示在Android的UI中,這裏因爲是新的線程,就不能直接修改主線程裏的UI,所以我們一種方法是通過Handler.發送消息去及時更新,Handler實例向UI線程發送消息,完成界面的更新,這種方式對於整個過程的控制比較精細,但也是有缺點的,例如代碼相對臃腫,在多個任務同時執行時,不易對線程進行精確的控制,但是還有另一種較好的方法就是用AsyncTask異步任務,此方法較爲輕量級。
3、AsyncTask是一個抽象類,通常用於被繼承,繼承時需要制定三個範型參數。
public class AsyncTask<Params,Progress,Result>
1)Params:啓動任務執行的輸入參數。
2)Progress:後臺完成進度值的類型。
3)Result:後臺執行任務完成後返回結果的類型。
4、使用步驟
1)創建AsyncTask子類,併爲三個泛型參數指定類型。如果某個泛型參數不需要指定類型,可將它指定爲Void。
2)根據需要實現對應的方法。提供的操作方法如下。
①doInBackground(Params...):重寫該方法就是後臺線程將要完成的任務,這裏可以調用publishProgress(Progress...valuers)方法更新執行的進度。
②onProgressUpdate(Progress... values):在doInBackground()方法中調用publishProgress()方法更新任務的執行進度後,將會觸發此方法,直接將進度信息更新到UI組件上。
③onPreExecute():該方法將在執行後臺耗時操作前調用。因此他是完成一些初始化操作的,也就是準備工作,比如在界面上顯示進度條等等。
④onPostExecute(Result result):當doInBackground()完成後,系統會自動調用onPostExecute()方法,並將doInBackground()方法的返回值傳給該方法。此方法用來將最後結果顯示的UI中
⑤onCancelled()方法:當task調用cancel(True)時,被調用,是編寫取消任務對應UI的更改操作
3)使用AsyncTask時必須遵守如下原則。
① 必須在UI線程中創建AsyncTask的實例。
②必須在Ui線程中調用AsyncTask的execute方法。
④AsyncTask的onPreExecute().onPostExecute(Result result),doInBackground(Params...params),onProgressUpdate(Progress....values)方法不應該由程序員代碼調用,而是由Android系統負責調用。
⑤每個AsyncTask只能被執行一次,多次調用將會引發異常,就是一個任務實例只能執行一次,如果執行第二次將會拋出異常。
執行的順序,onPreExecute()---->>doInBackground()--->>內部調用publishProgress(Progress...values)--->>結束觸發onProgressUpdate()--->>onPostExecute()
如果中途取消,執行順序
onPreExecute()---->>doInBackground()--->>內部調用publishProgress(Progress...values)--->>結束觸發onProgressUpdate()--->onCancelled()(這個方法是程序員調用cancel時才觸發的,和其他不一樣)
下面Demo,
新建一個AsyncTaskTest 類,
package org.crazyit.handler;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.view.View;
import android.widget.TextView;
public class AsyncTaskTest extends Activity
{
private TextView show;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
show = (TextView) findViewById(R.id.show);
}
// 重寫該方法,爲界面的按鈕提供事件響應方法
public void download(View source) throws MalformedURLException
{
DownTask task = new DownTask(this);
task.execute(new URL("http://www.crazyit.org/ethos.php"));
}
class DownTask extends AsyncTask<URL, Integer, String>
{
// 可變長的輸入參數,與AsyncTask.exucute()對應
ProgressDialog pdialog;
// 定義記錄已經讀取行的數量
int hasRead = 0;
Context mContext;
public DownTask(Context ctx)
{
mContext = ctx;
}
@Override
protected String doInBackground(URL... params)
{
StringBuilder sb = new StringBuilder();
try
{
URLConnection conn = params[0].openConnection();
// 打開conn連接對應的輸入流,並將它包裝成BufferedReader
BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream()
, "utf-8"));
String line = null;
while ((line = br.readLine()) != null)
{
sb.append(line + "\n");
hasRead++;
publishProgress(hasRead);
}
return sb.toString();
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result)
{
// 返回HTML頁面的內容
show.setText(result);
pdialog.dismiss();
}
@Override
protected void onPreExecute()
{
pdialog = new ProgressDialog(mContext);
// 設置對話框的標題
pdialog.setTitle("任務正在執行中");
// 設置對話框 顯示的內容
pdialog.setMessage("任務正在執行中,敬請等待...");
// 設置對話框不能用“取消”按鈕關閉
pdialog.setCancelable(false);
// 設置該進度條的最大進度值
pdialog.setMax(202);
// 設置對話框的進度條風格
pdialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
// 設置對話框的進度條是否顯示進度
pdialog.setIndeterminate(false);
pdialog.show();
}
@Override
protected void onProgressUpdate(Integer... values)
{
// 更新進度
show.setText("已經讀取了【" + values[0] + "】行!");
pdialog.setProgress(values[0]);
}
}
}
main.xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AsyncTaskTest">
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="14dp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="下載"
android:onClick="download" />
</RelativeLayout>
AndroidManifest.xml ,添加權限
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.crazyit.handler"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".AsyncTaskTest"
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>
有些朋友也許會有疑惑,AsyncTask內部是怎麼執行的呢,它執行的過程跟我們使用Handler又有什麼區別呢?答案是:AsyncTask是對Thread+Handler良好的封裝,在android.os.AsyncTask代碼裏仍然可以看到Thread和Handler的蹤跡。下面就向大家詳細介紹一下AsyncTask的執行原理。
看看這個圖,發現 只有doInBackground()是抽象方法,其餘幾個是空方法,我們需要的時候可以選擇性的覆寫它們;publishProgress(Progress... values)是final修飾的,不能覆寫,只能去調用,我們一般會在doInBackground(Params... params)中調用此方法;
另外,我們可以看到有一個Status的枚舉類和getStatus()方法,Status枚舉類代碼段如下:
//初始狀態
private volatile Status mStatus = Status.PENDING;
public enum Status {
/**
* Indicates that the task has not been executed yet.
*/
PENDING,
/**
* Indicates that the task is running.
*/
RUNNING,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED,
}
/**
* Returns the current status of this task.
*
* @return The current status.
*/
public final Status getStatus() {
return mStatus;
}
可以看到,AsyncTask的初始狀態爲PENDING,代表待定狀態,RUNNING代表執行狀態,FINISHED代表結束狀態,這幾種狀態在AsyncTask一次生命週期內的很多地方被使用,非常重要。
介紹完大綱視圖相關內容之後,接下來,我們會從execute(Params... params)作爲入口,重點分析一下AsyncTask的執行流程,我們來看一下execute(Params... params)方法的代碼段:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
//如果該任務正在被執行則拋出異常
//值得一提的是,在調用cancel取消任務後,狀態仍未RUNNING
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
//如果該任務已經執行完成則拋出異常
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
//改變狀態爲RUNNING
mStatus = Status.RUNNING;
//調用onPreExecute方法
onPreExecute();
mWorker.mParams = params;
sExecutor.execute(mFuture);
return this;
}
代碼中涉及到三個陌生的變量:mWorker、sExecutor、mFuture,我們也會看一下他們的真面目:
關於sExecutor,它是java.util.concurrent.ThreadPoolExecutor的實例,用於管理線程的執行。代碼如下
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 10;
//新建一個隊列用來存放線程
private static final BlockingQueue<Runnable> sWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
//新建一個線程工廠
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
//新建一個線程
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//新建一個線程池執行器,用於管理線程的執行
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
mWorker實際上是AsyncTask的一個的抽象內部類的實現對象實例,它實現了Callable<Result>接口中的call()方法,代碼如下
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
而mFuture實際上是java.util.concurrent.FutureTask的實例,下面是它的FutureTask類的相關信息:
/**
* A cancellable asynchronous computation.
* ...
*/
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
可以看到FutureTask是一個可以中途取消的用於異步計算的類。
下面是mWorker和mFuture實例在AsyncTask中的體現:
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
//call方法被調用後,將設置優先級爲後臺級別,然後調用AsyncTask的doInBackground方法
public Result call() throws Exception {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return doInBackground(mParams);
}
};
//在mFuture實例中,將會調用mWorker做後臺任務,完成後會調用done方法
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
Message message;
Result result = null;
try {
result = get();
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
//發送取消任務的消息
message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
message.sendToTarget();
return;
} catch (Throwable t) {
throw new RuntimeException("An error occured while executing "
+ "doInBackground()", t);
}
//發送顯示結果的消息
message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(AsyncTask.this, result));
message.sendToTarget();
}
};
}
我們看到上面的代碼中,mFuture實例對象的done()方法中,如果捕捉到了CancellationException類型的異常,則發送一條“MESSAGE_POST_CANCEL”的消息;如果順利執行,則發送一條“MESSAGE_POST_RESULT”的消息,而消息都與一個sHandler對象關聯。這個sHandler實例實際上是AsyncTask內部類InternalHandler的實例,而InternalHandler正是繼承了Handler,下面我們來分析一下它的代碼:
private static final int MESSAGE_POST_RESULT = 0x1; //顯示結果
private static final int MESSAGE_POST_PROGRESS = 0x2; //更新進度
private static final int MESSAGE_POST_CANCEL = 0x3; //取消任務
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
//調用AsyncTask.finish方法
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
//調用AsyncTask.onProgressUpdate方法
result.mTask.onProgressUpdate(result.mData);
break;
case MESSAGE_POST_CANCEL:
//調用AsyncTask.onCancelled方法
result.mTask.onCancelled();
break;
}
}
}
我們看到,在處理消息時,遇到“MESSAGE_POST_RESULT”時,它會調用AsyncTask中的finish()方法,我們來看一下finish()方法的定義:private void finish(Result result) {
if (isCancelled()) result = null;
onPostExecute(result); //調用onPostExecute顯示結果
mStatus = Status.FINISHED; //改變狀態爲FINISHED
}
原來finish()方法是負責調用onPostExecute(Result result)方法顯示結果並改變任務狀態的啊。
另外,在mFuture對象的done()方法裏,構建一個消息時,這個消息包含了一個AsyncTaskResult類型的對象,然後在sHandler實例對象的handleMessage(Message msg)方法裏,
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
AsyncTask的一個內部類,是用來包裝執行結果的一個類
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
總結以下,當我們調用execute(Params... params)方法後,execute方法會調用onPreExecute()方法,然後由ThreadPoolExecutor實例sExecutor執行一個FutureTask任務,這個過程中doInBackground(Params... params)將被調用,如果被開發者覆寫的doInBackground(Params... params)方法中調用了publishProgress(Progress... values)方法,則通過InternalHandler實例sHandler發送一條MESSAGE_POST_PROGRESS消息,更新進度,sHandler處理消息時onProgressUpdate(Progress... values)方法將被調用;如果遇到異常,則發送一條MESSAGE_POST_CANCEL的消息,取消任務,sHandler處理消息時onCancelled()方法將被調用;如果執行成功,則發送一條MESSAGE_POST_RESULT的消息,顯示結果,sHandler處理消息時onPostExecute(Result result)方法被調用。
經過上面的介紹,相信朋友們都已經認識到AsyncTask的本質了,它對Thread+Handler的良好封裝,減少了開發者處理問題的複雜度,提高了開發效率,希望朋友們能多多體會一下。