深入理解 Android 之 AsyncTask
為什么需要工作者線程
我們知道,Android應用的主線程(UI 線程)肩負著繪制用戶界面和及時響應用戶操作的重任,為了避免“用戶點擊按鈕后沒反應”這樣的糟糕用戶體驗,我們就要確保主線程時刻保持著較高的響應性。為了做到這一點,我們就要把耗時的任務移出主線程,那么耗時的任務交給誰來完成呢?答案就是工作者線程。Android開發中我們通常讓主線程負責前臺用戶界面的繪制以及響應用戶的操作,讓工作者線程在后臺執行一些比較耗時的任務。Android中的工作者線程主要有AsyncTask、IntentService、HandlerThread,它們本質上都是對線程或線程池的封裝。(對線程和線程池還不太理解的小伙伴,請戳文末參考資料部分給出的相關鏈接。)
總的來說,我們使用工作者線程是因為主線程已經有很多活要干了,累活就得交給別人干。AsyncTask是我們日常中廣泛使用的一種工作者線程,它的方便之處在于可以在后臺任務執行完畢時根據返回結果相應的更新UI。下面我們來深入了解一下AsyncTask。
AsyncTask的基本使用
AsyncTask是對Handler與線程池的封裝。使用它的方便之處在于能夠更新用戶界面,當然這里更新用戶界面的操作還是在主線程中完成的,但是由于AsyncTask內部包含一個Handler,所以可以發送消息給主線程讓它更新UI。另外,AsyncTask內還包含了一個線程池。使用線程池的主要原因是避免不必要的創建及銷毀線程的開銷。設想下面這樣一個場景:有100個只需要0.001ms就能執行完畢的任務,如果創建100個線程來執行這些任務,執行完任務的線程就進行銷毀。那么創建與銷毀線程的開銷就很可能成為了影響性能的瓶頸。通過使用線程池,我們可以實現維護固定數量的線程,不管有多少任務,我們都始終讓線程池中的線程輪番上陣,這樣就避免了不必要的開銷。
在這里簡單介紹下AsyncTask的使用方法,為后文對它的工作原理的研究做鋪墊,關于AsyncTask的詳細介紹大家可以參考官方文檔或是相關文章。
AsyncTask是一個抽象類,我們在使用時需要定義一個它的派生類并重寫相關方法。AsyncTask類的聲明如下:
public abstract class AsyncTask<Params, Progress, Result>
我們可以看到,AsyncTask是一個泛型類,它的三個類型參數的含義如下:
- Params:doInBackground方法的參數類型;
- Progress:AsyncTask所執行的后臺任務的進度類型;
- Result:后臺任務的返回結果類型。
我們再來看一下AsyncTask類主要為我們提供了哪些方法:
onPreExecute() //此方法會在后臺任務執行前被調用,用于進行一些準備工作
doInBackground(Params... params) //此方法中定義要執行的后臺任務,在這個方法中可以調用publishProgress來更新任務進度
//(publishProgress內部會調用onProgressUpdate方法)
onProgressUpdate(Progress... values) //由publishProgress內部調用,表示任務進度更新
onPostExecute(Result result) //后臺任務執行完畢后,此方法會被調用,參數即為后臺任務的返回結果
onCancelled() //此方法會在后臺任務被取消時被調用
以上方法中,除了doInBackground方法由AsyncTask內部線程池執行外,其余方法均在主線程中執行。
AsyncTask的局限性
AsyncTask的優點在于執行完后臺任務后可以很方便的更新UI,然而使用它存在著諸多的限制。先拋開內存泄漏問題,使用AsyncTask主要存在以下局限性:
- 在Android 4.1版本之前,AsyncTask類必須在主線程中加載,這意味著對AsyncTask類的第一次訪問必須發生在主線程中;在Android 4.1以及以上版本則不存在這一限制,因為ActivityThread的main方法中會自動加載AsyncTask
- AsyncTask對象必須在主線程中創建
- AsyncTask對象的execute方法必須在主線程中調用
- 一個AsyncTask對象只能調用一次execute方法
接下來,我們從源碼的角度去探究一下AsyncTask的工作原理,并嘗試著搞清楚為什么會存在以上局限性。
探索AsyncTask的工作原理
首先,讓我們來看一下AsyncTask類的構造器都做了些什么:
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);
}
}
};
}</code></pre>
在以上代碼中,首先初始化了mWorker,它是一個派生自WorkRunnable類的對象。WorkRunnable是一個抽象類,它實現了Callable<Result>接口。我們再來看一下call方法的定義,首先將mTaskInvoked設為true表示當前任務已被調用過,然后會設置線程的優先級。接著往下看,調用了AsyncTask對象的doInBackground方法開始執行我們所定義的后臺任務,并獲取返回結果存入result中。最后將任務返回結果傳遞給postResult方法。關于postResult方法我們會在下文進行分析。由此我們可以知道,實際上AsyncTask的成員mWorker包含了AyncTask最終要執行的任務(即mWorker的call方法)。
接下來讓我們看看對mFuture的初始化。我們可以看到mFuture是一個FutureTask的直接子類(匿名內部類)的對象,在FutureTask的構造方法中我們傳入了mWorker作為參數。我們使用的是FutureTask的這個構造方法:
public FutureTask(Callable<V> callable) {
if (callable == null) throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
也就是說,mFuture是一個封裝了我們的后臺任務的FutureTask對象,FutureTask類實現了FutureRunnable接口,通過這個接口可以方便的取消后臺任務以及獲取后臺任務的執行結果,具體介紹請看這里: Java并發編程:Callable、Future和FutureTask 。
從上面的分析我們知道了,當mWorker中定義的call方法被執行時,doInBackground就會開始執行,我們定義的后臺任務也就真正開始了。那么這個call方法什么時候會被調用呢?我們可以看到經過層層封裝,實際上是mFuture對象封裝了call方法,當mFuture對象被提交到AsyncTask包含的線程池執行時,call方法就會被調用,我們定義的后臺任務也就開始執行了。下面我們來看一下mFuture是什么時候被提交到線程池執行的。
首先來看一下execute方法的源碼:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
我們可以看到它接收的參數是Params類型的參數,這個參數會一路傳遞到doInBackground方法中。execute方法僅僅是調用了executeOnExecutor方法,并將executeOnExecutor方法的返回值作為自己的返回值。我們注意到,傳入了sDefaultExecutor作為executeOnExecutor方法的參數,那么sDefaultExecutor是什么呢?簡單的說,它是AsyncTask的默認執行器(線程池)。AsyncTask可以以串行或并行兩種方式來執行后臺任務,在Android3.0及以后的版本中,默認的執行方式是串行。這個sDefaultExecutor就代表了默認的串行執行器(線程池)。也就是說我們平常在AsyncTask對象上調用execute方法,使用的是串行方式來執行后臺任務。關于線程池更加詳細的介紹與分析請見: 深入理解Java之線程池
我們再來看一下executeOnExecutor方法都做了些什么:
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;
}</code></pre>
從以上代碼我們可以知道,當AsyncTask對象的當前狀態為RUNNING或FINISHED時,調用execute方法會拋出異常,這意味著不能對正在執行任務的AsyncTask對象或是已經執行完任務的AsyncTask對象調用execute方法,這也就解釋了我們上面提到的局限中的最后一條。
接著我們看到有一個對onPreExecute方法的調用,這表示了在執行后臺任務前確實會調用onPreExecute方法。
再往下看,我們傳入的execute方法的params參數賦值給了mWorker的mParams成員變量;而后調用了exec的execute方法,并傳入了mFuture作為參數。exec就是我們傳進來的sDefaultExecutor。那么接下來我們看看sDefaultExecutor究竟是什么。在AsyncTask類的源碼中,我們可以看到這句:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
sDefaultExecutor被賦值為SERIAL_EXECUTOR,那么我們來看一下SERIAL_EXECUTOR:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
現在,我們知道了實際上sDefaultExecutor是一個SerialExecutor對象,我們來看一下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);
}
}
}</code></pre>
我們來看一下execute方法的實現。mTasks代表了SerialExecutor這個串行線程池的任務緩存隊列,在execute方法中,我們首先用offer方法向任務緩存隊列中添加一個任務,任務的內容如run方法定義所示。我們可以看到,run方法中:調用了mFuture(execute方法的參數r就是我們傳入的mFuture)的run方法,而mFuture的run方法內部會調用mWorker的call方法,然后就會調用doInBackground方法,我們的后臺任務也就開始執行了。那么我們提交到任務緩存隊列中的任務什么時候會被執行呢?我們接著看。
我們可以看到SerialExecutor類中定義了一個Runnable變量mActive,它代表了當前正在執行的AsyncTask對象。在execute方法中會判斷mActive是否為null,若為null,就調用scheduleNext方法。在scheduleNext方法中,若緩存隊列非空,則調用THREAD_POOL_EXECUTOR.execute方法執行從緩存隊列中取出的任務,這時我們的后臺任務便開始真正執行了。
通過以上的分析,我們可以知道SerialExecutor所完成的工作主要是把任務加到任務緩存隊列中,而真正執行任務的是THREAD_POOL_EXECUTOR。我們來看下THREAD_POOL_EXECUTOR是什么:
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
從上面的代碼我們可以知道,它是一個線程池對象。根據AsyncTask的源碼,我們可以獲取它的各項參數如下:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
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);
由以上代碼我們可以知道:
-
corePoolSize為CPU數加一;
-
maximumPoolSize為CPU數的二倍加一;
-
存活時間為1秒;
-
任務緩存隊列為LinkedBlockingQueue。
現在,我們已經了解到了從我們調用AsyncTask對象的execute方法開始知道后臺任務執行完都發生了什么。現在讓我們回過頭來看一看之前提到的postResult方法的源碼:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
在以上源碼中,先調用了getHandler方法獲取AsyncTask對象內部包含的sHandler,然后通過它發送了一個MESSAGE_POST_RESULT消息。我們來看看sHandler的相關代碼:
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
public InternalHandler() {
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;
}
}
}</code></pre>
從以上代碼中我們可以看到,sHandler是一個靜態的Handler對象。我們知道創建Handler對象時需要當前線程的Looper,所以我們為了以后能夠通過sHandler將執行環境從后臺線程切換到主線程(即在主線程中執行handleMessage方法),我們必須使用主線程的Looper,因此必須在主線程中創建sHandler。這也就解釋了為什么必須在主線程中加載AsyncTask類,是為了完成sHandler這個靜態成員的初始化工作。
在以上的handleMessage方法中,我們可以看到,當sHandler收到MESSAGE_POST_RESULT方法后,會調用finish方法,finish方法的源碼如下:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
首先會通過調用isCancelled方法判斷AsyncTask任務是否被取消,若取消了則調用onCancelled方法,否則調用onPostExecute方法;然后把mStatus設為FINISHED,表示當前AsyncTask對象已經執行完畢。
經過了以上的分析,我們大概了解了AsyncTask的內部運行邏輯,知道了它默認使用串行方式執行任務。那么如何讓它以并行的方式執行任務呢? 閱讀了以上的代碼后,我們不難得到結論,只需調用executeOnExecutor方法,并傳入THREAD_POOL_EXECUTOR作為其線程池即可。
參考資料
- Android SDK Sources
- 《Android開發藝術探索》
- Java核心技術點之多線程
- 深入理解Java之線程池
來自:http://www.jianshu.com/p/490f155fb651