上一節講了asynctask的官方文件,這一節深入講解如何深入使用AsyncTask。
asynctask本質上也是執行緒啟動,只是它封裝了一些內容,可以執行在後臺,同時可以和UI執行緒互動。
asynctask最少要啟動2個執行緒,最多四個。
AsyncTask的狀態
AsyncTask的狀態共有三種,PENDING,RUNNING和FINISHED。這三種狀態在AsyncTask的生命週期中之出現一次。
- PENDING,表示非同步任務還沒有開始執行。
- RUNNING,表示非同步任務正在執行。
- FINISHED,表示onPostExecute已經執行完成。
安卓官方文件推薦我們編寫一個子類繼承自AsyncTask並且必須實現doinbackground方法。
我們編寫一個簡單的程式測試這三個狀態的具體情況。
private ProgressTask task = new ProgressTask();//自定義的一個內部類,繼承自AsyncTask
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
...
Log.d("task", "before execute task`s status is " + task.getStatus());
task.execute();
Log.d("task", "after execute task`s status is " + task.getStatus());複製程式碼
在AsyncTask中我們實現以下幾個方法
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.d("task", "onPreExecute task`s status is " + task.getStatus());
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Log.d("task", "onPostExecute task`s status is " + task.getStatus());
handler.sendEmptyMessage(1);
}複製程式碼
在handler中我們接收一個資訊,用來列印此時task的狀態
switch (msg.what) {
case 1:
Log.d("task", "finally task`s status " + task.getStatus());
break;
}複製程式碼
這樣,我們的程式執行起來看一下日誌
04-02 18:10:43.747 10433-10433/com.example.saka.materialdesignapplication D/task: before execute task`s status is PENDING
04-02 18:10:43.748 10433-10433/com.example.saka.materialdesignapplication D/task: onPreExecute task`s status is RUNNING
04-02 18:10:43.748 10433-10433/com.example.saka.materialdesignapplication D/task: after execute task`s status is RUNNING
04-02 18:11:17.724 10433-10433/com.example.saka.materialdesignapplication D/task: onPostExecute task`s status is RUNNING
04-02 18:11:17.724 10433-10433/com.example.saka.materialdesignapplication D/task: finally task`s status FINISHED複製程式碼
可以看到,在整個任務週期中,task在execute之前是一直處於pennding狀態,
在execute之後onPostExecute中的所有方法執行完成之前一直處於RUNNING狀態,
跳出onPostExecute方法之後所有的task任務相當於已經完成,
這時候task的狀態時FINISHED。
通過觀察原始碼我們可以看一下:
當你新建一個AsyncTask物件以後,系統就會自動生成一個變數:private volatile Status mStatus = Status.PENDING;
這也就是說明當你new一個AsyncTask後,它的狀態就被設定為了PENDING狀態。
直到execute或者executeOnExecutor方法執行之後。
呼叫task.execute()之後,系統會呼叫executeOnExecutor()方法:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}複製程式碼
傳入的參數列明會啟動預設的執行緒執行器,而現在系統預設的序列執行,也就是分先後次序執行。
executeOnExecutor()方法返回的同樣是一個AsyncTask例項:
@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;
}複製程式碼
在這個方法中,首先判斷非同步任務的狀態,
假如這個任務沒有在等待狀態,而是正在執行或者已經結束,會丟擲異常。
然後會將非同步任務的狀態設定為RUNNING,呼叫onPreExecute方法,
並將引數傳遞給AsyncTask中的引數接收器,
然後才開始執行exec.execute(mFuture)方法。所以在onPreExecute之前,狀態已經設定為了RUNNING。
再來研究以下何時會停止。
呼叫exec.execute(mFuture)中傳入的引數是一個介面型別的Runable:
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)
上面兩步都是在new一個AsyncTask例項的時候執行的,try塊中時執行doInbackGround方法,
在finally中執行的是結束動作。在postResult()方法中,定義了一個message,
呼叫該方法的時候會使用AsyncTask中的handler傳送訊息,handler接收到訊息後執行如下程式碼:
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
...
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}複製程式碼
在finish方法中設定了status為FINISHED,
注意此處時在執行完onCancelled或者onPostExecute之後才改變狀態,
這也就解釋了為什麼在onPostExecute方法中獲取的狀態仍然是RUNNING。
Note:根據文件中的介紹,並沒有講當task被cancel後是否會執行FINISHED,實際是執行的。
取消任務
關於cancel方法官方的介紹比較簡單,參看上篇文章。
首先看一下官方文件對這個API的解釋:boolean cancel (boolean mayInterruptIfRunning)
嘗試取消執行此任務。 如果任務已經完成,已經被取消或由於某種其他原因而無法取消,則此嘗試將失敗。
如果成功,並且在取消被呼叫時此任務尚未開始,則此任務不應該執行。
如果任務已經啟動,那麼mayInterruptIfRunning引數可以確定執行該任務的執行緒是否應該被中斷,以試圖停止該任務。
呼叫此方法將導致在doInBackground(Object [])返回後在UI執行緒上呼叫onCancelled(Object)。
呼叫此方法可以保證onPostExecute(Object)從不被呼叫。
呼叫此方法後,應該從doInBackground(Object [])定期檢查isCancelled()返回的值,以儘早完成任務。
這是什麼意思呢?
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}複製程式碼
呼叫asynctask的cancel方法其實就是呼叫futuretask的cancel方法,這個是java執行緒中的方法,
假如傳入的引數為true的時候(並不推薦這種方式),會立即停止當前任務;
當傳入的引數為false的時候,會等到本次任務執行完成後,
無論在哪種情況下,我們都應該清楚的認識到,
此時獲取當前任務的isCanceled都會返回為true。
呼叫cancel方法影響到的會有doInBackground、onPostExecute和onCancelled方法,
當傳入引數為ture的時候,doInBackGround方法會立即中斷,進入onCancelled方法,而不執行onPostExecute方法;
當傳入的引數為false的時候,doInBackground方法會執行完畢後進入onCancelled方法,也不執行onPostExecute方法;
始終不影響onPreExecute方法,它始終會執行。
但是偶爾會出現doInbackground方法不執行而直接進入onCancelled方法。
-
task啟動直接cancel:
private void setCancelNow() { task.cancel(false); }複製程式碼
此時日誌是:
04-02 21:44:24.692 8082-8082/com.example.saka.materialdesignapplication D/task: before execute task`s status is PENDING 04-02 21:44:24.692 8082-8082/com.example.saka.materialdesignapplication D/task: onPreExecute task`s status is RUNNING 04-02 21:44:24.692 8082-8082/com.example.saka.materialdesignapplication D/task: after execute task`s status is RUNNING 04-02 21:44:24.747 8082-8082/com.example.saka.materialdesignapplication D/task: onCancelled task`s status:RUNNING複製程式碼
可見方法執行了onPreExecute和onCancelled方法,然而並沒有執行doInBackground方法。
-
task啟動後過一段時間在cancel:
private void setCancelDelay() { try { Thread.sleep(1000); task.cancel(false); Log.d("task", "task after cancel"); } catch (InterruptedException e) { e.printStackTrace(); } }複製程式碼
此時的輸出日誌是:
04-02 21:51:22.781 14525-14525/? D/task: before execute task`s status is PENDING 04-02 21:51:22.781 14525-14525/? D/task: onPreExecute task`s status is RUNNING 04-02 21:51:22.781 14525-14525/? D/task: after execute task`s status is RUNNING 04-02 21:51:22.782 14525-14540/? D/task: doInBackground 04-02 21:51:23.113 14525-14540/? D/task: the progress value 0 04-02 21:51:23.452 14525-14540/? D/task: the progress value 1 04-02 21:51:23.782 14525-14525/com.example.saka.materialdesignapplication D/task: task after cancel ... 04-02 21:51:56.552 14525-14525/com.example.saka.materialdesignapplication D/task: onCancelled task`s status:RUNNING 04-02 21:51:56.552 14525-14525/com.example.saka.materialdesignapplication D/task: finally task`s status FINISHED複製程式碼
可以看到此時執行了doInbackground方法,最後進入了onCancelled方法。
因為preExecute方法是在非同步任務啟動後而真正的執行緒啟動前呼叫的,
而cancel方法呼叫的時futuretask的cancel方法,
也就是真正啟動執行緒之後才執行cancel的,
所以preExecute一定會執行。
doInBackground是在WorkerRunnable的call回撥方法中執行的,
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
...
try {
...
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};複製程式碼
假如WorkerRunnable不執行,也就不會呼叫onCancelled方法。
futuretask有一個回撥方法是必須實現的,done()方法,當任務結束後會呼叫此方法。
futuretask在done的回撥方法中檢測mTaskInvoked是否被呼叫,假如未被呼叫會執行postResultIfNotInvoked(get())
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}複製程式碼
此時同樣會呼叫postResult進而呼叫onCancelled方法。
所以無論何時都會呼叫onCancelled方法。
任務的序列和並行
首先看看execute方法:
AsyncTask<params, progress,=”” result=””> execute (Params… params)