Android AsyncTask(1)-使用方法和線程池解析

當我們在android開發中需要開N個線程的時候,很多人的做法是直接new N 個 Thread。這樣做不但不利于線程管理、而且大量的線程沒有得到釋放將會消耗性能,而且一般的Thread是沒有返回線程執行完畢的返回值的,我們在Android開發中可以通過handler來告訴主線程線程是否執行完畢了。其實,Google工程師已經為我們設計好了一套強大的異步任務管理類 - AsyncTask。那么它到底強大在何處呢?接下來就開始慢慢講解它的強大之處。

一、AsyncTask優點:

  • 內部采用了線程池機制,可以有效的管理線程。
  • 可以自定義線程池,實現多個線程按順序同步執行、異步并發執行。
  • 提供了回調方法,后臺任務執行完畢后會返回需要的數據,拿到數據后可以直接更新UI控件

二、AsyncTask用法步驟

2.1 構建自定義AsyncTask的參數

AsyncTask是一個抽象類,通常用于被繼承,繼承AsyncTask需要指定三個泛型參數


  1. Params :啟動任務時輸入參數的類型。

     【就是傳進去耗時操作任務需要用到的參數,比如聯網請求String類型的URL】
    
  2. Progress:后臺任務執行中返回進度值的類型

     【可用于更新ProgressBar】如果要設置進度,則第二個參數一般設置為Integer
    
  3. Result:后臺執行任務完成之后返回結果的類型

     【耗時操作結束后返回的類型,比如返回Bitmap】
    

    你設置的第三個參數類型是什么,doInBackGround的返回值就是什么類型



    doInBackground返回的Bitmap最終會傳遞到onPostExecute(Bitmap bitmap)中作為參數
    【AsyncTask底層是把異步耗時任務得到的數據result通過handler發送到了主線程】

2.2 必須實現AsyncTask的抽象方法

執行順序從上往下。案例代碼如下面2.3節代碼所示:
(1)onPreExecute:執行后臺耗時操作前被調用,通常用于完成一些初始化操作
(2)doInBackGround:必須實現,異步執行后臺線程將要完成的任務【該方法在子線程運行】
(3)onProgressUpdate:在doInBackGround方法中調用publishProgress方法,就可以更新
任務的執行進度
(4)onPostExecute:當doInBackGround完成后,系統會自動調用,并將doInBackGround方法返回的值傳給該方法

2.3 AsyncTask的用法案例

1.實例化自定義AsyncTask類,傳入一個圖片url和ImagView,execute執行。

new MyAsyncTask(url, imageView).execute();

2.自定義AsyncTask類代碼案例:

private class MyAsyncTask extends AsyncTask<Void, Void, Bitmap> {

    private String mUrl;
    private ImageView mImageView;

    public MyAsyncTask(String url, ImageView imageView) {
        mUrl = url;
        mImageView = imageView;
    }

    @Override
    protected Bitmap doInBackground(Void... params) {
        return getBitmapFromUrl(mUrl);
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        if (mUrl.equals(mImageView.getTag()))
            mImageView.setImageBitmap(bitmap);
    }
} 

三、AsyncTask線程池

AsyncTask之所以如此強大,核心功臣就是它背后的線程池。
在AsyncTask中提供有兩種線程池,一個是THREAD_POOL_EXECUTOR,另一個是SERIAL_EXECUTOR。
【但是,特別注意,其實在AsyncTask中只有一個線程池THREAD_POOL_EXECUTOR,只不過SERIAL_EXECUTOR實現了線程隊列,最終還是使用的THREAD_POOL_EXECUTOR】

1、THREAD_POOL_EXECUTOR 
        多個任務可以在線程池中異步并發執行。
2、SERIAL_EXECUTOR
        把多個線程按串行的方式執行,所以是同步執行的。
        也就是說,只有當一個線程執行完畢之后,才會執行下個線程。

