對於AsyncTask,想必大家一定不陌生,它是我們網絡請求然後更新UI比較常用的一種方式,跟Thread+Handler一樣都是我們處於耗時操作,然後通過發送消息去UI線程進行更新UI。如果有同學還是不瞭解異步的,可以參考一下我的另一篇文章,關於異步消息處理機制的源碼分析。http://blog.csdn.net/qq_435559203/article/details/52787339
簡單介紹使用
可能有些同學會更喜歡使用AsyncTask,因爲它十分清晰地向大家展示了從準備到子線程到UI線程的步驟,下面我們來看一下。
AsyncTask是一個抽象類,所以我們需要用一個自定義子類去繼承它。當繼承的時候,需要設定三個泛型參數。
Parmas:
AsyncTask在執行doInBackground()時需要用到的參數。Progress:
在doInBackground()執行時,可以指定返回類型作爲執行進度。Result:
當AsyncTask執行完後,根據指定的泛型,將對於的JavaBean進行返回。
講述完這對於的三個泛型參數,我們分別說一下最常重寫的三個方法。
onPreExecute():
void的方法。通常我們在一開始需要準備的操作,例如重置某些參數、顯示/隱藏某些UI,都會在這裏完成處理。doInBackground():
抽象類AsyncTask的抽象方法,返回值爲Result,參數爲Parmas…,如我們可以將需要Post的參數以數組的方式傳進doInBackground(),當子線程完成網絡請求會根據我們需要的Result來return對於的數據類型。onPostExecute():
這裏是從子線程到UI線程後的方法。我們這取得doInBackground()返回的Result值,就可以直接更新UI。
源碼分析
注:以下源碼參考的是。
上面的例子:
new CustomAsyncTask().execute(new String[]{});
那麼我們先從構造方法開始看起。源碼如下:
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);
}
}
};
}
第二行,初始化WorkerRunnable,WorkerRunnable是一個靜態內部抽象類,它實現了Callable接口。接着就是Callable的方法。第四行將mTaskInvoked.set(true),表示當前任務已被調用。第六行就是設置線程優先級。第八行是調用類中的doInBackground(),並將值賦給Result,然後傳遞給postResult方法。我們先插隊看看postResult()代碼;
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
大家有沒非常熟悉,這裏就是我們之前在消息異步處理機制說過的,將消息發送至主線程的寫法。這句new AsyncTaskResult(this, result)中,是我們異步任務最後的處理結果。
這裏的getHandler(),命名方式就知道是獲取一個Handler對象,我們就直接看AsyncTask類自定義的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;
}
}
}
上面的一看就能看到,我們直接看finish()。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
這裏可以看到異步任務執行完之後調用finish()後會調用onPostExecute(result); 這個就是我們一開始說處理耗時操作後獲得的結果的方法。最後也將狀態改成執行完成。
then,我們接着之前的第十四行,mFuture的初始化,將上面得到的mWorker對象傳遞進去,FutureTask類實現了RunnableFuture接口,通過這個接口可以方便的取消後臺任務以及獲取後臺任務的執行結果。接着是重寫done()方法。該方法裏面的postResultIfNotInvoked(get());
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
get()傳進來的是mWorker的call的返回值Result,第二行表示或者任務是否被調用。沒有的話執行postResult(result)。但是在mWorker初始化時就已經將mTaskInvoked爲true,所以一般這個postResult執行不到。
稍微梳理一下。構造方法中,當mWorder的call方法被執行,doInBackground就接開始執行,後臺任務執行完之後通過消息處理機制,將我們得到的結果發送給主線程進行之後的操作。上面的分析,
緊接着思路,我們看execute()的方法源碼。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
這裏可以看到execute()方法,攜帶的參數,就是我們doInBackground()到時用到的參,executeOnExecutor返回的類型跟自己本身一致,但是executeOnExecutor()的參數中有一個是sDefaultExecutor,那麼sDefaultExecutor是什麼呢?簡單的說,它是AsyncTask的默認執行器(線程池)。AsyncTask可以以串行(一個接一個的執行)或並行(一併執行)兩種方式來執行後臺任務,在Android3.0及以後的版本中,默認的執行方式是串行。這個sDefaultExecutor就代表了默認的串行執行器(線程池)。也就是說我們平常在AsyncTask對象上調用execute方法,使用的是串行方式來執行後臺任務。我們接着往下看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;
}
從if語句開始,判斷當前的狀態是否爲RUNNING或FINISHED時,就會拋出IllegalStateException異常。這個意思是我們要執行的異步狀態不能是正在執行和執行結束這兩個狀態。
到十五行,我們將異常狀態改成執行狀態,然後調用onPreExecute()方法。這裏我們之前就說,我們通常在
onPreExecute()做一些異步任務前所需要的做的準備動作。
到十九行,將我們傳進來的params參數賦值給了mWorker的mParams成員變量;隨後調用exec的execute()方法,其實方法帶mFuture參數。這裏mWorker和mFutrue,在我們上述AsyncTask的構造方法裏就介紹過一下,大家梳理一下。
我們繼續分析exec,exec就是我們之前方法的sDefaultExecutor。
那我們點一下這個sDefaultExecutor對象看看它是搞什麼的。
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);
}
}
}
第二行mTasks是SerialExecutor這個串行線程池的任務緩存隊列,第三行定義了一個Runnable變量mActive,他表示當前正執行的AsyncTask對象,之後判斷mActive是否爲null,若null就調用scheduleNext()方法。
我們先繼續,第六行用offer方法向任務緩存隊列中添加一個任務,任務的內容就如第七行到第十三行的run()方法。第九行調用的是mFuture對象的run()方法(一開始execute傳進來的參數就是mFuture),這run()方法裏會調用mWorker的call()方法,那麼就如構造方法那時說的,mWorker的call()方法裏面就會調用doInBackground方法,我們的後臺任務也就開始執行了(需要的翻上去看一下)。
接着剛剛說的scheduleNext()。在scheduleNext()方法中,若緩存隊列非空,則調用THREAD_POOL_EXECUTOR.execute方法執行從緩存隊列中取出的任務,這時我們的後臺任務便開始你真正執行了。
然而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);
看完線程池這裏,我們回顧一下SerialExecutor。例如我們有多個異步任務,調用execute(s synchronized)方法,第一個任務入隊,然後在mActive = mTasks.poll()) != null被取出,並且賦值給mActivte,然後交給線程池去執行。然後第二個任務入隊,但是此時mActive並不爲null,並不會執行scheduleNext();所以如果第一個任務比較慢,多個任務都會進入隊列等待;真正執行下一個任務的時機是,線程池執行完成第一個任務以後,調用Runnable中的finally代碼塊中的scheduleNext,所以雖然內部有一個線程池,其實調用的過程還是線性的。一個接着一個的執行,相當於單線程。
上面就是對AsyncTask的源碼分析,整體是先對AsyncTask的構造方法開始,然後到execute()。在分析execute()的過程中將onPreExecute()、doInBackground()、onPostExecute()串聯在一起分析。
AsyncTask的不足
我相信有一部分的同學可能更關心下面說的,因爲在面試的時候,面試官會先從Thread+Handler的切入,然後會問與AsyncTask的差異和缺點。針對的AsyncTask中線程池的源碼看出,corePoolSize爲CPU數加一,maximumPoolSize爲CPU數的二倍加一,存活時間爲1秒,任務緩存隊列爲LinkedBlockingQueue,128。所以當超過這個度之後就會拋出java.util.concurrent.RejectedExecutionException;
還有就是AsyncTask是單線程方式進行的,有一些公司對業務需求是同時進行的。
AsyncTask異步任務基本就分析到此,希望能讓大家對AsyncTask的認識加深一點點。我們也要培養一種看源碼分析的習慣。謝謝大家!
注參考資料: