大話Android多線程(六) AsyncTask知識掃盲

版權聲明:本文爲博主原創文章,未經博主容許不得轉載
源碼:github.com/AnliaLee
你們要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論html

前言

本章咱們將結合以前幾篇博客,來研究研究多線程知識綜合應用程度很高的AsyncTask類(Android 7.0版本)java

往期回顧
大話Android多線程(一) Thread和Runnable的聯繫和區別
大話Android多線程(二) synchronized使用解析
大話Android多線程(三) 線程間的通訊機制之Handler
大話Android多線程(四) Callable、Future和FutureTask
大話Android多線程(五) 線程池ThreadPoolExecutor詳解android


AsyncTask簡介

經過以前幾篇博客的學習和研究,咱們知道了要將耗時的任務放到子線程中執行,而後使用Handler機制通知UI線程任務的結果並執行更新UI的操做。若是這些步驟都由咱們本身動手去寫,勢必會讓代碼顯得很是臃腫git

Android給咱們提供了一種輕量級的異步任務類AsyncTask,該類實現了異步操做,並提供相應的接口反饋異步任務執行結果及進度,實現了從子線程執行任務到通知主線程更新UI的一條龍服務,大大減小了咱們的開發工做。下面咱們將從如何使用開始逐步揭開AsyncTask的神祕面紗github


如何使用AsyncTask

咱們以去快餐店點餐爲例。咱們將顧客點餐與取餐的行爲放在主線程中(更新UI界面等操做),而服務人員在廚房配餐的行爲放在子線程中進行(在後臺執行耗時操做多線程

顧客不會關心服務人員在廚房是如何工做的,他們只關心點了什麼(配置參數並調用AsyncTask.execute進行提交)以及什麼時候收到通知去取餐(在AsyncTask.onPostExecute回調方法中接收後臺任務返回的結果,並執行相應的操做)。服務人員在配餐以前能夠幫助顧客準備餐盤、紙巾等等,固然這些工做對顧客來講是可見的(經過重寫AsyncTask.onPreExecute回調方法執行一些耗時任務以前的準備工做,該方法運行在主線程中)。準備工做完畢後,服務人員會通知廚房進行配餐,這部分工做對於顧客來講是不可見的(在AsyncTask.doInBackground方法中編寫執行耗時任務的代碼,這些耗時任務運行在子線程中)。配餐完畢後,通知顧客來取餐(AsyncTask.doInBackground結束時會返回一個值,該值會傳遞到AsyncTask.onPostExecute中,證實耗時任務已經執行完畢)併發

整個流程簡單總結一下就是 開始任務execute) → 任務準備onPreExecute) → 執行任務doInBackground) → 反饋任務結果回到主線程執行相應操做onPostExecuteapp

下面咱們來看具體的代碼(關於使用AsyncTask會致使內存泄漏的問題請看文末的補充,這裏的代碼只是簡單實現就很少贅述了)異步

public class AsyncTaskTestActivity extends AppCompatActivity {
    TextView textShow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task_test);
        textShow = (TextView) findViewById(R.id.text_show);
    }

    public void clickEvent(View view) {
    	switch (view.getId()) {
            case R.id.btn_start:
                List<String> list = new ArrayList<>();
                list.add("薯條");
                list.add("漢堡");
                list.add("可樂");
                new MyAsyncTask().execute(list);
                break;
    	}
    }

    public class MyAsyncTask extends AsyncTask<List<String>,String,String>{
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            textShow.setText("餐盤準備好了,開始配餐...");
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            textShow.setText("配餐完畢," + s);
        }

        @Override
        protected String doInBackground(List<String>... params) {
            String foods = "已經配好的食物 —— ";
            try {
                for (String str : params[0]){
                    Thread.sleep(1000);//模擬配餐的時間
                    foods = foods + str + " ";
                    Log.e("白鬍子快餐店",foods);
                }
            }catch (Exception e){}
            return foods;
        }
    }
}
複製代碼

運行效果如圖所示async

咱們來分析一下上述代碼中的細節

使用AsyncTask首先得實現它的子類,咱們先來看下抽象類AsyncTask的部分源碼

public abstract class AsyncTask<Params, Progress, Result> 複製代碼

