淺談android中非同步載入之"取消非同步載入"二

來源:互聯網
上載者:User

標籤:

首先,我得解釋一下為什麼我的標題取消非同步載入打引號,這是因為可能最後實現效果並不是你自己想象中的那樣。大家看取消非同步載入,這不是很簡單嗎?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中非同步載入之"取消非同步載入"二

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.