相信有很多同學(xué)都使用過AsyncTask,都知道onPreExecute,onProgressUpdate,onPostExecute方法均運行在主線程腮出,doInBackground方法運行在子線程,可以在其中做一些耗時操作,在doInBackground方法中也可以調(diào)用publishProgress方法來更新UI(之后會調(diào)用到onProgressUpdate方法)芙沥。不知道大家有沒有好奇過,為什么onPreExecute,onProgressUpdate,onPostExecute方法會運行在主線程呢?為什么doInBackground方法會運行在子線程呢?子線程調(diào)用publishProgress之后為什么能進(jìn)而調(diào)用到主線程的onProgressUpdate宿亡?AsyncTask的使用真的有想象中那么安全嗎?
不用多想纳令,上面的幾個問題挽荠,只有源碼君才能告訴我們答案,所以平绩,今天就帶領(lǐng)大家從底層源碼的角度深入剖析AsyncTask的內(nèi)部實現(xiàn)機制圈匆,讓大家對AsyncTask的工作原理有一個透徹的理解,大家是不是有點小期待了呢_
隨著Android版本的變遷捏雌,AsyncTask在任務(wù)執(zhí)行方面有著較大的差異跃赚。當(dāng)一開始推出時,諸多任務(wù)是在一個單個的后臺線程上串行執(zhí)行的性湿。從Android 1.6(API 4)開始,任務(wù)是在一個線程池中并發(fā)執(zhí)行的纬傲。從Android3.0(API 11)開始,任務(wù)又變?yōu)樵谝粋€單個的線程上串行執(zhí)行窘奏。本篇文章基于Android 4.1.2的源碼進(jìn)行分析嘹锁。
首先來看AsyncTask的構(gòu)造方法葫录,代碼如下:
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
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 occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
AsyncTask的構(gòu)造方法中做的事情不多着裹,就是初始化了兩個成員變量,一個是WorkerRunnable類型的mWorker(WorkerRunnable實現(xiàn)了Callable接口),一個是FutureTask類型的mFuture米同,并且將mWorker作為構(gòu)造參數(shù)傳入mFuture中(對Callable,Future,FutureTask不太了解的同學(xué)請先移步至http://www.cnblogs.com/dolphin0520/p/3949310.html骇扇。
接下來我們?nèi)タ碅syncTask的execute方法:
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>Note: this function schedules the task on a queue for a single background
* thread or pool of threads depending on the platform version. When first
* introduced, AsyncTasks were executed serially on a single background thread.
* Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
* executed on a single thread to avoid common application errors caused
* by parallel execution. If you truly want parallel execution, you can use
* the {@link #executeOnExecutor} version of this method
* with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
* on its use.
*
* <p>This method must be invoked on the UI thread.
*
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
* @see #execute(Runnable)
*/
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
execute方法竟然如此簡單,僅僅是去調(diào)用了executeOnExecutor方法面粮,我們趕緊去看一下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;
}
在executeOnExecutor中少孝,會先去判斷當(dāng)前AsyncTask的狀態(tài),如果不為PENDING熬苍,則拋異常稍走。如果是PENDING袁翁,則將當(dāng)前狀態(tài)變?yōu)镽UNNING,再去調(diào)用onPreExecute方法婿脸,由于此時的executeOnExecutor是運行在主線程的,所以onPreExecute方法也是運行在主線程的粱胜。之后再去對mWorker的mParams字段進(jìn)行賦值并利用exec去執(zhí)行mFuture。
我們基本可以推斷出狐树,任務(wù)執(zhí)行的邏輯應(yīng)該是在** exec.execute(mFuture)中焙压,觀察一下exec所對應(yīng)的實參,是一個名為sDefaultExecutor**的Executor抑钟。這個sDefaultExecutor具體又是個什么樣的Executor呢涯曲?看下面兩行代碼:
/**
* An {@link Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
我們發(fā)現(xiàn),sDefaultExecutor其實就是一個SerialExecutor類型的常量在塔,根據(jù)注釋幻件,SerialExecutor在執(zhí)行任務(wù)時,會以一個串行的順序蛔溃,一次僅執(zhí)行一個任務(wù)傲武。由于SERIAL_EXECUTOR是一個常量,所以在一個特定的進(jìn)程之內(nèi)城榛,串行化是具有全局效果的揪利。
我們趕緊去看一下SerialExecutor的源代碼,重點關(guān)注它是如何將任務(wù)的執(zhí)行變成串行化的:
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中有兩個很重要的成員變量狠持,一個是ArrayDeque<Runnable>類型的mTasks疟位,它是一個雙端隊列,存儲了我們要執(zhí)行的任務(wù)喘垂。還有一個是Runnable類型的mActive甜刻,初始值為null。
接下來就到了最為關(guān)鍵的execute方法了正勒。execute方法接收的參數(shù)是一個Runnable對象得院,到這里,有的同學(xué)可能感覺到有點奇怪章贞,在executeOnExecutor方法中祥绞,我們的Executor的execute方法接收的是一個FutureTask的對象,為什么兩個execute方法接收的參數(shù)不匹配呢鸭限?其實是這樣的蜕径,F(xiàn)utureTask本身就實現(xiàn)了RunnableFuture接口,RunnableFuture接口又繼承了Runnable, Future接口败京,所以兜喻,F(xiàn)utureTask完全可以當(dāng)作一個Runnable來用。我們繼續(xù)去看SerialExecutor的execute方法赡麦,首先朴皆,新建了一個Runnable任務(wù)帕识,在其run方法中有一個try,finally結(jié)構(gòu),try中直接去調(diào)用了傳入的Runnable對象的run方法,finally中調(diào)用了scheduleNext方法遂铡。之后將這個新建立的Runnable任務(wù)放入雙端隊列的尾部渡冻。接下來會去判斷mActive這個變量是否為null,若為null,執(zhí)行scheduleNext方法忧便。第一個任務(wù)到來時mActive肯定是null族吻,所以肯定會去執(zhí)行scheduleNext方法。我們再去看一下scheduleNext方法珠增,它會從雙端隊列的隊頭取出一個元素超歌,賦給mActive變量,如果此時的mActive變量不為null蒂教,則利用線程池THREAD_POOL_EXECUTOR來執(zhí)行這個任務(wù)巍举。
想象一下,如果在第一個任務(wù)執(zhí)行的過程中凝垛,又來了第二個任務(wù)懊悯,會發(fā)生什么事情呢?首先梦皮,依然是對我們傳入的Runnable任務(wù)進(jìn)行重新封裝炭分,入隊,之后會再去判斷mActive是否為null,此時剑肯,mActive是不為null的捧毛,所以不會再去執(zhí)行scheduleNext方法。那我們第二個任務(wù)就永遠(yuǎn)得不到執(zhí)行了嗎让网?其實不是的呀忧,我們回到之前的try,finally結(jié)構(gòu),我們發(fā)現(xiàn)溃睹,當(dāng)try中的任務(wù)邏輯執(zhí)行完成之后而账,會在finally中調(diào)用scheduleNext方法,也就是說因篇,當(dāng)我們第一個任務(wù)執(zhí)行完成之后泞辐,會再去調(diào)用scheduleNext方法,在scheduleNext方法中惜犀,會從雙端隊列中取出第二個任務(wù)铛碑,交給線程池去執(zhí)行狠裹,由此虽界,任務(wù)的執(zhí)行變成串行化了。
我們再回過頭看一下SerialExecutor的execute方法涛菠,看到r.run()這一句莉御。我們知道撇吞,execute方法的參數(shù)表面上是一個Runnable對象,實際上我們傳遞給它的是一個FutureTask對象礁叔,那么r.run()自然也是執(zhí)行的FutureTask對象的run方法牍颈。FutureTask對象的run方法會去調(diào)用Sync內(nèi)部類的innerRun方法,我們來看一下Sync內(nèi)部類的innerRun方法:
void innerRun() {
if (!compareAndSetState(READY, RUNNING))
return;
runner = Thread.currentThread();
if (getState() == RUNNING) { // recheck after setting thread
V result;
try {
result = callable.call();
} catch (Throwable ex) {
setException(ex);
return;
}
set(result);
} else {
releaseShared(0); // cancel
}
}
在Sync內(nèi)部類的innerRun方法中琅关,會去調(diào)用callable的call方法煮岁。這個callable是什么呢?其實就是我們在AsyncTask構(gòu)造方法中初始化的mWorker變量涣易,我們再回顧一下mWorker的初始化代碼:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
注意画机,此時的call方法是在子線程運行的。我們看到return postResult(doInBackground(mParams))這一句新症,終于步氏,我們發(fā)現(xiàn)了doInBackground方法,由于當(dāng)前的call方法是在子線程運行的徒爹,所以doInBackground方法也是在子線程運行的荚醒。
我們繼續(xù)去看一下postResult方法:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
大家有沒有感覺到這段代碼很熟悉呢?沒錯隆嗅,這就是我們Android的異步消息處理機制界阁。首先,利用sHandler去獲取一條消息胖喳,消息的what字段是MESSAGE_POST_RESULT铺董,消息的obj字段是new AsyncTaskResult<Result>(this, result),AsyncTaskResult中封裝了當(dāng)前的AsyncTask任務(wù)以及需要傳遞的數(shù)據(jù)禀晓。之后調(diào)用message的sendToTarget方法將這條消息發(fā)送給sHandler精续。
我們看下sHandler的定義:
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
@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是一個InternalHandler類型的常量粹懒。由于我們的AsyncTask只能在主線程初始化重付,所以sHandler在初始化時用的是主線程的Looper,其handleMessage方法自然也是運行在主線程的凫乖。在handleMessage方法中确垫,首先取出消息的obj字段并強轉(zhuǎn)為AsyncTaskResult類型,之后會去判斷消息的what字段帽芽,如果是MESSAGE_POST_RESULT删掀,則執(zhí)行result.mTask.finish(result.mData[0])。其中result.mTask代表當(dāng)前的AsyncTask對象导街,result.mData[0]代表doInBackground(mParams)的執(zhí)行結(jié)果披泪,我們繼續(xù)去看一下AsyncTask的finish方法:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
finish方法的邏輯很簡單,如果我們的任務(wù)已經(jīng)通過調(diào)用cancel(boolean)方法取消了搬瑰,那么會去執(zhí)行onCancelled(result)方法款票,否則執(zhí)行onPostExecute(result)方法控硼。最后,會將當(dāng)前的AsyncTask對象的狀態(tài)置為FINISHED艾少。由于之前的handleMessage方法是運行在主線程的卡乾,所以finish方法也是運行在主線程的,finish方法中的 onCancelled(result)缚够,onPostExecute(result)方法自然也是運行在主線程的幔妨。
在handleMessage中,還有一種what字段為MESSAGE_POST_PROGRESS的消息谍椅,那么什么時候會收到這種類型的消息呢陶冷,猜一下也知道,應(yīng)該是我們調(diào)用publishProgress的時候毯辅。我們?nèi)タ匆幌聀ublishProgress的源代碼:
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
當(dāng)任務(wù)尚未取消時埂伦,會向sHandler發(fā)送一條what字段為MESSAGE_POST_PROGRESS,obj字段為new AsyncTaskResult<Progress>(this, values)的消息思恐≌疵眨回到handleMessage方法,因為handleMessage方法是運行在主線程中的胀莹,所以onProgressUpdate方法也是運行在主線程中的基跑。
好了,AsyncTask的源碼到這里已經(jīng)基本分析完畢了描焰,下面向大家介紹的是AsyncTask使用的缺陷問題媳否。
可能有同學(xué)會想,AsyncTask這么牛X的一個工具荆秦,能有啥缺陷篱竭?
其實,AsyncTask還真的有缺陷步绸,而且掺逼,這個缺陷和Android的版本息息相關(guān),在某些Android版本下瓤介,如果AsyncTask使用不慎吕喘,甚至有可能造成我們的應(yīng)用程序崩潰。
前面我們曾經(jīng)提及刑桑,從Android 1.6(API 4)開始,任務(wù)是在一個線程池中并發(fā)執(zhí)行的氯质。從Android3.0(API 11)開始,任務(wù)又變?yōu)樵谝粋€單個的線程上串行執(zhí)行祠斧。問題就出在這個并發(fā)執(zhí)行上闻察,這個并發(fā)執(zhí)行所使用的線程池,最大支持128個任務(wù)的并發(fā),10個任務(wù)的等待蜓陌。也就是說觅彰,同時執(zhí)行138個任務(wù)是沒問題的吩蔑,但是同時執(zhí)行139個任務(wù)則會拋出異常:java.util.concurrent.RejectedExecutionException钮热。而在Android 1.6之下,Android3.0及其之上的版本中烛芬,所有的任務(wù)都是串行執(zhí)行的隧期,同時執(zhí)行再多的任務(wù)都不會有問題。
看到這里赘娄,相信大家已經(jīng)對AsyncTask的底層原理有了一個較為深入的理解了,想不到小小的AsyncTask的內(nèi)部竟然隱藏著如此美妙的天地仆潮,著實值得我們?nèi)ヌ剿髋c回味啊~~~
參考:
http://blog.csdn.net/lmj623565791/article/details/38614699
http://blog.csdn.net/guolin_blog/article/details/11711405
http://www.cnblogs.com/dolphin0520/p/3949310.html