1. 程式人生 > >淺談android中非同步載入之"取消非同步載入"二

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

首先,我得解釋一下為什麼我的標題取消非同步載入打引號,這是因為可能最後實現效果並不是你自己想象中的那樣。大家看取消非同步載入,這不是很簡單嗎?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;
	@Override
	protected 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進行繫結
@Override
protected 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;
	@Override
	protected 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進行繫結
@Override
protected 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直接傳入,然後在該類中直接更新UI
		this.bar=bar;
		this.tv=tv;

	}
	@Override
	protected 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 "下載完成";
	}
	@Override
	protected void onProgressUpdate(Integer... values) {
		if (isCancelled()) {//判斷如果為true那麼說明已經有請求取消當前任務的訊號了,既然無法終止執行緒的執行,但是可以終止執行線上程中一系列操作,使它空執行,無法達到更新UI的效果
			return ;
		}
		bar.setProgress(values[0]);
		tv.setText("下載進度:"+values[0]+"%");
		super.onProgressUpdate(values);
	}
	@Override
	protected void onPostExecute(String result) {
		tv.setText(result);
		super.onPostExecute(result);
	}
}

執行結果:


相關推薦

android中非同步載入"取消非同步載入"

首先,我得解釋一下為什麼我的標題取消非同步載入打引號,這是因為可能最後實現效果並不是你自己想象中的那樣。大家看取消非同步載入,這不是很簡單嗎?AsyncTask中不是有一個cancel方法嗎?直接呼叫該方法不就行了嗎?但是事實上是這樣的嗎?如果真是這樣,我相信我就沒有以寫這

android中圖片處理圖形變換特效Matrix(四)

今天,我們就來談下android中圖片的變形的特效,在上講部落格中我們談到android中圖片中的色彩特效來實現的。改變它的顏色主要通過ColorMatrix類來實現。 現在今天所講的圖片變形的特效主要就是通過Matrix類來實現,我們通過上篇部落格知道,改變色彩特效,主要

android中圖片處理色彩特效處理ColorMatrix(三)

在android開發中對圖片處理很是頻繁,其中對圖片的顏色處理就是很常見的一種。我們經常看到一些類似美圖秀秀,美顏相機的app,為什麼那麼黑的人拍出來是確實那麼地白呢?長的那麼那個(醜)的人,用美顏相機拍出來的看起來也有那麼回事(拍出來就感覺挺漂亮)。就像網上有個段子,有錢

android中的ListView解決ScrollView和ListView巢狀衝突(實際上一切都是浮雲,閒的蛋疼)(一)

     相信大家都已經可以熟練使用ListView和GridView,大神們估計都在使用RecyclerView了。如果還在使用ListView,你肯定有這樣的一個深刻的感受,那就是在做一個APP的時候使用ListView和GridView很頻繁,並且經常會遇到一個頁面中

Android中的非同步載入ListView中圖片的快取及優化三

     隔了很久沒寫部落格,現在必須快速脈動回來。今天我還是接著上一個多執行緒中的非同步載入系列中的最後一個使用非同步載入實現ListView中的圖片快取及其優化。具體來說這次是一個綜合Demo.但是個人覺得裡面還算有點價值的就是裡面的圖片的快取的實現。因為老實說它確實能

AndroidAndroid中的MVP

個人開發的微信小程式,目前功能是書籍推薦,後續會完善一些新功能,希望大家多多支援! 前言 為什麼使用MVP,網上有很多說法,最主要就是減輕了Activity的責任,相比於MVC中的Activity承擔的責任太多,因此有必要講講MVP。 MVP入門 在MVC框

android的MVP設計模式記憶體洩露問題

我上次寫了淺談mvp,經過一段時間的思考,發現我忽略了一個問題 記憶體洩露問題。 因為Presenter中持有View介面物件,這個介面物件實際為MainActivity.this,Modle中也同時擁有Presenter物件例項,當MainActivi

AndroidSurfaceFlinger相關介紹(三)

