標籤:
首先,我得解釋一下為什麼我的標題取消非同步載入打引號,這是因為可能最後實現效果並不是你自己想象中的那樣。大家看取消非同步載入,這不是很簡單嗎?AsyncTask中不是有一個cancel方法嗎?直接調用該方法不就行了嗎?但是事實上是這樣的嗎?如果真是這樣,我相信我就沒有以寫這個作為一篇部落格的必要了。為什麼會有這樣的想法呢?實際上源於我上一篇中Demo中的一個BUG,然後解決該BUG,需要去取消非同步任務,是怎麼樣,我們不妨來看看。
首先,還是來一起回顧一下上篇部落格中載入進度條Demo吧。
AsyncTask子類:
01.package com.mikyou.utils; 02. 03.import android.os.AsyncTask; 04.import android.widget.ProgressBar; 05.import android.widget.TextView; 06. 07.public class MikyouAsyncTaskProgressBarUtils extends AsyncTask<Void, Integer, String>{ 08. private TextView tv; 09. private ProgressBar bar; 10. public MikyouAsyncTaskProgressBarUtils(TextView tv,ProgressBar bar){//這裡我就採用構造器將TextView,ProgressBar直接傳入,然後在該類中直接更新UI 11. this.bar=bar; 12. this.tv=tv; 13. 14. } 15. @Override 16. protected String doInBackground(Void... params) { 17. for(int i=1;i<101;i++){ 18. try { 19. Thread.sleep(1000); 20. } catch (InterruptedException e) { 21. e.printStackTrace(); 22. } 23. publishProgress(i); 24. } 25. return "下載完成"; 26. } 27. @Override 28. protected void onProgressUpdate(Integer... values) { 29. bar.setProgress(values[0]); 30. tv.setText("下載進度:"+values[0]+"%"); 31. super.onProgressUpdate(values); 32. } 33. @Override 34. protected void onPostExecute(String result) { 35. tv.setText(result); 36. super.onPostExecute(result); 37. } 38.}
MainActivity
01.package com.mikyou.asynctask; 02. 03.import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils; 04. 05.import android.app.Activity; 06.import android.os.Bundle; 07.import android.view.View; 08.import android.widget.Button; 09.import android.widget.ProgressBar; 10.import android.widget.TextView; 11. 12.public class MainActivity extends Activity { 13. private Button downLoad; 14. private ProgressBar bar; 15. private TextView tv; 16. @Override 17. protected void onCreate(Bundle savedInstanceState) { 18. super.onCreate(savedInstanceState); 19. setContentView(R.layout.activity_main); 20. initView(); 21. } 22. private void initView() { 23. bar=(ProgressBar) findViewById(R.id.bar); 24. tv=(TextView) findViewById(R.id.tv); 25. } 26. public void download(View view){ 27. MikyouAsyncTaskProgressBarUtils mikyouAsyncTaskProgressBarUtils=new MikyouAsyncTaskProgressBarUtils(tv, bar); 28. mikyouAsyncTaskProgressBarUtils.execute(); 29. } 30. 31.}
運行結果的Demo的BUG:
通過分析上面的demo會發現:
當我們點擊開始下載後,進度條就開始更新了,然後就在更新中途退出Activity,然後再次進入Activity,點擊開始下載會發現,等了好一會,進度條才開始更新。
原因:這並不是本程式的一個BUG,而是非同步載入的一個機制,因為非同步載入從源碼中我們可以得出,它底層的實現還是Thread或者Thread-pool+Handler機制
那麼它的機制就是如果當前開始的線程沒有執行完畢,其他的線程必須等待,這也就解釋了為什麼說非同步載入是安全,因為可以基於該機制避免了線程同步帶來的安全問題。
解決辦法:其實很簡單的就是將非同步載入的生命週期和我們的Activity的生命週期進行綁定,當我第一次在中途中斷的時候,退出Activity時會調用OnPause
方法,只需要在OnPause方法中順便把本次中斷的非同步任務取消即可,也即把當前的線程池中的線程給取消了,當下次重新進入的時候,線程池中並沒有
其他的子線程,那麼它就無需等待其他的線程了,直接運行。
通過以上的分析,我們就可以得到了一個解決辦法,那就是如何去取消當前的非同步任務,那就我們就按照大家的一致想法使用cancel方法來終止非同步任務。
來了看看是否可以達到我們想要的效果呢??一起來看看
package com.mikyou.asynctask;import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;import android.app.Activity;import android.os.AsyncTask;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.ProgressBar;import android.widget.TextView;public class MainActivity extends Activity {private Button downLoad;private ProgressBar bar;private TextView tv;private MikyouAsyncTaskProgressBarUtils mTask;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {bar=(ProgressBar) findViewById(R.id.bar);tv=(TextView) findViewById(R.id.tv);}public void download(View view){ mTask=new MikyouAsyncTaskProgressBarUtils(tv, bar);mTask.execute();}//將非同步任務的生命週期與Activity進行綁定@Overrideprotected void onPause() {//判斷當前的非同步任務是否為空白,並且判斷當前的非同步任務的狀態是否是運行狀態{RUNNING(運行),PENDING(準備),FINISHED(完成)}if (mTask!=null&&mTask.getStatus()==AsyncTask.Status.RUNNING) {/** *cancel(true) 取消當前的非同步任務,傳入的true,表示當中斷非同步任務時繼續已經啟動並執行線程的操作, *但是為了線程的安全一般為讓它繼續設為true * */mTask.cancel(true);}super.onPause();}}運行結果:
大家仔細看下,發現好像我們加的cancel方法並沒有什麼用,這也就我這篇需要講的了,
為什麼無法去停止一個非同步任務,這是為什麼呢?
注意:這是因為cancel方法只是發出一個請求取消非同步任務的訊號, 將對應當前的非同步任務標記為CANCEL狀態,而並不是真正取消線程的執行,而此時非同步任務中的線程仍然在執行並沒有結束,所以效果依然是這樣的,並且在java中我們是無法直接暴力將一個線程給停止掉 既然我們知道無法去取消一個已經正在啟動並執行線程,但是我們如何去解決這個BUG呢?在非同步任務中還給我們提供一個isCanceled的回調方法,也就是當我已經給當前的非同步任,調用了cancel(true)方法,發出一個請求取消非同步任務的訊號,那麼此時的isCanceled的回調方法會直接返回一個true,那麼我們就可以通過判斷當前非同步任務isCanceled是否為true,來終止線程中的操作而不是去終止線程,從而達到了介面顯示好像線程中的操作被終止了,而實際上該線程依然在運行
因為我們是無法去徹徹底底地採用暴力的方法直接kill一個線程,所以我們不能直接去取消一個非同步任務,但是我們可以通過調用cancel方法來發送一個取消非同步任務的請求訊號,這時候就會給mCanceled的值設定為true,標記為該非同步任務是取消了,通過回調方法isCanceled來返回一個true,表示該非同步任務取消。如果有疑問我們來看下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); }
會看mCanceled會設定為true,該值將會通過isCanceled方法回調傳出去。所以,我們採用這樣一個想法,既然我們不能去終止一個線程,那麼我們可以間接解決這個bug
通過判斷該值直接終止線程中的操作,而不是去終止線程。
package com.mikyou.asynctask;import com.mikyou.utils.MikyouAsyncTaskProgressBarUtils;import android.app.Activity;import android.os.AsyncTask;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.ProgressBar;import android.widget.TextView;public class MainActivity extends Activity {private Button downLoad;private ProgressBar bar;private TextView tv;private MikyouAsyncTaskProgressBarUtils mTask;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {bar=(ProgressBar) findViewById(R.id.bar);tv=(TextView) findViewById(R.id.tv);}public void download(View view){ mTask=new MikyouAsyncTaskProgressBarUtils(tv, bar);mTask.execute();}//將非同步任務的生命週期與Activity進行綁定@Overrideprotected void onPause() {//判斷當前的非同步任務是否為空白,並且判斷當前的非同步任務的狀態是否是運行狀態{RUNNING(運行),PENDING(準備),FINISHED(完成)}if (mTask!=null&&mTask.getStatus()==AsyncTask.Status.RUNNING) {/** *cancel(true) 取消當前的非同步任務,傳入的true,表示當中斷非同步任務時繼續已經啟動並執行線程的操作, *但是為了線程的安全一般為讓它繼續設為true * */mTask.cancel(true);/** * 但是重新運行後會發現還是不能起到效果, * 注意:這是因為cancel方法只是發出一個請求取消非同步任務的訊號, * 將對應當前的非同步任務標記為CANCEL狀態,而並不是真正取消線程的執行, * 而此時非同步任務中的線程仍然在執行並沒有結束 * 所以效果依然是這樣的,並且在java中我們是無法直接暴力將一個線程給停止掉 * 既然我們知道無法去取消一個已經正在啟動並執行線程,但是我們如何去解決這個BUG呢? * 在非同步任務中還給我們提供一個isCanceled的回調方法,也就是當我已經給當前的非同步任務 * 調用了cancel(true)方法,發出一個請求取消非同步任務的訊號,那麼此時的isCanceled的回調方法 * 會直接返回一個true,那麼我們就可以通過判斷當前非同步任務isCanceled是否為true,來終止 * 線程中的操作而不是去終止線程,從而達到了介面顯示好像線程中的操作被終止了,而實際上 * 該線程依然在運行 * */}super.onPause();}}
AsyncTask的子類
package com.mikyou.utils;import android.os.AsyncTask;import android.widget.ProgressBar;import android.widget.TextView;public class MikyouAsyncTaskProgressBarUtils extends AsyncTask<Void, Integer, String>{private TextView tv;private ProgressBar bar;public MikyouAsyncTaskProgressBarUtils(TextView tv,ProgressBar bar){//這裡我就採用構造器將TextView,ProgressBar直接傳入,然後在該類中直接更新UIthis.bar=bar;this.tv=tv;}@Overrideprotected String doInBackground(Void... params) {for(int i=1;i<101;i++){try {if (isCancelled()) {//判斷如果為true那麼說明已經有請求取消當前任務的訊號了,既然無法終止線程的運行,但是可以終止運行線上程中一系列操作break;}Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}publishProgress(i);}return "下載完成";}@Overrideprotected void onProgressUpdate(Integer... values) {if (isCancelled()) {//判斷如果為true那麼說明已經有請求取消當前任務的訊號了,既然無法終止線程的運行,但是可以終止運行線上程中一系列操作,使它空運行,無法達到更新UI的效果return ;}bar.setProgress(values[0]);tv.setText("下載進度:"+values[0]+"%");super.onProgressUpdate(values);}@Overrideprotected void onPostExecute(String result) {tv.setText(result);super.onPostExecute(result);}}
運行結果:
淺談android中非同步載入之"取消非同步載入"二