AsyncTask源碼分析
概述
AsyncTask是安卓API裏提供的一個多線程操作工具類,能夠靈活方便地從子線程切換到UI線程。相信包括我在內的很多開發者在平常的項目種都有用到這個多線程工具,筆者在使用過程中就遇到一些問題,所以接下來會簡單的描述其使用,然後分析其源碼邏輯,解決自己遇到的疑問。
基本使用
一般使用AsyncTas如下:
private class MyAdAysncTask extends AsyncTask<Uri, Integer, Boolean> {
// 構造方法傳參
private MyAdAysncTask(Object Params) {
}
@Override
protected Boolean doInBackground(Uri... uris) {
try {
while (true) {
int downloadPercent = Downloader.downloadFile(uri);
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onProgressUpdate(Integer... values) {
//顯示下載進度
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Boolean adBean) {
super.onPostExecute(adBean);
}
}
// 執行任務
new MyAdAysncTask("傳入需要的參數").execute();
查看AsyncTask源碼可以知道AsyncTask是一個抽象類,當我們要使用它的時候,需要創建一個子類去繼承它,然後實現相應的方法。從上面的實現可以看到,AsyncTask有三個泛型參數如下:
- Params:該參數爲AsyncTask執行時要傳入的參數,可用於後臺任務執行時使用;
- Progress:該參數爲子線程任務執行時發佈的當前進度參數,用於進度顯示;
- Result:該參數爲子線程任務執行完時返回的結果類型。
跟進上面的參數,下面介紹AsyncTask中主要的方法。
- onPreExecute()
該方法在子線程任務執行前調用,一般做一些界面初始化操作。 - doInBackground(Uri… uris)
該方法爲子線程任務方法,運行在子線程中,用以處理耗時操作,可以接收Params參數,在任務完成後可以返回結果,如果返回參數爲Void則不返回,另外在該方法執行時,可以調用publishProgress(Integer… values)方法來發布當前處理進度,以便ui顯示。 - onProgressUpdate(Integer… values)
顧名思義就是用來更新進度的方法,在doInBackground方法中如果調用publishProgress方法則可以在這個方法內進行進度顯示。 - onPostExecute(Boolean adBean)
當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被調用,主要是做一個任務的收尾工作。
有了AsyncTask後,在子線程與UI線程之間的切換我們就不用在大費周章的使用Handler與Thread,只需簡單的調用即可實現。
源碼分析
爲了更好的理解AsyncTask的使用,下面我們來了解下源碼。
首先看下構造函數。
/**
* 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);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return 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);
}
}
};
}
在構造函數中主要創建了兩個對象,工作線程對象與任務調度棧,可以看到工作線程中調用了doInBackground()方法,因此doInBackground()在子線程執行,但當execute未執行時doInBackground()是還沒開始調用的,關於它們的作用後續會介紹。
當我們使用AsyncTask時會一般使用execute方法來執行,下面我們來看看它的執行流程:
@MainThread
// 主線程執行
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
@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;
}
從源碼可以看到execute()方法至在主線程執行的,在執行execute()方法後會首先調用onPreExecute方法,所以可以在這個方法裏做一些界面初始化的工作。在這個方法執行之後,執行了關鍵代碼exec.execute(mFuture),從字面上看,是通過Executor對象實例執行了一個任務,通過上面的代碼可以看到,這個Executor對象是sDefaultExecutor,它的創建如下:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// 創建Executor實例
public static final Executor SERIAL_EXECUTOR = new 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);
}
}
}
很明顯,當exec.execute(mFuture)執行了,其實就是把mFuture拋給了SERIAL_EXECUTOR來執行,然後調用mFuture中的run()方法。而在上面也介紹過,mFuture是在AsyncTask構造方法中被創建的FutureTask對象,下面我直接看下FutureTask中的run()方法:
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
// 這裏的call其實是初始化時創建的WorkerRunnable對象
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 調用WorkerRunnable的call方法
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
根據上面源碼描述,FutureTask中的run()方法會調用到WorkerRunnable對象的call()方法,而在上面的構造函數說明時已經闡述過WorkerRunnable對象創建過程,下面來重新看下call()方法的實現:
// 工作線程,即子線程
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
注:此時線程已經切換到子線程
到這裏,我們終於看到了doInBackground()的調用,這也可以證明了該方法是在子線程運行的,因此我們可以做一些耗時操作。在這個方法執行完後會返回AsyncTask創建時的第三個參數對象,然後執行postResult(result),我們繼續來看下postResult(result)的實現:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
// 將result投遞到handler所在的線程
message.sendToTarget();
return result;
}
在postResult()的主要工作就是把結果投遞到getHandler()獲取到的Handler對象所在的線程,按AsyncTask的工作原理,這個線程肯定是主線程,下面來看下這個Handler對象獲取過程:
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
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;
}
}
}
private void finish(Result result) {
// 任務是否取消
if (isCancelled()) {
onCancelled(result);
} else {
//執行最後一步
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
到這裏,一個AsyncTask的任務執行結束過程就很明顯了,Handler內部對消息的類型進行了判斷,如果這是一條MESSAGE_POST_RESULT消息,就會去執行finish()方法,如果這是一條MESSAGE_POST_PROGRESS消息,就會去執行onProgressUpdate()方法。而正常情況下都是執行finish()方法,而在這個方法中可以看到,如果當前任務被取消掉了,就會調用onCancelled()方法,如果沒有被取消,則調用onPostExecute()方法,這樣當前任務的執行就全部結束了。
而在上面描述doInBackground()方法時介紹了,可以調用publishProgress()方法來發布任務處理進度,下面來看看這個方法實現:
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
//發佈ui進度更新消息
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
可以看到,publishProgress()也是通過handler對象來發送ui更新消息,從而完成對UI元素的更新操作。
至此,AsyncTask的源碼介紹已經結束,從整個流程來看,AsyncTask只是谷歌利用Thread與Handler機制封裝了一個方便使用者調用的工具類,但它的實現模式確實是我們可以好好學習的。
遇到的坑
使用AsyncTask進行異步任務時,我們會經常在生命週期結束的時候嘗試調用AsyncTask的cancel()方法來取消任務,但有些時候你會遇到代碼已經執行了cancel()方法但最終AsyncTask還是調用了其他回調方法,而生命週期回調的方法已經回收了一些資源,導致主線代碼異常,爲此我看了下cancel()方法實現以及介紹:
/**
* <p>Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>
*
* <p>Calling this method will result in {@link #onCancelled(Object)} being
* invoked on the UI thread after {@link #doInBackground(Object[])}
* returns. Calling this method guarantees that {@link #onPostExecute(Object)}
* is never invoked. After invoking this method, you should check the
* value returned by {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])} to finish the task as early as
* possible.</p>
*
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete.
*
* @return <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally;
* <tt>true</tt> otherwise
*
* @see #isCancelled()
* @see #onCancelled(Object)
*/
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
從上面的註釋可以看出,這個取消操作可能會因爲任務已經執行完,已經取消或者由於一些其他原因不能被取消導致任務繼續進行,所以大家在使用時也不要太信任源碼提供的方法。
就此對於AsyncTask源碼分析就此結束,由於筆者技術有限,如果分析過程中有出現錯誤,歡迎指出,謝謝。