這裏告訴咱們若是要繼承AsyncTask,需配置3個泛型參數的具體類型,這3個參數的介紹以下

  • Params開始執行異步任務時需傳入的參數類型,即AsyncTask.execute方法中要傳遞的參數類型。例如咱們將Params設爲String類型,那麼將調用 execute(String s) 方法開始任務,提交的參數s類型爲String。另外此參數類型一樣和傳入AsyncTask.doInBackground方法的參數相對應

    ps:若是不須要傳遞任何參數,則能夠將參數類型設爲Void,那麼開始任務時只須要調用 execute() 便可,下同(返回參數同理)

  • Progress執行異步任務過程當中主線程傳遞的進度值的類型。咱們能夠在AsyncTask.doInBackground方法中調用publishProgress方法告知主線程當前耗時任務的執行進度,咱們設置的進度值類型publishProgress方法要傳遞參數的類型
  • Result任務執行完畢後,返回的結果類型,即AsyncTask.doInBackground方法返回值的類型,也是AsyncTask.onPostExecute方法傳入參數的類型

結合以前的代碼,在咱們實現的AsyncTask子類中,Params設爲List<String>類型,Progress設爲String類型,Result設爲String類型

public class MyAsyncTask extends AsyncTask<List<String>,String,String> 複製代碼

那麼對應的咱們在提交參數開始執行任務時,就須要傳入List<String>類型的參數了

List<String> list = new ArrayList<>();
list.add("薯條");
list.add("漢堡");
list.add("可樂");
new MyAsyncTask().execute(list);//這裏若傳入多個list,在doInBackground中按順序取參便可
複製代碼

doInBackground中獲取咱們提交的參數

protected String doInBackground(List<String>... params) {
	for (String str : params[0])//params[0]就是咱們提交的第一個list
	...
	
	return foods;//String foods
}
複製代碼

任務執行完畢後,返回一個String類型的值,該值即爲onPostExecute方法的傳入參數

protected void onPostExecute(String s)//s就是咱們須要的返回值 複製代碼

好了,代碼的細節分析完畢,下面咱們來看看如何實現任務的進度更新功能

首先咱們須要重寫AsyncTask.onProgressUpdate回調方法,將更新進度UI的操做放在這裏面

public class MyAsyncTask extends AsyncTask<List<String>,String,String>{
	//省略部分代碼...
	@Override
	protected void onProgressUpdate(String... values) {
		super.onProgressUpdate(values);
		textShow.setText(values[0]);
	}
}
複製代碼

而後在doInBackground方法中(子線程)調用publishProgress方法就能夠把當前任務進度傳遞到onProgressUpdate中(主線程)了

@Override
protected String doInBackground(List<String>... params) {
	String foods = "已經配好的食物 —— ";
	try {
		for (String str : params[0]){
			Thread.sleep(1000);//模擬配餐的時間
			foods = foods + str + " ";
			publishProgress(foods);//一樣這裏能夠傳遞多個String類型的參數
		}
		Thread.sleep(500);
	}catch (Exception e){}
	return foods;
}
複製代碼

運行結果以下

除了以上這些方法外,AsyncTask還提供了onCancelled回調方法。當咱們調用 cancel(true) 時,doInBackground方法將強制中斷任務並調用onCancelledonCancelled被調用時onPostExecute不會被調用),所以咱們能夠將取消任務的操做放在onCancelled中,例如

public class MyAsyncTask extends AsyncTask<List<String>,String,String>{
	//省略部分代碼...
	@Override
	protected void onCancelled(String s) {
		super.onCancelled(s);
		textShow.setText("未完成配餐," + s);
	}

	@Override
	protected String doInBackground(List<String>... params) {
		String foods = "已經配好的食物 —— ";
		try {
			for (String str : params[0]){
				if(str.equals("可樂")){
					Thread.sleep(500);
					cancel(true);
					while (true){
						/** * cancel方法只是簡單把標誌位改成true * 最後使用Thread.interrupt去中斷線程執行 * 但這不能保證能夠立刻中止任務 * 因此需使用isCancelled來判斷任務是否被取消 */
						if(isCancelled()){
							return foods;
						}
					}
				}
				...
			}
			Thread.sleep(500);
		}catch (Exception e){}
		return foods;
	}
}
複製代碼

運行結果以下

簡單總結一下這些AsyncTask中經常使用的方法

  • execute(Params... params):提交參數,開始任務
  • onPreExecute():執行任務以前的準備操做
  • doInBackground(Params... params):在子線程中執行任務,返回任務結果
  • onPostExecute(Result result):接收任務結果,在UI線程主線程)中執行相應操做
  • onProgressUpdate(Progress... values):在UI線程中執行更新進度的操做,配套的提交任務進度的方法爲 publishProgress(Progress... values)
  • onCancelled(Result result):接收取消任務時的結果並執行相應的操做,配套的取消中斷任務的方法爲 cancel(boolean mayInterruptIfRunning)