3.1 異步、同步、并行、串行的區別

  • 異步:
    發送方發出數據后,不用等接收方發回響應,接著發送下個數據包的通訊方式。
    【比如,主main函數的代碼從上往下執行,new一個Thread并在子線程中途執行了sleep 10秒鐘,而主main函數后面的代碼不需要等子線程sleep完10秒再執行,而是直接繼續執行下面的代碼?!?/li>
  • 同步:
    發送方發出數據后,需要等接收方發回響應以后才發下一個數據包的通訊方式。
    【比如,主main函數的代碼從上往下執行,如果中途執行了sleep 10秒鐘,則后面的代碼都要等10秒后才會執行?!?/li>
  • 并行:
    也稱為并發。從宏觀上來理解,就是在同一時間內同時執行多個線程任務。
    【比如,同時開啟10張圖片下載,宏觀上他們是10張圖同時下載的?!?/li>
  • 串行:
    可以理解為,只有當一個線程執行完畢之后,才會執行下個線程。
    【比如,10張圖片下載線程串行執行,只能是第一張下載完后,才會開始執行下一張圖片下載?!?/li>

3.2 THREAD_POOL_EXECUTOR 異步線程池

ThreadPoolExecutor構造函數需要傳遞以下幾個參數:

       /**
         * @param corePoolSize 線程池的核心運行線程數量
         * 
         * @param maximumPoolSize 線程池中可以創建的最大運行線程數量【包括核心運行線程】
         * 
         * @param keepAliveTime 當線程池線程數量超過corePoolSize時,
         * 多余的空余線程在緩沖隊列的存活時間,超時后將會被移除
         * 
         * @param unit 線程池維護線程所允許的空閑時間的單位,一般設置為秒
         * 
         * @param workQueue 線程池所使用的緩沖隊列,可以設置緩沖隊列容納的線程數量
         * 
         * @param threadFactory 線程工廠,用于創建線程。
         *    當一個異步任務執行execute的時候將會通過該工廠new出一個thread
         * 
         */
        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 threadFactory, defaultHandler);
        }

那么我們該如何理解這幾個參數的意思呢?
首先我們傳入線程池參數:

Executor executor = new ThreadPoolExecutor(3,5,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(5));

然后,同時execute執行10個任務
看圖示解析:


  • 第一個參數傳入是3,表示初始化核心并發運行的有三個線程,當execute任務大于3時,將會放入緩沖隊列中
  • 當緩沖隊列滿了之后,【緩沖隊列最多存5個線程】,此時已經加入進來了8個進程。
  • 但是,這還并沒有結束,我們設置了第二個參數為5,就意味著,在線程池中可以創建的最大運行線程數量為5個?,F在已經有3個核心線程創建了,
    所以還可以再創建兩個線程。整個線程池最大支持容納10個線程。
  • 重點來了:這個時候,將會并發執行5個核心線程,是哪5個呢?答案是:1,2,3,9,10。因為新創建的兩個運行線程也會開始工作。
    【1,2,3線程是核心運行線程,4,5,6,7,8在緩沖隊列,9,10在運行線程池中。】
  • 那么5個線程結束之后?是繼續并發執行3個,還是5個呢?答案是5個,因為運行線程已經創建為5個了。
  • 特別注意:【只有當緩沖隊列滿的時候,才會創建新的運行線程,否則默認只按corePoolSize的數量執行。直到超出maximumPoolSize大小,拋出異?!?/li>
  • 當再加一個線程進來時,AsyncTask中有一個拒絕策略的handler拋出異常,此時線程池已經無法再容納更多線程任務了。
    不多說,上演示圖:


從AsyncTask默認構造的THREAD_POOL_EXECUTOR可以看出,AsyncTask最大支持的緩沖任務隊列是128個。


3.3 SERIAL_EXECUTOR 串行線程池