3.3 Surface Java層相關封裝 主要介紹三個類,對應如下: Java C++ SurfaceSession.java SurfaceComposeClient 對應JNI檔案為: android_view_surfacesession.cpp

AndroidActivity Decor View建立流程介紹

6 Activity DecorView建立流程介紹 上頭已經完整的介紹了Activity的啟動流程,Activity是如何繫結Window,Window的décor view是如何通過ViewRootImpl與WMS建立關聯的,也就是說,整個框架已經有了,唯一缺的就是Ac

AndroidActivity 視窗顯示流程介紹(

7.3 Activity Décorview佈局(layout) Measure確定Décor View以及child views的大小,layout則是確定child view在其parent view中的顯示區域,只有layout結束,view的left,right,t

AndroidActivity 視窗顯示流程介紹(一)

7 Activity 視窗顯示流程介紹 Activity 視窗顯示,其實就是Décor View繪製並顯示的過程,但是在繪製之前,Décor View需要先做下面兩件事情: 1)  確定Décor View的大小 2)  對Décor View進行佈局操作,也就是確定Déc

android載入高清大圖及圖片壓縮方式()

  這一講就是本系列的第二篇,一起來聊下關於android中載入高清大圖的問題,我們都知道如果我們直接載入原圖的話,一個是非常慢,需要等待一定時間,如果沒有在一定的時間內給使用者響應的話,將會極大影響使用者的體驗。另一個是如果你的手機記憶體小的話,可能會直接崩潰。這也就是直

AndroidActivity觸控事件傳輸機制介紹

8 Activity觸控事件傳輸機制介紹 當我們觸控式螢幕幕的時候,程式會收到對應的觸控事件,這個事件是在app端去讀取的嗎?肯定不是,如果app能讀取,那會亂套的,所以app不會有這個許可權,系統按鍵的讀取以及分發都是通過WindowManagerService來完成

AndroidApp視窗檢視管理

5 App視窗檢視管理 WindowManagerGlobal負責管理App所有要新增到WMS的視窗,介面即為上頭的addView 首先,對於App本地視窗來說,其最核心的資料無非就兩個,一個是Window Parameters,另一個就是視窗的DécorView,一個負

Android進階

熱文導讀 | 點選標題閱讀作者:斜槓Allen地址:http://www.apkbus.com/

測試行業職業發展

clas bug 項目管理 合格 方法 事情 自動化 應用程序 境界 大家都說軟件測試入門容易,似乎軟件測試成了跳進互聯網生態圈的最佳途徑。 但是不少小夥伴在入門軟件測試後,卻變的相當的迷茫,不知道自己應該做什麽,似乎點點點就成了工作中唯一的事情了。 趁現在負能量還

Android中的組播(多播)

-1 ip協議 strong 多個 接受 端口 ui線程 nbsp 數據 組播使用UDP對一定範圍內的地址發送相同的一組Packet,即一次可以向多個接受者發出信息,其與單播的主要區別是地址的形式。IP協議分配了一定範圍的地址空間給多播(多播只能使用這個範圍內

Android發展趨勢分析

相互 ces rec scl reg AC sed pen 可能   去年11月16、17日,我有幸參加了北京2017安卓技術大會,做了關於車載Android系統的演講,並主持了諸多大咖參與的圓桌討論,對Android未來幾年的發展趨勢進行了一番討論。來自小米、百度、高通等

Android編碼規範及命名規範

event err class wrap spa for循環 who 全局變量 經典 原文:淺談Android編碼規範及命名規範前言:   目前工作負責兩個醫療APP項目的開發,同時使用LeanCloud進行雲端配合開發,完全單挑。   現大框架已經完成,正在進行細節模

Android開發實戰(十一):android:clipChildren屬性

.cn viewpage port 部分 lap ole 有一個 默認 版本 原文:Android開發實戰(二十一):淺談android:clipChildren屬性實現功能: 1、APP主界面底部模塊欄 2、ViewPager一屏多個界面顯示 3、........