那麼這些方法在AsyncTask內部具體是怎麼運做的呢?下面咱們就繼續深刻探尋一番吧


AsyncTask內部工做流程

以前咱們簡單地總結過一次流程:

開始任務execute) → 任務準備onPreExecute) → 執行任務doInBackground) → 反饋任務結果回到主線程執行相應操做onPostExecute

若是將這個流程繼續細化,則以下圖所示

從圖中咱們能夠看到線程池ThreadPoolExecutor,負責線程間通訊的Handler等等。若是有看過我以前幾篇博客或者瞭解過相關知識的童鞋應該很快就能在腦中描繪出AsyncTask整個工做流程的藍圖了。咱們這裏就不一行行地分析每一個方法的源碼了,只是對照着上圖幫你們理清思路,這樣你們去看一些源碼分析的博客時就沒那麼頭疼了

首先從實現AsyncTask的子類提及,AsyncTask內部有3個狀態,它們封裝在Status枚舉類中,分別是

  • Status.PENDING:在AsyncTask對象建立時就設置了狀態爲PENDING,表示AsyncTask等待被使用,還沒有開始執行任務
  • Status.RUNNING:提交參數開始任務後,狀態設置爲RUNNING,表示AsyncTask處於執行任務的狀態,任務正在運行中
  • Status.FINISHED:任務完成後,狀態會設置成FINISHED

須要補充的是,這些狀態在整個任務生命週期中只會設置一次,什麼時候設置狀態已在上圖用虛線標出

咱們調用execute方法後,AsyncTask會繼續調用executeOnExecutor方法(在此方法中調用了onPreExecute,所以在建立子線程執行任務前就完成了準備操做)並傳入默認的任務執行者SERIAL_EXECUTORSerialExecutor),在SerialExecutor中維護着一個任務隊列並限制了任務必須單一依次執行。不少博客將SerialExecutor說成是一個線程池,我我的並不贊同這一說法,由於實際上在SerialExecutor中完成建立線程、維護線程這些工做的是一個真正意義上的線程池(THREAD_POOL_EXECUTOR),所以最終提交任務的操做仍是回到了線程池的老路子,調用ThreadPoolExecutor.execute方法將任務入列


Android 7.0版本的AsyncTask默認串行執行任務,那有什麼方法能夠突破這一限制呢?答案是調用咱們以前提到的executeOnExecutor方法

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) 複製代碼

咱們能夠跳過execute方法直接調用executeOnExecutor並傳入咱們自定義的線程池,這樣就能夠併發地執行多線程任務了


回到咱們的工做流程,以前講到調用ThreadPoolExecutor.execute方法提交任務,提交的任務類型爲CallableWorkerRunnable),AsyncTask在其call方法中調用doInBackground方法。也就是說,提交任務後,ThreadPoolExecutor建立了子線程,而子線程執行了doInBackground中的耗時任務

任務執行完畢後,按套路使用Handler發送Message通知主線程耗時任務已經完成了(或調用publishProgress方法同樣可讓Handler發送消息通知主線程執行更新進度的操做),以後的事件就是根據發送Message的內容決定是執行onPostExecute(若設置了任務取消則執行onCancelled)仍是onProgressUpdate方法了(上圖因爲位置有限沒法體現更新進度這一過程,原理其實是同樣的)

那麼到這AsyncTask的內部工做流程咱們已經基本過了一遍,若是想要更深刻地瞭解源碼實現的過程,這裏向你們推薦幾位前輩的博客(因爲他們博客更新的時間不一樣,你們需注意比對各版本AsyncTask的差別)

Android AsyncTask 源碼解析
Android 7.0 AsyncTask分析
以及 Android進階之光 一書中關於AsyncTask的講解


一些額外的補充

  1. AsyncTask不一樣版本線程池的區別
  2. AsyncTask的缺陷

    相關博客推薦
    AsyncTask的缺陷和問題
    Android多線程-AsyncTask的使用和問題(取消,並行和串行,屏幕切換)

  3. Android實現弱引用AsyncTask,將內存泄漏置之度外

大話Android多線程系列到這就暫告一段落了,不知道你們看完這個系列以後收穫如何呢?若是以爲還行那就多多點贊,而後再買點橘子給博主吃。固然,我就吃兩個,剩下的都留給大家哈哈~

最後祝你們新春快樂"狗"到最後事事都旺旺~

相關文章
相關標籤/搜索