AsyncTask源碼分析

基礎知識點

1.線程池Executor
2.Future
3.Callable
4.中斷線程
5.Handler
6.枚舉
7.泛型
8.單例模式
9.可變參數

AsyncTask設計思想

1.回調函數
2.如何有序調度任務
3.串行或并行執行任務
4.中斷任務
5.線程調度

回調函數

AsyncTask,是可異步執行任務的工具類。開發者只需要至少重寫一個回調函數doInBackground(Params... params);就能完成線程調度

我們看看它的使用方法:
我們寫一個抽象類AsyncTask的具體子類MyAsyncTask,并覆寫其四個函數。

private class MyAsynckTask extends AsyncTask<Long, String, String> {
    /**
     * 任務被執行前,會被調用,UI線程做一些提示操作,如顯示進度條
     */
    @Override protected void onPreExecute() {
      super.onPreExecute();
      Log.d(TAG, "onPreExecute: " + Thread.currentThread().getName());
    }  

    /**
     * 在后臺子線程執行任務,返回類型String
     * @param params  模擬計算時間
     * @return
     */    

    @Override protected String doInBackground(Long... params) {
      try {
        TimeUnit.SECONDS.sleep(params[0]);//模擬后臺耗時計算,參數大小作為計算時間
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      Log.d(TAG, "doInBackground: " + Thread.currentThread().getName());
      return Thread.currentThread().getName();
    }


    /**
     * 任務執行完畢后,在主線程回調這個函數。
     * @param s  后臺任務返回的結果
     */
    @Override protected void onPostExecute(String s) {
      super.onPostExecute(s);
      Log.d(TAG, "onPostExecute: " + s + " ---" + Thread.currentThread().getName());
    }

    /**
     * 任務中途被取消會回調它
     */
    @Override protected void onCancelled() {
      super.onCancelled();
      Log.d(TAG, "onCancelled: ");
    }
  }

我們主要看doInBackground(Parms...parm)函數,它是什么時候被回調的呢?在后臺FutrueTask類執行call()函數時回調。 這里WorkerRunable是一個實現了Callable接口的實現類,它可以執行任務后能攜帶一個返回值Result,并拓展了可以持有一個Parms參數。我們看看它的回調時刻:

  public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
             /省略一些代碼
            Result result = null;
            result = doInBackground(mParams);
            ````省略一些代碼
            return result;
        }
  };

我們在UI線程使用它即可:

   new MyAsynckTask().execute(100l);
   new MyAsynckTask().execute(5l);
   new MyAsynckTask().execute(5l);
   new MyAsynckTask().execute(5l);

輸出結果:

04-03 22:44:47.713 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:47.713 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:47.713 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:47.714 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:52.746 4256-4295/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #1
04-03 22:44:52.747 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #1 ---main
04-03 22:44:57.788 4256-4376/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #2
04-03 22:44:57.788 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #2 ---main
04-03 22:45:02.832 4256-4450/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #3
04-03 22:45:02.833 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #3 ---main
04-03 22:45:07.875 4256-4450/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #3
04-03 22:45:07.875 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #3 ---main

所以,我們在基類里面寫一些函數鉤子,并調用,然后然使用者在具體實現類里面去實現。

如何結合線程池串行調度任務

從上面的輸出結果來看,AsyncTask是默認串行執行的(不考慮版本問題)。它用了兩個調度器Executor實現。

  • SERIAL_EXECUTOR,用隊列保存等待被被執行的任務
    *THREAD_POOL_EXECUTOR。異步執行任務

SerialExecutor串行調度。我們把任務裝入一個隊列數據結構,一個個排隊執行, 代碼:

 private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();//一個環形隊列用來保存提交過來的任務。
    Runnable mActive;//表示可立即執行的任務。

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }
    
    protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

我們直接看SerialExecutor在同步方法execute(r)中的實現,它有兩步:
第一步: 保存任務到隊列。新建一個Runable包裹提交進來的任務,在try作用域里面先執行r.run(),必須等任務執行完后,在finally作用域中會執行 scheduleNext(),來調度下一個任務。因此任務是串行執行的。最后我們會把這個新建的Runnable對象放入隊列mTasks中。

第二步:查看是否有可立即執行的任務mActive。 如果有,則立即放入THREAD_POOL_EXECUTOR執行。

我們再看看scheduleNext()函數,就是調用mTasks.poll())從隊列得到下一個任務,執行。
文字描述也許會迷糊,不如看下圖[采用OmniGraffle軟件制作]:

串行調度圖.png

