最近找實習面試中經(jīng)常會被問到關于AsyncTask的一些內(nèi)部機制的問題,之前也早有學習,但是還不夠系統(tǒng),沒有形成一個體系,現(xiàn)在我們來完整的徹底的梳理一下吧,掃除一些知識盲點。
相信大多數(shù)Android開發(fā)者都接觸過AsyncTask這個類,AsyncTask主要是用來處理一些后臺的任務,說到底就是一個異步處理工具類,隨著Android開發(fā)配套的開源項目越來越完善越來越好,比如網(wǎng)絡請求時有了Volley配套OKHttp等非常好用的庫,但是在一些例如和UI做交互的情況下還是需要AsyncTask來幫忙的。
那么AsyncTask到底是什么呢,閱讀源碼我們可以發(fā)現(xiàn),AsyncTask就是一個Handler和線程池的封裝,線程池用來異步處理后臺任務,handler用來發(fā)送消息進行UI方面的交互。好,既然說到了源碼,那么我們先來看看AsyncTask的源碼。(基于API 23)
先看看AsyncTask的構造方法:
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
/**
* 創(chuàng)建一個新的異步任務,這個構造方法只能在主線程調(diào)用。
*/
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);
}
}
};
}
首先new一個mWorker,這是一個WorkerRunnable<Params,Result>,抽象類,實現(xiàn)了Callable的接口,本質(zhì)上就是一個Callable。mFuture是一個FutureTask對象。
可以看到在WorkerRunnable中最主要是做了三步,將代表任務是否調(diào)用的原子boolean標記設為true,將這個線程的優(yōu)先級設為后臺線程優(yōu)先級,并且丟出doInBackground處理的結(jié)果。
這個結(jié)果被丟到了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;
}
可以看到其實這個方法并沒有對丟過來的result做什么事,丟進來的result依然原封不動的丟了出去,只是在這個過程中捎帶實例化了一下AsyncTaskResult,將丟進來的這個result以及當前
這個AsyncTask一起又拋給了新初始化的AsyncTaskResult, 并且將向handler發(fā)送了一個Message。注意這里message的 what是MESSAGE_POST_RESULT, 這里是有用到的,我們后面再講。
好,我們繼續(xù)往下走,看看result丟進去的AsyncTaskResult。
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
可以看到AsyncTaskResult只是一個static的內(nèi)部類,只是起了一個保存當前的AsyncTask對象和后臺處理的result data的作用。我們再繼續(xù)往下看看這里保存的result和task在哪里使用到了。之前
已經(jīng)提到了AsyncTask本質(zhì)上就是一個線程池和Handler的封裝,現(xiàn)在我們都已經(jīng)獲得了后臺處理的結(jié)果了,消息也發(fā)送了,那么我們跟著result來看看這個重要組成部分吧。
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;
}
}
}
這里就非常清楚了,之前也講過在post中發(fā)送的消息的what內(nèi)容是MESSAGE_POST_RESULT, 我們看看handler中,result也是消息中傳遞過來的AsyncTaskResult,里面保存了當前的task和處理的結(jié)果,
進入我們預想的case,通過對應的task,處理AsyncTaskResult中的data, 這里是走到了AsyncTask的finish方法。走到這兒可能有點遠了,但是不怕,我們繼續(xù)往下看,其實這份代碼寫的還是很有條理的。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
注意,這里的finish方法是一個private,只有當前的task能調(diào)用,一定程度上保證了安全性。它通過對isCancelled的判斷可以進入不同的結(jié)果處理流程,實際上onCancelled和onPostExecute就是我們平常使用AsyncTask經(jīng)常使用的兩個方法,到這一步就沒什么好說了,task攜帶著之前一步步傳過來的doInBackground的結(jié)果,來到了我們最熟悉的幾個方法了。只是我們需要留意一下Status這個環(huán)節(jié)。
mStatus是一個volatile的Status對象,而Status是一個枚舉類,只包括了
- PENDING, 等待狀態(tài),表明當前這個task還沒有執(zhí)行。
- RUNNING, 運行狀態(tài),當前task正在執(zhí)行中。
- FINISHED, 結(jié)束狀態(tài),當前task已經(jīng)運行結(jié)束了。
而mStatus在這個task初始時就直接設為了PENDING, 并且只有在finish方法中改變?yōu)镕INISHED, 這已經(jīng)是最后的環(huán)節(jié)了。那么是在哪里變?yōu)镽UNNING呢,我們現(xiàn)在就來看看整個AsyncTask最核心的部分,線程池這一塊。
看完了從task構造方法一路跟隨到最后finish,我們現(xiàn)在再跟著我們平時的用法來一起學習線程池一部分。事實上我們在使用時,每次初始化后,會使用execute這個方法來讓task跑起來。我們看看這里是發(fā)生了什么事。
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
代碼很簡單,主要就是攜帶參數(shù),在主線程中啟動起來。注意,這里只能是在UI線程也就是主線程中完成這一步,這也是AsyncTask一個比較大的軟肋。
事實上task是通過一個隊列完成的。別急,好飯不怕晚,我們先看看這里return 的executeOnExecutor方法干了什么。
@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;
}
這里是不是就和之前一直到finish的分析連接上了呢。這里執(zhí)行task時,講道理的話到這一步時當前的task一定還是PENDING狀態(tài),所以這里會先檢查mStatus,如果不是PENDING就會拋出異常,否則就正常執(zhí)行,把mStatus改為RUNNING狀態(tài)。然后就到了我們也非常熟悉的方法了,onPreExecute,這里一般做一些后臺任務之前的事情。
終于到了重點了,完成了準備工作后,注意exec.execute(mFuture)這一步,這個exec是什么? 一個Executor,Executor只是一個接口,定義了一個execute的方法,這個exec是在上一步execute時傳入的全局sDefaultExecutor。sDefaultExecutor是一個默認線程池。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
可以看到,sDefaultExecutor實際上是一個SerialExecutor. 但是這里為什么要分開寫呢。可以發(fā)現(xiàn)SERIAL_EXECUTOR是一個final對象,也就是說sDefaultExecutor默認是使用系統(tǒng)初始化好的SerialExecutor,但是我們也可以手動給這個Task設置一個Executor。
/** @hide */
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}
這樣就清楚了,其實留給開發(fā)者的余地還是很大的。在這里還是只研究默認的Executor吧。一樣,我們看看源碼。
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);
}
}
}
哈,可以看到這里的excute方法就是我們前面分析每次提到的excute,到最后就都到了這里。首先我們可以看到在SerialExecutor中維護了一個ArrayDeque, mTasks。還記得之前在executeOnExecutor中看到的exec.execute(mFuture)方法嗎?mFuture是一個FutureTask,實現(xiàn)了Future和Runnable,這里就將丟過來的mFutuer封裝成了一個Runnable對象,然后在把這個Runnable添加到隊列。
接著包含一個scheduleNext() 方法。scheduleNext是先從mTasks隊列中取出隊首的一個Runnable任務叫做mActive,如果這個Runnable不為空,就將其添加到真正的線程池THREAD_POOL_EXECUTOR之中執(zhí)行。
需要指出的是,由于第一次execute時,在將Runnable加進隊列后,mActive初始化為null,所以會默認走進scheduleNext,這樣也保證了一開始的自動啟動。
以后的任務就是在try{}finally{}中可以看到,每個Runnable運行完后就進入finally執(zhí)行隊列中的下一個任務。
我們已經(jīng)發(fā)現(xiàn)了這里的每個Runnable都是在THREAD_POOL_EXECUTOR中完成的。這是什么?一個ThreadPoolExecutor. 我們先看看一個ThreadPoolExecutor的構造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
我們看看依次需要的參數(shù):
- corePoolSize 核心線程池大小,也就是線程池中可以維持的線程數(shù)目,即使這些線城是空閑的,也不會終止(除非設置了allowCoreThreadTimeOut),因此可以理解為常駐線程池的線程數(shù)目。
- maximumPoolSize 線程池中允許的最大線程數(shù)目。因為一般來說線程數(shù)目越多,調(diào)度所用的花銷越大,所以需要設置一個數(shù)目上限。
- keepAliveTime 當線程數(shù)目大于核心線程數(shù)目時,如果超過這個keepAliveTime時間,那么空閑的線程會被終止。
- unit keepAliveTime的時間單位。
- workQueue 一個保存尚未執(zhí)行的線程的隊列。這個隊列只保存由execute方法提交的Runnable任務。
- threadFactory 用來構造線程池的工廠。
我們再看看ThreadPoolExecutor的execute方法。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//獲取ctl的int值。這個int值保存了線程池中任務數(shù)目和線程池的狀態(tài)等信息
int c = ctl.get();
// 當線程池中的任務數(shù)量 < 核心線程數(shù)目時,通過addWorker(command, true)新建一個線程,并將任務(command)添加到該線程中,然后啟動該線程來執(zhí)行任務。
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
我們可以看一下這個線程池調(diào)度的大致流程:
- 如果線程池中的線程數(shù)目小于corePoolSize,那么就新啟動一個線程,并且將這個Runnable作為這個新線程的第一個任務添加到線程中進行執(zhí)行
- 如果線程池中的線程數(shù)目大于等于corePoolSize,就將任務添加到workQueue隊列中等待。這種情況下,會兩次確認線程池的狀態(tài),如果第2次讀到的線程池狀態(tài)和第1次讀到的線程池狀態(tài)不同,就從隊列中刪除該任務。
- 如果這個隊列已經(jīng)滿了,就在線程池中新建一個線程,并將該任務添加到線程中進行執(zhí)行。如果執(zhí)行失敗,就通過reject()拒絕該任務。
總之可以發(fā)現(xiàn),線程池ThreadPoolExecutor通過workQueue來管理線程和任務,每個線程在啟動后,會執(zhí)行線程池中的任務;當一個任務執(zhí)行結(jié)束后,它會從線程池workQueue中取出任務來繼續(xù)執(zhí)行。workQueue是管理線程池任務的隊列,當添加到線程池中的任務超過線程池的最大線程數(shù)目時,這個任務就會進入阻塞隊列進行等待。
好,我們現(xiàn)在來看看android中的THREAD_POOL_EXECUTOR是怎么走的。
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
我們看看傳進去的參數(shù):
//cpu的數(shù)量,Runtime獲取
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心線程數(shù)目
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//最大線程數(shù)目
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
//工作隊列,一個阻塞隊列,用來保存任務。
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
//創(chuàng)建新線程的一個工廠方法
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());
}
};
可以看到,在構建THREAD_POOL_EXECUTOR時是基于android平臺的特點來的。核心線程數(shù)是設為了CPU_COUNT+1。
但是實際上在之前的android API版本中,這些值是直接設為了固定的值。
/*4.3 版本的AsyncTask源碼 */
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread More ...newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
從4.4版本改成了目前的配置。另外可以看到,4.4之前的sPoolWorkQueue的大小也只有10。這些配置的更改給AsyncTask的動態(tài)調(diào)度還是帶來了優(yōu)勢的。比如在4.3中,核心線程數(shù)目只有5,因此如果開了5個線程后,再繼續(xù)開的話就只能等待了。
這是對線程池的初始化和調(diào)度分析了,之后就是線程的執(zhí)行,并且接著我們最初的流程,最后走到task的finish方法,需要我們在AsyncTask實現(xiàn)時完成onPostExecute就可以了。關于Future部分的分析,我們以后再講。
實際上,面試時面試官會經(jīng)常問一個問題就是AsyncTask是可以并行的嗎?
經(jīng)過我們上面的分析,已經(jīng)可以確定AsyncTask內(nèi)部有一個線程池來進行線程的調(diào)度管理以及執(zhí)行。那么AsyncTask是可以并行執(zhí)行的嗎?先賣個關子,看看2.3.7的AsyncTask部分的代碼:
public final AsyncTask<Params, Progress, Result> More ...execute(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();
Worker.mParams = params;
sExecutor.execute(mFuture);
return this;
}
看到?jīng)],在2.3.7上execute(Params... params)這個方法和6.0上有什么區(qū)別?
我們在之前已經(jīng)分析過了,execute(params)是走到了 return executeOnExecutor(sDefaultExecutor, params);
而sDefaultExecutor是一個volatile的對象。
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
我們可以得出結(jié)論,在6.0上不是并行的,在2.3上是并行的。實際上閱讀多個版本的源碼可以發(fā)現(xiàn),這一變化是在3.0上做到的。因此我們可以理解為從3.0之后就變成串行了,1.5-2.3是并行的。最初也是串行的。
事實上,也可以自己寫demo測試一下,看看每個task的執(zhí)行的時間是不是同時開始。
那么面試官又會問了,為什么在目前的版本上要將AsyncTask設計成串行呢?
揣摩設計者的意圖其實真的是一個很有意思的事情啊- -23333
我們已經(jīng)提到了AsyncTask是線程池和Handler的一個封裝好的工具,其實做到的功能在handler message和loop也能做到,那么為什么要設計一個這樣的工具呢,主要還是方便開發(fā)者的使用,尤其是初級開發(fā)者。
而可能隨著android版本的迭代開發(fā),發(fā)現(xiàn)有開發(fā)者很少在doInBackground中做線程安全的考慮,既然很少有人會考慮最資源的并發(fā)訪問的安全性,那么干脆就不開放這個功能,保證每個線程的串行執(zhí)行。這樣就是皆大歡喜。
好了,對AsyncTask的分析就到這兒了,大家應該清楚整個的流程以及其特點了。但是可能大家也看到一些大牛說了,并不推薦使用默認的AsyncTask,因為實在有一些不能忽視的缺點啦- -
首先,默認的AsyncTask的線程池中的核心線程數(shù)是有限的,不管是和CPU數(shù)目有關還是以前的固定的5, 還是有一個數(shù)量的限制,因此不適合大量的后臺任務處理,例如瀑布流圖片的加載等。
其次,AsyncTask類必須在主線程初始化,必須在主線程創(chuàng)建,因為return executeOnExecutor(sDefaultExecutor, params)這里也只能在UI線程走。
最后,我們也講到了,AsyncTask在3.0后改成了串行的,因此想真正做一些并行的后臺任務,就不太適合了。
總之,大家想要更好的使用AsyncTask,最好自己修改一下再使用啦。目前默認的AsyncTask確實還不是最佳狀態(tài)。