AsyncTask是Android提供的一個輕量級非同步任務機制,使用AsyncTask可以方便的執行非同步任務,並將結果更新到main thread。AsyncTask中是通過Handler機制來讓work thread和main thread通訊的。
在這篇文章中我們將瞭解AsyncTask的基本用法以及從原始碼的角度來分析AsyncTask機制,首先我們來了解下開發過程中AsyncTask的使用
AsyncTask類的基本方法:
public abstract class AsyncTask<Params, Progress, Result> {
/**
* Override this method to perform a computation on a background thread. The
* specified parameters are the parameters passed to {@link #execute}
* by the caller of this task.
*
* This method can call {@link #publishProgress} to publish updates
* on the UI thread.
*
* @param params The parameters of the task.
*
* @return A result, defined by the subclass of this task.
*
* @see #onPreExecute()
* @see #onPostExecute
* @see #publishProgress
*/
@WorkerThread
protected abstract Result doInBackground(Params... params);
/**
* Runs on the UI thread before {@link #doInBackground}.
*
* @see #onPostExecute
* @see #doInBackground
*/
@MainThread
protected void onPreExecute() {
}
/**
* <p>Runs on the UI thread after {@link #doInBackground}. The
* specified result is the value returned by {@link #doInBackground}.</p>
*
* <p>This method won't be invoked if the task was cancelled.</p>
*
* @param result The result of the operation computed by {@link #doInBackground}.
*
* @see #onPreExecute
* @see #doInBackground
* @see #onCancelled(Object)
*/
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onPostExecute(Result result) {
}
/**
* Runs on the UI thread after {@link #publishProgress} is invoked.
* The specified values are the values passed to {@link #publishProgress}.
*
* @param values The values indicating progress.
*
* @see #publishProgress
* @see #doInBackground
*/
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values) {
}
}複製程式碼
從上面AsyncTask的程式碼可以看到,AsyncTask是一個抽象類,我們如果想要使用的話必須先建立它的子類,並實現它的抽象方法doInBackground,根據功能需要我們還可以重寫onPreExecute、onPostExecute、onProgressUpdate。從註釋我們就可以知道onPreExecute、onPostExecute、onProgressUpdate這幾個方法是執行在main thread,doInBackground是執行在workthread中的。我們把耗時的操作放在doInBackground進行,當doInBackground執行完之後會把結果返回給onPostExecute,我們可以在onPostExecute做一些更新UI的操作。onPreExecute在doInBackground之前執行,用於執行準備工作,onProgressUpdate用來更新後臺任務的執行進度。
AsyncTask有三個泛型引數,分別是Params, Progress, Result。Params是指後臺任務執行的引數,用於在doInBackground使用,Progress是用來指示後臺任務執行進度的單位,Result是後臺任務的返回結果,在doInBackground方法中返回,交給onPostExecute處理。這些引數不需要時可以用Void代替。
在本文中我們自定義一個簡單的AsyncTask類,程式碼定義如下:
public class TestAsyncTask extends AsyncTask<Void, Integer, Void> {
private String taskName;
public TestAsyncTask(String name) {
super();
taskName = name;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Log.v("stone", "task = " + taskName + " onPostExecute in " + Thread.currentThread().toString());
}
@Override
protected Void doInBackground(Void... params) {
Log.v("stone", "task = " + taskName + " doInBackground in " + Thread.currentThread().toString());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.toString();
}
return null;
}
}
//AsyncTask的兩種啟動方式
new TestAsyncTask("task_" + i).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new TestAsyncTask("tasktesk_" + i).execute();複製程式碼
到目前為止我我們已經大致瞭解了AsyncTask的使用,以及啟動後臺任務的兩種方式,execute和executeOnExecutor。在executeOnExecutor中我們使用了AsyncTask自定義的THREAD_POOL_EXECUTOR,它是一個ThreadPoolExecutor。這兩種後臺任務的啟動方式有什麼區別呢?一個應用中最多可以new多個AsyncTask呢?多個任務是並行處理還是序列處理呢?最多同時有多少個任務在處理呢?下面我們將從原始碼的角度來回答上面的問題,由於AsyncTask從出現到現在已經有了多次改動,下面我的分析將是基於API 23的原始碼來進行的,請各位注意不同API版本的AsyncTask實現是有差異的。
#AsyncTask機制詳解#
通過上文我們已經知道了AsyncTask的簡單用法,下面我將從原始碼的角度來分析,一個AsyncTask從建立到執行的過程。首先一個任何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);
}
}
};
}複製程式碼
程式碼很簡單,初始化了mWorker 和mFuture 這兩個物件,我們暫時先不管立面的具體實現,只要知道這個mFuture 會交給Executor去執行。現在AsyncTask物件已經建立好了,我們來看AsyncTask的執行過程。我們先來分析execute()方法,該方法的定義如下:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}複製程式碼
execute的程式碼實際上直接呼叫了executeOnExecutor方法,並且傳入了一個叫做sDefaultExecutor的Executor物件。接著我們來看下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中,我們先呼叫了onPreExecute()方法,這就解釋了為什麼onPreExecute方法是最新被呼叫的。我們把執行引數params賦給mWorker物件,mWorker實現了callable介面。把mFuture交給我們傳入的Executor來執行。對面我們前文提到了AsyncTask的兩種啟動方式,他們執行方式的不同是因為傳入的Executor不同導致的。我們前面提到過execute方法傳給executeOnExecutor的是一個sDefaultExecutor物件,通過原始碼來看一下sDefaultExecutor是什麼:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;複製程式碼
我們看到sDefaultExecutor 被賦值為SERIAL_EXECUTOR,我們來看一下SERIAL_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();複製程式碼
SERIAL_EXECUTOR 是一個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);
}
}
}複製程式碼
到這裡我們可以知道,通過execute啟動AsyncTask任務,會把任務一個一個新增到mTasks中,在執行完一個任務後才會去執行下一個任務,mTasks的大小沒有限制,所以理論上通過execute啟動AsyncTask任務這種方式可以建立無數個task,並且所有的task是序列執行的。這個runnable引數就是我們在AsyncTask建構函式中初始化的mFuture。我們接著再來看一下建構函式的原始碼:
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);
}
}
};
}複製程式碼
我們看到在初始化mWorker 物件時,在它的call方法裡面呼叫了doInBackground方法,執行完doInBackground之後會呼叫postResult(result)來傳遞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;
}複製程式碼
postResult方法中主要是生成了一個MESSAGE_POST_RESULT訊息,並將訊息傳送給了target handler。現在我們就要來看看這個Handler物件是什麼,就知道是把訊息傳送到了哪個執行緒中。我們來看一下getHandler()方法的實現:
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}複製程式碼
看到這裡,我們知道這個handler是一個InternalHandler物件,我們接著來看一下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;
}
}
}複製程式碼
看到這裡已經很清楚,這個handler是繫結了主執行緒loop的handler,所以接下來的工作就從工作執行緒切換到主執行緒中去執行。在handleMessage中可以看到MESSAGE_POST_RESULT會導致呼叫result.mTask.finish(result.mData[0])。這個就是AsyncTask的finish方法,我們來看一下finish方法的實現:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}複製程式碼
finish方法裡面會做判斷,如果 isCancelled()返回的是true,就會去執行onCancelled,否則就會呼叫onPostExecute方法。至此我們就走完了execute方式的啟動流程。通過以上的分析,我們可以知道這種方式下AsyncTask的物件可以建立任意多個並執行,且是順序執行的。
對於executeOnExecutor啟動方式,他的執行方式和傳入的Executor相關,每個任務的執行邏輯和execute啟動方式是一樣的。executeOnExecutor方式中我們傳入的引數是AsyncTask.THREAD_POOL_EXECUTOR,這是AsyncTask預設幫我們配置的Executor。我們來具體看下這個Executor的配置情況:
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);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);複製程式碼
這個ThreadPoolExecutor配置如下:核心執行緒數為CPU核個數+1,最大執行緒數是2倍的CPU核個數+1,任務排隊佇列大小為128,所以假設我們的手機CPU核的個數為8,在核心執行緒數為9,最大執行緒數為17。所以根據ThreadPoolExecutor的特點我們可以知道,最大的任務數為128+17,超過這個數量就會丟擲RejectedExecutionException。在排隊佇列未滿之前,最多有9個執行緒在執行,當排隊佇列滿了之後,最多有17個執行緒在執行。