[Toc]
基礎認識
AsyncTask 是基於 Handler 進行封裝的輕量級非同步類,它是一個抽象類,我們要使用的時候需要實現其子類的以下 4 個方法
方法 | 描述 |
---|---|
onPreExecute() | 任務執行前被呼叫,執行在 UI 執行緒中,在這裡我們做一些任務啟動前的準備 |
doInBackground() | 執行在新的子執行緒中的,做非同步任務的處理 |
onProgressUpdate() | 這個方法是在呼叫 publishProgress 的時候被呼叫的,是執行在 UI 執行緒的 |
onPostExecute() | 這個方法是任務執行完畢之後被呼叫的,是執行在 UI 執行緒中的 |
作用
- 實現多執行緒: 在工作執行緒中執行任務,如 耗時任務
- 非同步通訊、訊息傳遞: 實現工作執行緒 & 主執行緒(UI執行緒)之間的通訊,即:將工作執行緒的執行結果傳遞給主執行緒,從而在主執行緒中執行相關的UI操作
AsyncTask 的三種狀態
每個狀態在一個任務的生命週期中只會被執行一次。
狀態 | 描述 |
---|---|
PENDING | 等待(還沒有開始執行任務) |
RUNNING | 執行中 |
FINSHED | 完成 |
AsyncTask 的內部執行過程
AsyncTask 的物件呼叫 execute 方法,execute 內部又呼叫了 executeOnExecutor ,onPreExecute 方法就是在這裡被回撥,之後將 AsyncTask 的引數封裝成一個併發類,然後將其新增到排隊執行緒池(SerialExecutor)中進行排隊,如果當前有任務正在執行,則等待,否則 THREAD_POOL_EXECUTOR 執行該任務。在任務的執行過程中,通過 InternalHandler 將進度 pos(MESSAGE_POST_GROGRESS)傳送到主執行緒中,此時會呼叫 onProgressUpdate 方法,任務執行完畢之後,InternalHandler 將結果 post(MESSAGE_POST_RESULT) 傳送到主執行緒中,此時 onPostExecute 或者 onCancle 會被呼叫,任務執行到這裡就結束了。
基本使用
- 舉個例子:在非同步任務中每隔 1s 列印 1 ~ 10 的數值
- 我們不干預任務的執行過程,由任務執行完成,檢視任務執行情況;
- 任務執行完成後,我們再點選開始,檢視任務執行情況;
- 干預任務的執行過程,在任務執行期間點選取消,檢視任務執行情況;
public class MainActivity extends AppCompatActivity {
private TextView mText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mText = findViewById(R.id.text);
progressAsycn1 = new ProgressAsycn();
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
progressAsycn1.execute(1);
}
});
findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
progressAsycn1.cancel(true);
mText.append(String.format("取消任務%s\n",new Date().toString()));
}
});
}
private ProgressAsycn progressAsycn1;
private class ProgressAsycn extends AsyncTask<Integer,Integer,String> {
// 這個方法是在啟動之前被呼叫的,執行在 UI 執行緒中,在這裡我們做一些任務啟動前的準備
@Override
protected void onPreExecute() {
super.onPreExecute();
mText.append(String.format("準備執行%s\n",new Date().toString()));
}
// 這個方法是執行在新的執行緒的中的
@Override
protected String doInBackground(Integer... params) {
for (int i = params[0]; i <= 10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
publishProgress(i);
}
return "任務已經執行完畢";
}
// 這個方法是在呼叫 publishProgress 的時候被呼叫的,是執行在 UI 執行緒的
@Override
protected void onProgressUpdate(Integer... values) {
mText.append(String.format("工作進度:%d\n",values[0]));
}
// 這個方法是任務執行完畢之後被呼叫的
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mText.append(String.format("任務執行完畢%s\n",new Date().toString()));
}
/**
* 非同步任務被取消時回撥,即 AsyncTask 的物件呼叫了 cancel 方法
* 這個方法和 onPostExecute 互斥
* doInBackground 方法中的任務執行完畢,才會被回撥
*/
@Override
protected void onCancelled() {
mText.append(String.format("非同步任務已取消%s\n",new Date().toString()));
}
}
}
複製程式碼
-
執行結果
- 不干預任務的執行過程,最後的執行結果如下
java.lang.IllegalStateException: Cannot execute task: the task has already been executed (a task can be executed only once) 複製程式碼
- 在任務的執行過程中,點選取消任務,呼叫 cancel() 方法,之後我們可以看到 onProgressUpdate() 和 onPostExecute() 方法不再被呼叫,但是我們取消任務的時候,任務還是沒有停止的,等到任務真正停止的時候,onCancelled() 方法被呼叫,執行效果圖如下:
-
問題:從上面的執行結果中,我們可以看出兩個問題
-
- AsyncTask 的物件只能被呼叫一次,再次呼叫的時候,會出錯
-
- Asynctask 呼叫了 cancel() 方法取消任務,但是任務並沒有真正的停止
-
提出問題
我們在閱讀原始碼之前,先給自己提一些問題,然後我們在閱讀原始碼的時候,帶著問題來去找答案,這樣我們的目標才會更加明確。
- 為什麼AsyncTask 的物件只能被呼叫一次,否則會出錯?(每個狀態只能執行一次)
- AsyncTask 的類為什麼必須在主執行緒載入
- AsyncTask 的物件為什麼必須在主執行緒中建立
- AsyncTask 是序列執行任務還是並行執行任務?
- AsyncTask 呼叫 cancel() 任務是否立即停止執行?onPostExecute() 還會被呼叫嗎?onCancelled() 什麼時候被呼叫?
內部原始碼分析
建構函式中做了什麼
首先,我們檢視在 AsyncTask 的建構函式裡面到底做了些什麼
/**
* 該構造方法必須在 UI 執行緒中被呼叫
*/
public AsyncTask() {
this((Looper) null);
}
/**
* 該構造方法必須在 UI 執行緒中被呼叫
*
* @hide
*/
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// doInBackground 的呼叫時機
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);
}
}
};
}
複製程式碼
從以上的程式碼中,我們可以看出 AsyncTask 做了以下幾個工作:
-
初始化 handler
- 我們可以從預設呼叫的建構函式中傳入的 callbackLooper 是為 null,那麼 handler 初始化的時候是呼叫 getMainHandler() 方法的,getMainHandler() 方法的的具體程式碼如下:
private static InternalHandler sHandler; private static Handler getMainHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(Looper.getMainLooper()); } return sHandler; } } 複製程式碼
- 我們從上面的程式碼中可以知道,getMainHandler() 最後返回的值是 sHandler,而 sHandler 是個靜態 InternalHandler 例項,而關於 InternalHandler 的實現,我們在下面再做分析。 我們在這裡需要注意的點有兩個
- 為了能夠將執行環境切換到主執行緒中,我們要求 sHandler 必須在主執行緒中建立,所以 AsyncTask 的建構函式必須在 UI 執行緒中呼叫
- 我們知道靜態成員會在載入類的時候進行初始化,由於 sHandler 是個靜態變數,那麼我們要求 AsyncTask 類必須在 UI 執行緒中進行載入,否則 AsyncTask 無法正常工作
-
初始化 mWorker
- mWorker 是一個 Callable 物件,並在之後初始化 mFutrue 的時候作為引數傳入,我們在 mWorker 的程式碼實現這裡可以看到 doInBackground() 在這裡被呼叫了(但是真正的呼叫時機不不是在這裡進行的,而是在 SerialExecutor 的 execute 的方法中),並將結果返回給 result,並在方法結束之前呼叫 postResult(result)。 方法
-
初始化 mFuture
- MFuture 是一個 FutureTask 物件
execute() 中做了什麼
接著如果想要啟動某一個任務,就需要呼叫該任務的 execute() 方法,因此現在我們來看一看 execute() 方法的原始碼
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
複製程式碼
execute() 方法的比較簡單,只是呼叫了 executeOnExecutor 方法,那麼具體的邏輯是在 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;
}
複製程式碼
- 首先,executeOnExecutor 先對當前的狀態進行判斷,由於 AsyncTask 中有三個狀態(PENDING、RUNNING、FINISHED),並且每個狀態在 AsyncTask 的生命週期中有且只能被執行一次,如果當前的狀態不是 Status.PENDING(未執行),那麼就丟擲異常。在這裡也解釋了,我們 execute() 方法只能被執行一次,再次呼叫會報錯的原因。
- 接著,我們可以看到 onPreExecute() 方法被呼叫了
- 緊接著,我們可以看到 exec.execute(mFuture) 這行程式碼,而 exec 又是什麼,通過查詢上面的 execute 方法,我們可以看到這個 exec 是一個 sDefaultExecutor 變數,sDefaultExecutor 變數的定義如下:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_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);
}
}
}
複製程式碼
從以上程式碼可以看到,在 executeOnExecutor 中的 exce 最終呼叫的是 SerialExecutor 中 execute() 方法。而 execute() 這個方法的邏輯是在子執行緒中執行的,而 execute 這個方法傳入的 Runnable 正是 mFuture,在 run 方法中呼叫了, mFuture 物件的 run。我們再找到 FutureTask 中實現的 run 方法的程式碼,程式碼如下(有省略):
public void run() {
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
try {
result = c.call();
} catch (Throwable ex) {
}
}
} finally {
// .....
}
}
複製程式碼
從上面的程式碼中我們可以看出,最終呼叫了 Callable 中的 call (),而這個 callball 就是我們在 executeOnExecutor 中傳入 mFuture 的 mWorker 物件。現在我們又重新拿出 MWorker 的程式碼來看一下:
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() 方法的呼叫,並將結果給到 result,最後在方法結束之前呼叫了 postResult(result);
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
複製程式碼
而在 postResult 中使用了 sHandler 物件傳送了一條訊息,訊息中攜帶了 MESSAGE_POST_RESULT 常量和一個表示任務執行結果的 AsyncTaskResult 物件。這個 sHandler 物件是 InternalHandler 類的一個例項,那麼稍後這條訊息肯定會在 InternalHandler 的 handleMessage() 方法中被處理。InternalHandler 的原始碼如下所示:
private static class InternalHandler extends Handler {
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;
}
}
}
複製程式碼
在 handleMessage 這裡對訊息的型別進行了判斷,如果這是一條 MESSAGE_POST_RESULT 訊息,就會去執行 finish() 方法,如果這是一條 MESSAGE_POST_PROGRESS 訊息,就會去執行 onProgressUpdate() 方法。那麼 finish() 方法的原始碼如下所示:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
複製程式碼
可以看到,如果當前任務被取消掉了,就會呼叫 onCancelled() 方法,如果沒有被取消,則呼叫 onPostExecute() 方法,這樣當前任務的執行就全部結束了。
我們注意到,在剛才 InternalHandler 的handleMessage() 方法裡,還有一種 MESSAGE_POST_PROGRESS 的訊息型別,這種訊息是用於當前進度的,呼叫的正是 onProgressUpdate() 方法,那麼什麼時候才會發出這樣一條訊息呢?相信你已經猜到了,檢視publishProgress()方法的原始碼,如下所示:
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
複製程式碼
最後我們來理一下 executeOnExecutor 中的任務啟動到結束,關鍵方法的呼叫順序:
executeOnExecutor() -> sDefaultExecutor.execute() -> mFuture.run() -> mWorker.call() -> doInBackground() -> postResult() -> sHandler.sendMessage() -> sHandler.handleMessage() -> onPostExecute()
複製程式碼
問題解決
- 為什麼 AsyncTask 的物件只能被呼叫一次,否則會出錯?(每個狀態只能執行一次)
從上面我們知道,AsyncTask 有 3 個狀態,分別為 PENDING、RUNNING、FINSHED,而且每個狀態在 AsyncTask 的生命週期中有且只執行一次。由於在執行完 execute 方法的時候會先對 AsyncTask 的狀態進行判斷,如果是 PENDING(等待中)的狀態,就會往下執行並將 AsyncTask 的狀態設定為 RUNNING(執行中)的狀態;否則會丟擲錯誤。AsyncTask finish 的時候,AsyncTask 的狀態會被設定為 FINSHED 狀態。
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)");
}
}
複製程式碼
-
AsyncTask 的類為什麼必須在主執行緒載入
由於 sHandler 是一個靜態的 Handler 物件,為了能夠將執行環境切換到主執行緒中,這就要求 sHandler 必須在主執行緒中建立,也即是 AsyncTask 必須在主執行緒中建立。由於靜態成員會在載入類的時候進行初始化,因此也要求 AsyncTask 的類必須在主執行緒中載入,否則 AsyncTask 無法正常工作。 -
AsyncTask 的物件為什麼必須在主執行緒中建立
因為在 AsyncTask 的建構函式中對 handler 進行了初始化的操作,所以 AsyncTask 必須在主執行緒中進行建立,否則 AsyncTask 無法進行執行緒切換的工作 -
AsyncTask 是序列執行任務還是並行執行任務?
在 Android 1.6 之前,AsyncTask 是序列執行任務的,Android 1.6的時候 AsyncTask 是並行執行任務的,Android 3.0 之後,為了避免並行錯誤,AsyncTask 又採用一個執行緒來序列執行任務。 -
AsyncTask 呼叫 cancel() 任務是否立即停止執行?onPostExecute() 還會被呼叫嗎?onCancelled() 什麼時候被呼叫?
任務不會立即停止的,我們呼叫 cancel 的時候,只是將 AsyncTask 設定為 canceled(可取消)狀態,我們從以下程式碼可以看出,AsyncTask 設定為已取消的狀態,那麼之後 onProgressUpdate 和 onPostExecute 都不會被呼叫,而是呼叫了 onCancelled() 方法。onCancelled() 方法是在非同步任務結束的時候才呼叫的。時機是和 onPostExecute 方法一樣的,只是這兩個方法是互斥的,不能同時出現。
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
// 執行緒執行完之後才會被呼叫
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
複製程式碼
AsyncTask 在使用的過程中的一些限制總結
- (1)AsyncTask 的類必須在主執行緒載入
- (2)AsyncTask 的物件必須在主執行緒中建立
- (3)execute 方法必須在 UI 執行緒中呼叫
- (4)不要在主執行緒中呼叫 onPreExecute 、doInbackground、onPressUpdate、onPostExecute
- (5)AsyncTask 的物件只能被呼叫一次,否則會出錯
- (6)AsyncTask 不太適合做太耗時的操作
- (7)在 Android 1.6 之前,AsyncTask 是序列執行任務的,Android 1.6的時候 AsyncTask 是並行執行任務的,Android 3.0 之後,為了避免並行錯誤,AsyncTask 又採用一個執行緒來序列執行任務。
- (8)如果在 AsyncTask 中的 doInBackGround 中開啟了新的執行緒,我們執行了 cancle() 方法來停止非同步任務,執行緒是不會被停止的,直到任務執行完成為止,這個過程中,onProgressUpdate 和 onPostExecute 是不會被呼叫的