前言
AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主線程並在主線程中更新UI。從實現上來說,AsyncTask封裝了Thread和Handler,通過AsyncTask可以更加方便地執行後臺任務以及在主線程中訪問UI,但是AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時的任務來說,建議使用線程池。
AsyncTask 泛型參數
AsyncTask是一個抽象的泛型類,它提供了Params、Progress和Result這三個泛型參數。
- Params:表示參數的類型;
- Progress:表示後臺任務的執行進度的類型;
- Result:表示後臺任務的返回結果的類型;
如果AsyncTask確實不需要傳遞具體的參數,那麼這三個泛型參數可以用Void來代替。AsyncTask這個類的聲明如下所示。
public abstract class AsyncTask<Params, Progress, Result>
AsyncTask 核心方法
AsyncTask提供了5個核心方法,它們的含義如下所示。
- onPreExecute():在主線程中執行,在異步任務執行之前,此方法會被調用,一般可以用於做一些準備工作。
- doInBackground(Params... params):在線程池中執行,此方法用於執行異步任務,params參數表示異步任務的輸入參數。在此方法中可以通過publishProgress方法來更新任務的進度,publishProgress方法會調用onProgressUpdate方法。另外此方法需要返回計算結果給onPostExecute方法。
- onProgressUpdate(Progress... values):在主線程中執行,當後臺任務的執行進度發生改變時此方法會被調用。
- onPostExecute(Result result):在主線程中執行,在異步任務執行之後,此方法會被調用,其中result參數是後臺任務的返回值,即doInBackground的返回值。
- Cancel(Boolean mayInterruptIfRunning):取消當前任務,mayInterruptIfRunning參數代表是否中斷,true:中斷,false:不中斷。
上面這幾個方法,onPreExecute先執行,接着是doInBackground,最後纔是onPostExecute。除了上述四個方法以外,AsyncTask還提供了onCanceled()方法,它同樣在主線程中執行,當異步任務被取消時,onCanceled()方法會被調用,這個時候onPostExecute則不會被調用。
AsyncTask 使用示例
下面提供一個典型的示例,如下所示。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for(int i=0; i<count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
// 更新進度,就是通過handler發送進度給主線程,會回調onProgressUpdate方法
publishProgress((int) (i / (float)count) * 100));
if(isCanceled()) {
break;
}
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Download " + result + " bytes");
}
}
在上面的代碼中,實現了一個具體的AsyncTask類,這個類主要用於模擬文件的下載過程,它的輸入參數類型爲URL,後臺任務的進度參數爲Integer,而後臺任務的返回結果爲Long參數。注意到doInBackground和onProgressUpdate方法它們的參數中均包含...的字樣,在Java中...表示參數的數量不定,它是一種數組型參數,...的概念和C語言中的...是一致的。當要執行上述下載任務時,可以通過如下方式來完成:
new DownloadFilesTask().execute(url1, url2, url3);
在DownloadFilesTask中,doInBackground用來執行具體的下載任務並通過publishProgress方法來更新下載的進度,同時還要判斷下載任務是否被外界取消了。當下載任務完成後,doInBackground會返回結果,即下載的總字節數。需要注意的是,doInBackground是在線程池中執行的。onProgressUpdate用於更新界面中下載的進度,它運行在主線程,當publishProgress被調用時,此方法就會被調用。當下載任務完成後,onPostExecute方法就會被調用,它也是運行在主線程中,這個時候我們就可以在界面上做出一些提示,比如彈出一個對話框告知用戶下載已經完成。
AsyncTask 條件限制
AsyncTask在具體的使用過程中也是有一些條件限制的,主要有如下幾點:
- AsyncTask 的類必須在主線程中加載,這就意味着第一次訪問AsyncTask必鬚髮生在主線程,當然這個過程在Android 4.1及以上版本中已經被系統自動完成。
- AsyncTask的對象必須在主線程中創建。
- AsyncTask的ececute方法必須在UI線程調用。
- 不要在程序中直接調用onPreExecute()、onPostExecute()、doInBackground()和onProgressUpdate()方法。
- 一個AsyncTask對象只能執行一次,即只能調用一次execute()方法,否則會報運行時異常。
- 在Android 1.6之前,AsyncTask是串行執行任務的,Android 1.6的時候AsyncTask開始採用線程池處理並行任務,但是從Android 3.0 開始,爲了避免AsyncTask所帶來的併發錯誤,AsyncTask又採用一個線程來串行執行任務。儘管如此,在Android 3.0以及後續的版本中,我們仍然可以通過AsyncTask的executeOnExecutor方法來並行執行任務。
AsyncTask 工作原理
爲了分析AsyncTask的工作原理,我們從它的execute方法開始分析,execute方法又會調用executeOnExecutor方法,它們的實現如下所示:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case 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)");
}
}
- // 設置爲運行狀態
mStatus = Status.RUNNING;
- // 調用子類的onPreExecute方法,在主線程中執行
onPreExecute();
- // 把參數傳遞給mWorker工作線程
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
在上面的代碼中,sDefaultExecutor實際上是一個串行的線程池,一個進程中所有的AsyncTask全部在這個串行的線程池中排隊執行,這個排隊執行的過程後面會再進程分析。在executeOnExecutor方法中,AsyncTask的onPreExecute方法最先執行,然後線程池開始執行。下面分析線程池的執行過程,如下所示:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
// 一個個執行 接口的大小可變數組的實現。數組雙端隊列沒有容量限制,不是線程安全
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
// 即將執行的任務
Runnable mActive;
public synchronized void execute(final Runnable r) {
// 插入元素到尾端
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
// 做完一個任務執行下一個任務
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
// 取出第一個任務並移除
if ((mActive = mTasks.poll()) != null) {
// 線程池去執行任務
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
從SerialExecutor的實現可以分析AsyncTask的排隊執行的過程。
1、首先系統會把AsyncTask的Params參數封裝爲FutureTask對象,FutureTask是一個併發類,在這裏充當了Runnable的作用。
2、接着這個FutureTask會交給SeerialExecutor的execute方法去處理,SerialExecutor的execute方法首先會把FutureTask對象插入到任務隊列mTasks中,
3、如果這個時候沒有正在活動的AsyncTask任務,那麼就會調用SerialExecutor的scheduleNext方法來執行下一個AsyncTask任務。
4、同時當一個AsyncTask任務執行完後,AsyncTask會繼續執行其他任務直到所有的任務都被執行爲止,從這一點可以看出,在默認情況下,AsyncTask是串行執行的。
AsyncTask中有兩個線程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler),其中線程池SerialExecutor用於任務的排隊,而線程池
THREAD_POOL_EXECUTOR用於真正地執行任務,InternalHandler用於將執行環境從線程池切換到主線程。在AsyncTask的構造方法中有如下這麼一段代碼,由於FutureTask的run方法會調用mWork的call方法(FutureTask源碼解析:http://blog.csdn.net/xhbxhbsq/article/details/53608164),因此mWorker的call方法最終會在線程池中執行。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
在mWorker的call方法中,首先將mTaskInvoked設爲true,表示當前任務已經被調用過了,然後執行AsyncTask的doInBackground方法,接着將其返回值傳遞給postResult方法,它的實現如下所示。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
在上面的代碼中,postResult方法會通過getHandler發送一個MESSAGE_POST_RESULT的消息,這個getHandler的定義如下所示。
private static InternalHandler sHandler;
private static Handler getHandler() {
// 單例模式,餓漢式
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
private static class InternalHandler extends Handler {
public InternalHandler() {
//
Looper.getMainLooper()獲取主線程的Looper super(Looper.getMainLooper());
}
@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
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
可以發現,sHandler是一個靜態的Handler對象,爲了能夠將執行環境切換到主線程,這就要求sHandler這個對象必須在主線程中創建。由於靜態成員會在加載類的時候進行初始化,因此這就變相要求AsyncTask的類必須在主線程中加載,否則同一個進程中的AsyncTask都將無法正常工作。sHandler收到MESSAGE_POST_RESULT這個消息後會調用AsyncTask的finish方法,如下所示。
private void finish(Result result) {
// 是否設置了取消
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
// 設置狀態爲FINISHED
mStatus = Status.FINISHED;
}
public final boolean cancel(boolean mayInterruptIfRunning) {
// 設置取消
mCancelled.set(true);
//
mayInterruptIfRunning 是否中斷true:中斷,false:不中斷 return mFuture.cancel(mayInterruptIfRunning);
}
AsyncTask的finish方法的邏輯比較簡單,如果AsyncTask被取消執行了,那麼就調用onCanceled方法,否則就會調用onPostExecute方法,可以看到doInBackground的返回結果會傳遞給onPostExecute方法,到這裏AsyncTask的整個工作過程就分析完畢了。