至此,為什么要兩個調度器呢?我認為是為了遵守單一職責的設計原則。一個調度器只負責一件事,前面SerialExecutor我們已經分析。那THREAD_POOL_EXECUTOR線程池調度器則是根據CPU數構造線程Thread緩存池,節約資源,提高AsyncTask執行性能。

3.為什么要串行執行任務?不串行可以嗎?

為什么要串行執行,我認為是為了保證數據一致性。正如官方文檔說的:

tasks are executed on a single thread to avoid common application errors caused by parallel execution.</p>

  • If you truly want parallel execution, you can invoke
    executeOnExecutor(java.util.concurrent.Executor, Object[])} with
  • THREAD_POOL_EXECUTOR.

意思是說,串行模式能夠避免一些并發異常。如果你真的需要并發執行,這可以調用executeOnExecutor(),傳入自定義一個Executor,這樣就不會去調用SerialExecutor了。而我認為AsyncTask只適合執行一些小任務,因為在串行執行時,如果某個任務執行時間過長,或者中途阻塞,占有CPU太久,則會導致后面排隊的任務等待過久。

4.如何取消任務

我們知道為了能得到后臺線程運行后的結果,我們的任務實現了的Callable接口,它和Runnable區別是有一個返回值?,F在我們想取消任務,我們可以實現Future接口,它除了能通過get()得到返回值,還有cancle()方法用來取消任務,done()回調函數監聽是否任務結束

在AsyncTask中,我們用FutureTask這個系統SDK為我們實現了Ruanble和Future的實現類,它可以通過一個Callable類型的對象參數構造出來,這里使用我們的是上文中的WorkRunnable作為參數。
代碼如下:

FutureTask mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };

那么如果我們想取消任務,現在是不是直接調用mFuture.cancel(true)強制中斷線程就好了?答案是否定的。因為mFuture.cancel(true)方法內部實現機制其實是:

   //.....省略一些代碼
   if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
   //.....省略一些代碼

意思是如果任務正在運行,則interrupt()中斷線程。而在這里,如果任務正好還在排隊尚未執行,而我們想取消未執行的任務,那么直接調用mFuture.cancel(true)就無效!因此,我們還需要考慮未執行的任務如何中斷。

令人驚喜的是,在AsyncTask中還提供了一個對象:

 private final AtomicBoolean mCancelled = new AtomicBoolean();

它是一個原子類,通過CAS機制保證了mCancelled可見性,它內部保存了當前任務是否被取消的信號。

那么正確完整的取消任務方案是分兩個步驟:
第一步:調用AsyncTask中的cancel方法:

    public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);//用來取消未執行的任務
        return mFuture.cancel(mayInterruptIfRunning);//用來取消正在執行的任務可能會拋出異常
    }

第二步:在doInBackground回調函數中循環去判斷mCancelled中的值確認將要執行的任務是否要求取消:

 @Override protected String doInBackground(Long... params) {
      while (!isCancelled()){
        //todo something...
      }
      return null;
    }

至此,我們成功實現了取消任務功能。

線程調度

在安卓系統中,我們要從子線程更新UI主線程,當然是利用handler+looper+messageQueue機制,在AsyncTask中不例外也是如此。但是除此之外,我們可以學習如何通過單例模式構造一個handler,如何強制構造一個主線程執行的handler。
1.單例模式:

 private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

看看源碼中的這個單例模式,比較簡陋,也不是什么double-check :)

2.強制在主線程執行handler

 private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }
}

如上在父類構造函數 [super(Looper.getMainLooper())] 初始化Looper。

執行流程圖

總調度圖.png

總結

AsyncTask作為Activity內部類使用的時候還會因為持有acitivity對象引用,在后臺導致內存泄漏問題。推薦閱讀其他執行異步任務類框架, 如Job、Rxjava等。

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

推薦閱讀更多精彩內容

  • 使用AsyncTask的一般步驟是: 定義一個類繼承自AsyncTask,實現抽象方法 new 一個AsyncTa...
    yk_looper閱讀 396評論 0 2
  • 轉載請注明出處:http://www.lxweimin.com/p/531657db36f4 上一篇主要說了下Asy...
    朋永閱讀 285評論 0 0
  • 在Android開發道路上,有一個類你是無論如何都無法繞過去的。那就是AsyncTask,因為使用的足夠簡單,在面...
    人失格閱讀 582評論 1 3
  • 元亨,利貞;勿用有攸往,利建侯。 學習了建構主義之后,接觸到任何好知識,都想著如何轉變成可以讓大家都能掌握的課程,...
    承謙閱讀 445評論 0 1
  • 緣起 可能只是偶然,可能是想了解下王興這個人。 英語representation講公司或創始人文化時,有人講了王興...
    im天行閱讀 571評論 0 1