說到AsyncTask,它幾乎能夠用最簡單的方式將操作非同步執行,再呈現給UI執行緒。你不需要自己寫一個執行緒,然後通過Handler
去將結果返回給UI執行緒。只要簡單的重寫
onPreExecute
,doInBackground
,onProgressUpdate
,onPostExecute
四個方法,然後呼叫execute
方法,是不是超級簡單。
可是,你瞭解AsyncTask是如何操作你的任務的嗎?它是如何封裝Handler將非同步任務執行結果返回給UI執行緒的?使用AsyncTask有哪些需要注意的?本文從原始碼分析AsyncTask的工作原理,部分內容來自原始碼。
1.任務執行方式
目前的AsyncTask預設的任務處理是在單執行緒中順序執行,之前有過一段時間可以線上程池中執行,不信你看execute
方法的註釋:
我簡單的翻譯一下,execute
方法將佇列中的任務在一個後臺的單執行緒或執行緒池中執行。AsyncTask的第一個版本是順序執行。在1.6(DONUT)版本後,改成多工的執行緒池中執行。但在3.2(HONEYCOMB)後,為了避免一些執行緒同步的錯誤,又改回在單執行緒中執行。如果想線上程池中執行,可以這樣:
new AsynTask().executeOn(AsyncTask.THREAD_POOL_EXECUTOR,"")
複製程式碼
顯然這種方法,並不建議。
既然說到AsyncTask.THREAD_POOL_EXECUTOR
,它是什麼呢?
public static final Executor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory);
複製程式碼
THREAD_POOL_EXECUTOR是一個執行緒池的執行器(有關執行緒池的可以參考 這篇文章。在這裡你只要瞭解它是一個核心執行緒數量是CPU數+1,最大執行緒數量是2*CPU數量+1就可以了。
SerialExecutor
話說回來,單執行緒順序執行是如何執行的?請看:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
複製程式碼
這個sDefaultExecutor
是AsyncTask任務執行器。看過原始碼你會發現有這樣一個方法:
/** @hide */
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}
複製程式碼
sDefaultExecutor
是可以設定的,只不過你呼叫不了,被隱藏了(@hide)。
那麼SERIAL_EXECUTOR
是什麼呢?它是一個SerialExecutor
的例項。
可以看到,在execute
中會呼叫offer
方法會將Runnable r
包裝一下放到ArrayDeque
佇列裡,包裝的新Runnable
保證原來的Runnable
執行之後會去取佇列裡的下一個Runnable
,從而不會導致中斷。
scheduleNext
做了什麼呢?可以看到scheduleNext
是從佇列中取出Runnable
然後交給THREAD_POOL_EXECUTOR
執行。也就是說SerialExecutor
只是將任務按先後順序排列到佇列中,真正執行任務的是THREAD_POOL_EXECUTOR
。
2.任務執行過程
在你呼叫execute
的時候會這樣:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
複製程式碼
你看,最後還是會呼叫到executeOnExecutor
,預設傳了一個SERIAL_EXECUTOR
。並且,看見那個@MainThread
了吧,execute
一定在主執行緒呼叫。
請看executeOnExecutor
:
每個AsyncTask都有一個Status,代表這個AsyncTask的狀態,Status是一個列舉變數,每一個狀態在這個Task的生命週期裡賦值一次,也就是這個Task一定會經歷 PENDING -> RUNNING -> FINISHED
的過程。
PENDING代表Task還沒有被執行,RUNNING代表當前任務正在執行,FINISHED代表的是onPostExecute
方法已經執行完了,而不是doInBackground
。
/**
* Indicates the current status of the task. Each status will be set only once
* during the lifetime of a task.
*/
public enum Status {
PENDING,
RUNNING,
FINISHED,
}
複製程式碼
話說回到executeOnExecutor
中,如果當前的Task的狀態不是PENDING,那麼就會丟擲異常。也就是同一個Task,你只能execute
一次,直到它的非同步任務執行完成,你才可以再次呼叫他的execute
方法,否則一定會報錯。
然後呼叫onPreExecute
方法,之後會提交給SERIAL_EXECUTOR
執行。但是這個mWorker
是什麼?mFuture
是什麼?
mWorker
mWorker
是WorkerRunnable
的具體實現,實現Callable
介面,相當於一個能夠儲存引數,返回結果的Runnable
。
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
複製程式碼
當你建立一個AsyncTask的時候就會建立mWorker
和mFutureTask
。
可以看到,mWorker
的call
方法主要的工作是設定call
是否被呼叫,呼叫你重寫的doInBackground
方法,獲得Result
(這個Result
的型別就是你宣告AsyncTask時傳入的型別),再將Result
呼叫postResult
方法返回。關於postResult
請往下看。
mFuture
可以看到mFuture
中有一個postResultIfNotInvoked(get());
方法,通過get方法獲得mWorker
的執行結果,然後呼叫postResultIfNotInvoked
方法,由於某些原因,mWorker
的call
可能沒有執行,所以在postResultIfNotInvoked
中能夠保證postResult
一定會執行一次,要不在mWorker
的call
中執行,要不在postResultIfNotInvoked
中執行。
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
複製程式碼
那麼這個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
實際上是獲得了一個AsyncTask內部的一個Handler
,將result
包裝在AsyncTaskResult
中,並將它放在message傳送給Handler。
那麼AsyncTaskResult
是如何封裝的?
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
複製程式碼
可以看到包含AsyncTask的例項(mTask)和資料(mData)。當將任務執行的結果返回時,mData儲存的是Result,當更新進度的時候mData儲存的是和Progress
型別一樣的資料。你可以往下看。
獲取執行結果和更新執行的進度
先說一說Handler
。
每個AsyncTask都會獲得一個InternalHandler
的例項。可以看到,InternalHandler
繫結到了主執行緒的Looper
中(關於Looper與Handler的關係,可以參考這篇文章,所以你在非同步執行緒中執行的結果最終都可以通過InternalHandler
交給主執行緒處理。再看handlerMessage
方法,獲得AsyncTaskResult
物件,如果傳的是MESSAGE_POST_RESULT
型別,就呼叫AsyncTask的finish
方法(別忘了result.mTask
其實就是當前的AsyncTask)。
finish
做了什麼?
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
複製程式碼
可以看到,判斷你是否取消了任務,取消則優先執行onCancelled
回撥,否則執行onPostExecute
,並更改Task的狀態。
如果是一個MESSAGE_POST_PROGRESS
,就會執行onProgressUpdate
方法。那MESSAGE_POST_PROGRESS
的資訊是誰去傳送的呢?請看:
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
複製程式碼
也就是當你呼叫publishProgress
的時候,會將傳遞的values
包裝成AsyncTaskResult
,AsyncTaskResult
的mData會儲存進度的資料,將message傳送給handler。
有個方法需要說明一下,就是cancle
方法。
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
複製程式碼
作用是設定被取消的狀態,然後取消FutureTask
的執行。當task已經執行完了,或已經被取消,或因為某些原因不能被取消,會返回false。如果任務已經執行,那麼根據mayInterruptIfRunning
決定是否打斷(interrupt)當前正在執行Task的執行緒。
呼叫這個方法會在doInBackground
返回後回撥onCancelled
方法,並且onPostExecute
不會執行,所以當你需要取消Task的時候記得在doInBackground
通過isCancelled
檢查返回值。
注意事項
1. 由於AsyncTask是單執行緒順序執行的,所以不要用AsyncTask執行耗時太久的操作,如果有很多耗時太久的執行緒,最好使用執行緒池。
2. onPreExecute
、onProgressUpdate
、onPostExecute
都是在UI執行緒呼叫的,doInBackground
在後臺執行緒執行。
3. 呼叫cancel
方法取消任務執行,這個時候onPostExecute
就不會執行了,取而代之的是cancel
方法,所以為了儘快的退出任務的執行,在doInBackground
中呼叫isCancelled
檢查是否取消的狀態。
4. 其他
- AsyncTask類一定要在主執行緒載入
- AsyncTask類的例項一定在主執行緒建立
execute
方法一定在主執行緒呼叫- 不要主動呼叫
onPreExecute
等方法 - 任務只能在完成前執行一次。