AsyncTask是Android從1.5引入的類,它內部做了比較完整的封裝。藉助它,我們可以很easy的在子線程進行耗時操作,在主線程處理結果,而不用操作Thread或者Handler。即使到目前,用AsyncTask做類似文件的上傳或下載也是很方便的。
使用就不多介紹了,就是按照AsyncTask的泛型定義,自己構造子類繼承AsyncTask。
初始化
繼承AsyncTask的子類在初始化的時候,會調用AsyncTask的無參構造函數,構造函數必須在UI線程調用,具體原因後說。查看源碼,精簡後如下:
mHandler=getMainHandler();
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
......
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
......
}
};
這段代碼,初始化了一個Handler對象mHandler,一個Callable對象mWorker和一個FutureTask對象mFuture,並且將mWorker作爲mFuture的參數傳入。但這些只是定義好了回調和一些異常處理,還沒有真正的觸發執行。
任務的執行
開啓任務,需要執行execute()
,具體源碼:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params){
return executeOnExecutor(sDefaultExecutor, params);
}
註釋寫的很清楚了,必須在主線程調用。接着看:
@MainThread
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();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
這裏就比較清楚了,首先對mStatus做了狀態判斷,證實任務處於未執行狀態時,將任務的狀態置爲執行中,同時調用onPreExecute
,這裏子類可以做一些初始化工作。因爲execute
在主線程執行,因此該方法也是在UI線程中被調用。最後用傳進來的Executor對象去執行mFuture中定義的任務,因爲FutureTask也是實現了Runnable接口的。這裏的Executor對象就是sDefaultExecutor,這是個什麼東西?
sDefaultExecutor
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
在AsyncTask初始化的時候就爲我們提供了一個默認的Executor對象,並且是靜態類型的,也就是說該進程內所有的AsyncTask子類都共用這一個對象。再來看SerialExecutor的源碼:
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);
}
}
}
該類內部維護了一個雙端隊列mTasks,當開始執行時新建一個Runnable放入mTasks隊尾,這裏面調用mFuture的run
方法,點到FutureTask的源碼裏面可以看到,它引用了初始化時傳入的Callable對象,也就是mWorker,並調用其call
方法,這就和前面構造函數初始化的時候串起來了。
前面說了,一個進程中不管new多少AsyncTask實例都是公用的一個SerialExecutor。實際上新建一個Runnable加入到隊尾,並沒有觸發任務開始,什麼時候開始執行呢?就是THREAD_POOL_EXECUTOR.execute(mActive)
。在第一個任務調用execute
方法時,mActive爲null,執行scheduleNext
方法。該方法從隊列頭部取值如果不爲空,則賦值給mActive並開始執行。如果該任務耗時比較長,那麼下一個任務進來的時候mActive不爲null,不會立即執行scheduleNext
。這時候真正執行的時機是,第一個任務在自己執行完畢後一定要走finally代碼塊中的scheduleNext
,這時第二個任務已經被加入到隊尾中,自然就可以保證執行了。這說明SerialExecutor實際上是一個單線程類型的Executor,在任務耗時稍長而有多個任務的情況下,同一時刻只有一個任務執行,執行完畢後才能執行下一個任務。
THREAD_POOL_EXECUTOR
SerialExecutor這個線程池保證了任務的線性執行,真正執行任務的線程池是THREAD_POOL_EXECUTOR。該線程池在AsyncTask類被加載時創建:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
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 BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
AsnycTask默認構造的這個線程池,核心線程和最大線程數和CPU期望核心數有關,keepAliveTime爲30s,阻塞隊列長度爲128。最大線程數雖然不大,但是因爲任務實際是單線程執行的,並不會拋異常。
任務的執行流程
回過頭來,再看異步任務mWorker和mFuture的初始化過程:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
前面說過FutureTask的run()
會調用傳入的Callable對象的call()
,也就是mWorker的call()
,一旦call
開始執行,mTaskInvoked置爲true,這是一個原子型變量,標記任務開始執行。try代碼塊中又調用了doInBackground(mParams)
,這樣就完成了異步調用。過程中如果發生異常,原子變量mCancelled會置爲true。最終獲取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;
}
顯然這個就是Handler的異步消息處理機制。其中的AsyncTaskResult是AsyncTask內部封裝的一個消息實體。而發送消息的Handler就是getHandler()
返回的mHandler,它是在mWorker初始化之前通過getMainHandler()
創建了一個靜態內部類InternalHandler對象,並且和主線程綁定:
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@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;
}
}
}
這裏就可以看到,handleMessage
裏就切回到了主線程,並開始處理接收到的異步信息。因爲前面發送的是MESSAGE_POST_RESULT,而result.mTask就是AsyncTask對象本身,所以是調用了類內部的finish()
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
到這裏,如果mCancelled不爲true,也就是在上述任務執行過程中未發生異常或主動取消,那麼就會調用onPostExecute(result)
,所以我們可以重寫這個方法,在UI線程處理返回的結果。當然如果取消了任務,也可以重寫onCancelled(result)
做一些異常處理。最後將mStatus置爲FINISHED。
更新任務進度
注意到消息類型還有一個MESSAGE_POST_PROGRESS,發現在publishProgress
方法裏發送了這個消息:
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
這個方法AsyncTask本身自己不會調用,而是由使用者在子類的doInBackground
中調用。InternalHandler收到消息後開始處理,又會調用onProgressUpdate
。這樣使用者可以獲取任務執行的進度,並且在主線程實時更新,最常用的就是下載文件顯示進度條。
使用缺陷
從上面的分析看,AsyncTask爲我們封裝好了一整套Thread、Handler、Executor、Runnable,包括異常處理,泛型和回調,有很好的思想。當然,它也有一些缺陷,無法適用一些場景。
首先就是最初AsyncTask是單線程依次執行,從安卓1.6開始,變爲多線程並行執行,但同時最多支持128個任務併發,一旦任務耗時稍長而又有很多個任務的情況下就很容易拋異常。因此從安卓3.0以後,又引入了SerialExecutor,等於還是默認單線程一個一個任務執行。其實安卓也意識到了這個問題,所以它的executeOnExecutor(Executor exec,Params... params)
允許我們可以傳入自己定義的Executor,而不是默認的SerialExecutor。比如下例子:
private void execute() {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 150; i++) {
new MyTask().executeOnExecutor(executorService);
}
}
private class MyTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
try {
Log.i(TAG, Thread.currentThread().getName());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
可以看到快速打印出來的日誌,而不是每隔1s打印一次日誌,確實是並行的。什麼場景要用到並行執行?很典型的一個例子就是本地相冊,因爲Bitmap的解析不能放到UI線程中進行,而如果使用AsyncTask的串行執行,能想像向下滑動時圖片要一個一個的依次加載出來?但是實際上還是有很多坑。
即使是採用並行執行任務,如果相冊裏有幾千張圖片甚至更多,初次加載沒有緩存的話,當快速滑動的時候仍然會發生卡頓。就是當任務負載量短時間內比較高的情況下,doInBackground沒有及時得到執行的原因,在一幀(16ms)內沒有完成任務,造成了丟幀。具體爲什麼,我認爲可能是因爲是使用FutureTask包裝了任務執行的原因,降低了程序整體效率。因爲FutureTask本身就適用於很耗時的任務,而AsyncTask註釋又說明了自身適合的場景是短時間的輕量任務,也不知道谷歌爲何要這麼設計,雖然已經滿足了大部分需求了。除此之外,AsyncTask還存在如在組件中的生命週期、內存泄漏等缺陷。因此高負載量的後臺任務,還是用ThreadPoolExecutor+Runnable比較好。