在ActivityThread中的有一段代碼,設置了當Android 版本api 小于12,也就是版本小于3.1時,默認是使用AsyncTask默認的異步線程池THREAD_POOL_EXECUTOR
Android 3.1 之后使用的是 SERIAL_EXECUTOR

四、執行AsyncTask的兩個方法:

1、execute 
    第一個執行方法,使用的是AsyncTask默認的線程池
2、executeOnExecutor
    第二個執行方法,需要傳遞進去一個Executor,可以實現自定義線程池

4.1 execute 【默認線程池執行】

剛剛提到了,AsyncTask在3.1版本之前默認使用THREAD_POOL_EXECUTOR線程池,也就是說,3.1版本之前的使用AsynTask,將會是異步并發執行。

4.1.1 api8系統執行execute

我們來看2.3系統同時execute執行10個異步任務,得到的結果:
3.1版本之前的系統默認最大并發執行5個線程,緩沖線程隊列最大128個。雖然開了10個異步任務,但是只能同時并發執行5個,其他的任務都得等前面5個執行完后才繼續執行,接著也是5次并發執行。


4.1.2 api11以上系統執行execute

那么,3.1版本之后系統,默認是使用SERIAL_EXECUTOR串行任務執行,可以預料到異步任務將會是一個個順序執行。

果不其然,5.0系統同時execute執行10個異步任務,是一個個線程按加入順序同步執行的。也就是說,線程池中只有一個核心線程在工作,其他線程都要等之前的線程執行完才能執行。


4.2 executeOnExecutor 【自定義線程池執行,僅支持3.1以上系統】

很顯然,AsyncTask的默認線程池完全不能滿足我們的需求,這個時候就需要用到executeOnExecutor自定義線程池了。

但是,現在問題來了,3.1版本以下的系統是不支持自定義線程池執行的!所以3.1版本以下的系統需要實現同步執行異步任務,只能用Thread自己定義線程池。


4.2.1 使用默認提供的AsyncTask.THREAD_POOL_EXECUTOR線程池

AsyncTask.THREAD_POOL_EXECUTOR,代碼如下:

new MyAsyncTask(progressBar).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

這樣,3.1版本以上的系統便可以實現異步并發執行任務了。我們可以看到,和3.1版本之前執行默認execute一樣,采用了AsyncTask默認的線程池,最大并發執行5個線程,后面的線程都只能等之前5個結束之后再執行。


4.2.1 自定義線程池

重磅炸彈來了,我們可以自己定義線程池來執行異步并發,這樣我們就可以自由控制線程池了。
下面代碼可以實現10個核心任務并發執行,而且緩存隊列最多能夠存儲100個任務,當隊列滿了之后還可以創建40個運行線程,瞬間爆炸。

Executor executor = new ThreadPoolExecutor(10,50,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(100));

new MyAsyncTask(progressBar).executeOnExecutor(executor);
new MyAsyncTask(progressBar2).executeOnExecutor(executor);

如圖所示:10個線程并發執行了


五、總結

前面已經提到過了AsyncTask的優點,我們在開發中如果合理運用AsyncTask,將會比自己new Thread好很多。那么,就來總結一下AsyncTask的最佳適用場景:

  • AsyncTask比較適合有大量線程執行的情況,讓線程池去管理會更加高效。
  • 假如想讓幾個線程按順序執行時,可以使用AsyncTask。(前提是系統版本不能小于11)
  • 盡量使用自定義線程池,按需調節線程池參數。

5.1 AsyncTask缺點

  • 之前已經解析過了,AsyncTask在3.1系統以下默認使用AsyncTask的線程池,不可以自定義線程,假如在線程不多的情況下,是很耗性能的。因為假如你只有一個異步任務,還是會創建另外4個核心線程。

  • 容易造成內存泄露,如果持有context引用的話


線程太多有風險,開的時候需謹慎吶!下一節,將帶來AsyncTask的源碼解析部分。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容