在Android中實現非同步任務機制有兩種方式,Handler和AsyncTask。
Handler模式需要為每一個任務建立一個新的執行緒,任務完成後通過Handler例項向UI執行緒傳送訊息,完成介面的更新,這種方式對於整個過程的控制比較精細,但也是有缺點的,例如程式碼相對臃腫,在多個任務同時執行時,不易對執行緒進行精確的控制。關於Handler的相關知識,前面也有所介紹,不清楚的朋友們可以參照一下。
為了簡化操作,Android1.5提供了工具類android.os.AsyncTask,它使建立非同步任務變得更加簡單,不再需要編寫任務執行緒和Handler例項即可完成相同的任務。
先來看看AsyncTask的定義:
1 |
public abstract class AsyncTask<Params, Progress, Result> { |
三種泛型型別分別代表“啟動任務執行的輸入引數”、“後臺任務執行的進度”、“後臺計算結果的型別”。在特定場合下,並不是所有型別都被使用,如果沒有被使用,可以用java.lang.Void型別代替。
一個非同步任務的執行一般包括以下幾個步驟:
1.execute(Params… params),執行一個非同步任務,需要我們在程式碼中呼叫此方法,觸發非同步任務的執行。
2.onPreExecute(),在execute(Params… params)被呼叫後立即執行,一般用來在執行後臺任務前對UI做一些標記。
3.doInBackground(Params… params),在onPreExecute()完成後立即執行,用於執行較為費時的操作,此方法將接收輸入引數和返回計算結果。在執行過程中可以呼叫publishProgress(Progress… values)來更新進度資訊。
4.onProgressUpdate(Progress… values),在呼叫publishProgress(Progress… values)時,此方法被執行,直接將進度資訊更新到UI元件上。
5.onPostExecute(Result result),當後臺操作結束時,此方法將會被呼叫,計算結果將做為引數傳遞到此方法中,直接將結果顯示到UI元件上。
在使用的時候,有幾點需要格外注意:
1.非同步任務的例項必須在UI執行緒中建立。
2.execute(Params… params)方法必須在UI執行緒中呼叫。
3.不要手動呼叫onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)這幾個方法。
4.不能在doInBackground(Params… params)中更改UI元件的資訊。
5.一個任務例項只能執行一次,如果執行第二次將會丟擲異常。
接下來,我們來看看如何使用AsyncTask執行非同步任務操作,我們先建立一個專案,結構如下:
結構相對簡單一些,讓我們先看看MainActivity.java的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
package com.scott.async; import java.io.ByteArrayOutputStream; import java.io.InputStream; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; 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.util.Log; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends Activity { private static final String TAG = "ASYNC_TASK"; private Button execute; private Button cancel; private ProgressBar progressBar; private TextView textView; private MyTask mTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); execute = (Button) findViewById(R.id.execute); execute.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //注意每次需new一個例項,新建的任務只能執行一次,否則會出現異常 mTask = new MyTask(); mTask.execute("http://www.baidu.com"); execute.setEnabled(false); cancel.setEnabled(true); } }); cancel = (Button) findViewById(R.id.cancel); cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //取消一個正在執行的任務,onCancelled方法將會被呼叫 mTask.cancel(true); } }); progressBar = (ProgressBar) findViewById(R.id.progress_bar); textView = (TextView) findViewById(R.id.text_view); } private class MyTask extends AsyncTask<String, Integer, String> { //onPreExecute方法用於在執行後臺任務前做一些UI操作 @Override protected void onPreExecute() { Log.i(TAG, "onPreExecute() called"); textView.setText("loading..."); } //doInBackground方法內部執行後臺任務,不可在此方法內修改UI @Override protected String doInBackground(String... params) { Log.i(TAG, "doInBackground(Params... params) called"); try { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(params[0]); HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { HttpEntity entity = response.getEntity(); InputStream is = entity.getContent(); long total = entity.getContentLength(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int count = 0; int length = -1; while ((length = is.read(buf)) != -1) { baos.write(buf, 0, length); count += length; //呼叫publishProgress公佈進度,最後onProgressUpdate方法將被執行 publishProgress((int) ((count / (float) total) * 100)); //為了演示進度,休眠500毫秒 Thread.sleep(500); } return new String(baos.toByteArray(), "gb2312"); } } catch (Exception e) { Log.e(TAG, e.getMessage()); } return null; } //onProgressUpdate方法用於更新進度資訊 @Override protected void onProgressUpdate(Integer... progresses) { Log.i(TAG, "onProgressUpdate(Progress... progresses) called"); progressBar.setProgress(progresses[0]); textView.setText("loading..." + progresses[0] + "%"); } //onPostExecute方法用於在執行完後臺任務後更新UI,顯示結果 @Override protected void onPostExecute(String result) { Log.i(TAG, "onPostExecute(Result result) called"); textView.setText(result); execute.setEnabled(true); cancel.setEnabled(false); } //onCancelled方法用於在取消執行中的任務時更改UI @Override protected void onCancelled() { Log.i(TAG, "onCancelled() called"); textView.setText("cancelled"); progressBar.setProgress(0); execute.setEnabled(true); cancel.setEnabled(false); } } } |
佈局檔案main.xml程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/execute" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="execute"/> <Button android:id="@+id/cancel" android:layout_width="fill_parent" android:layout_height="wrap_content" android:enabled="false" android:text="cancel"/> <ProgressBar android:id="@+id/progress_bar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:progress="0" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> <ScrollView android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content"/> </ScrollView> </LinearLayout> |
因為需要訪問網路,所以我們還需要在AndroidManifest.xml中加入訪問網路的許可權:
1 |
<uses-permission android:name="android.permission.INTERNET"/> |
我們來看一下執行時的介面:
以上幾個截圖分別是初始介面、執行非同步任務時介面、執行成功後介面、取消任務後介面。執行成功後,整個過程日誌列印如下:
如果我們在執行任務時按下了“cancel”按鈕,日誌列印如下:
可以看到onCancelled()方法將會被呼叫,onPostExecute(Result result)方法將不再被呼叫。
上面介紹了AsyncTask的基本應用,有些朋友也許會有疑惑,AsyncTask內部是怎麼執行的呢,它執行的過程跟我們使用Handler又有什麼區別呢?答案是:AsyncTask是對Thread+Handler良好的封裝,在android.os.AsyncTask程式碼裡仍然可以看到Thread和Handler的蹤跡。下面就向大家詳細介紹一下AsyncTask的執行原理。
我們先看一下AsyncTask的大綱檢視:
我們可以看到關鍵幾個步驟的方法都在其中,doInBackground(Params… params)是一個抽象方法,我們繼承AsyncTask時必須覆寫此方法;onPreExecute()、onProgressUpdate(Progress… values)、onPostExecute(Result result)、onCancelled()這幾個方法體都是空的,我們需要的時候可以選擇性的覆寫它們;publishProgress(Progress… values)是final修飾的,不能覆寫,只能去呼叫,我們一般會在doInBackground(Params… params)中呼叫此方法;另外,我們可以看到有一個Status的列舉類和getStatus()方法,Status列舉類程式碼段如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//初始狀態 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)方法的程式碼段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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的例項,用於管理執行緒的執行。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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()方法,程式碼如下:
1 2 3 |
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; } |
而mFuture實際上是java.util.concurrent.FutureTask的例項,下面是它的FutureTask類的相關資訊:
1 2 3 4 5 |
/** * A cancellable asynchronous computation. * ... */ public class FutureTask<V> implements RunnableFuture<V> { |
1 2 3 4 5 6 7 |
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中的體現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
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,下面我們來分析一下它的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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()方法的定義:
1 2 3 4 5 |
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)方法裡,使用下面這種方式取得訊息中附帶的物件:
1 |
AsyncTaskResult result = (AsyncTaskResult) msg.obj; |
這個AsyncTaskResult究竟是什麼呢,它又包含什麼內容呢?其實它也是AsyncTask的一個內部類,是用來包裝執行結果的一個類,讓我們來看一下它的程式碼結構:
1 2 3 4 5 6 7 8 9 10 |
@SuppressWarnings({"RawUseOfParameterizedType"}) private static class AsyncTaskResult<Data> { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } } |
看以看到這個AsyncTaskResult封裝了一個AsyncTask的例項和某種型別的資料集,我們再來看一下構建訊息時的程式碼:
1 2 3 4 |
//傳送取消任務的訊息 message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null)); message.sendToTarget(); |
1 2 3 4 |
//傳送顯示結果的訊息 message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(AsyncTask.this, result)); message.sendToTarget(); |
在處理訊息時是如何使用這個物件呢,我們再來看一下:
1 |
result.mTask.finish(result.mData[0]); |
1 |
result.mTask.onProgressUpdate(result.mData); |
概括來說,當我們呼叫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的良好封裝,減少了開發者處理問題的複雜度,提高了開發效率,希望朋友們能多多體會一下。