AsyncTask:一只命途多舛的小麻雀

AsyncTask:一只命途多舛的小麻雀

麻雀雖小


AsyncTask是一只命途多舛的小麻雀,為什么說它命途多舛,因為它一直被改,從Android 1.6之前,然后1.6到2.3,再從3.0到現在(其實5.1開始后也有細微的改動),反反復復,從串行到并行,再恢復至串行,可見其內心之糾結,盡管如此,它還是不斷被開發人員各種吐槽,內存泄露,不靠譜的cancel等等,真可謂命途多舛。

可是天將降大任于斯人也,它畢竟是一只小麻雀啊,不到三百行代碼量的一只小麻雀,其特點便是麻雀雖小五臟俱全,因此好好解讀一下源碼是很有必要的。如何來解讀呢,不妨我們自己來創建一只簡化版的小麻雀——SimpleAsyncTask,通過這種方式來加深理解。

作為一個標題黨,小麻雀已經完成它的使命了,其實這篇文章真正的標題應該是《從FutureTask到AsyncTask》或者《自己寫個AsyncTask》,以下,開始正題。

一 需求和api選型

想想看我們對于AsyncTask的需求是什么,大概會有以下幾點:

  1. 能定義以下執行過程的操作:預執行、執行后臺任務、執行進度反饋、執行完畢、終止線程執行
  2. 能停止線程任務
  3. 子線程能與UI線程交互(反饋線程執行進度和執行結果)

第1點,我們定義這些方法目的是讓調用者重寫而實現各種交互,因此這5種操作實際上是5個抽象方法(或者空方法),我們需要在SimpleAsyncTask類中合適的時機去調用這5個抽象方法,顯然,這是模板方法模式(Template Method)的應用。

對于第2點,如何停止線程?這方面的最佳實踐是中斷+條件變量,縱觀java.util.concurrent,有個api很適合這個場景,那便是Future和Callback,而作為Future的唯一實現——FutureTask便是我們寫AsyncTask的重點。

第3點,看到子線程和UI線程,so easy呀,有了Handler,麻麻再也不用擔心線程之間的通訊了,關于Handler,如果你閑的蛋疼的話不妨看看筆者寫的《Handler和他的小伙伴們》上。 有一點要注意的是,AsyncTask會在各Activity中實例化,有可能在主線程或子線程中實例化,這么多的AsyncTask實例中,我們只需要一個持有mainLooper的Handler的實例,因此它將是一個單例對象,單個進程內共享

確定好需求和api選型之后,接下來我們來寫代碼。

二 使用Template Method

首先定義五個空方法:預執行、執行后臺任務、執行進度反饋、執行完畢、終止線程執行。

public abstract class SimpleAsyncTask<Params, Progress, Result> {

    /**
     * 模板方法1-預執行,應在線程啟動之前執行
     */
    protected void onPreExecute() {
    }

    /**
     * 模板方法2-執行后臺任務,應該在線程體中調用,即在FutureTask中調用
     */
    protected abstract Result doInBackground(Params params);

    /**
     * 模板方法3-執行進度反饋,應該在Handler中調用
     */
    protected void onProgressUpdate(Progress progress) {
    }

    /**
     * 模板方法4-執行完畢,應該在Handler中調用
     */
    protected void onPostExecute(Result result) {
    }

    /**
     * 模板方法5-終止線程執行,應該在Handler中調用
     */
    protected void onCancelled() {
    }
}

這幾個方法見名知意,同時從各自攜帶的參數類型可以清晰的理解幾個泛型的作用:

  • Params:執行后臺任務的時候使用
  • Progress:反饋進度的時候使用
  • Result:反饋結果的時候使用
    (注:這里為了代碼簡潔,去掉了Params和Progress的可變參數)

當然,模板方法模式最重要的是我們在何時去調用這些抽象的模板方法,很顯然,doInBackground作為后臺執行的邏輯,應該在線程中調用,也就是下文會講到的FutureTask,而其他幾個方法跟UI有關系,會在主線程中執行,因此它們會在Handler中調用到。

三 FutureTask開發

上文提到,Future和Callable是java.util.concurrent中提供了取消任務的一組api,要理解AsyncTask的話,需要先理解下這對組合。以下做個簡單的介紹:

  1. Callable與Runable

    public interface Callable<V> {
     V call() throws Exception;
    }

    簡單來講,Callable接口等價于Runable,call()等價于run(),區別在于它是有返回值的。

    我們可以通過ExecutorService調用Callable,執行后將返回Future對象,比如:


    Future<String> future = Executors.newSingleThreadExecutor().submit(mCallable);
  2. Future

    public interface Future<V> {
     boolean cancel(boolean mayInterruptIfRunning);
    
     boolean isDone();
    
     V get() throws InterruptedException, ExecutionException;
    
     V get(long timeout, TimeUnit unit)
         throws InterruptedException, ExecutionException, TimeoutException;
    }

    Future接口兩個方法著重理解下,一是cancel(boolean mayInterruptIfRunning),顧名思義就是終止線程,二是get(),它會阻塞線程,直到Callable的call()返回對象,并以此作為返回值。至于mayInterruptIfRunning這個boolean值的含義,大家看看FutureTask中相應的源碼就直到了,其實只是多了thread.interrupt()的邏輯而已。結合Callable的代碼,Future的使用如下:

    Future<String> future = Executors.newSingleThreadExecutor().submit(mCallable);
    //阻塞線程,等待Callable.call()的返回值
    String result = future.get();
  3. FutureTask

    AsyncTask:一只命途多舛的小麻雀

    FutureTask的繼承關系


    從FutureTask的繼承關系來看,它既是Runable也是Future,所以我們可以把當做Runable來使用,同時它也具備Future的能力,可以終止線程,可以阻塞線程,等待Callable的執行,并獲取返回值。另外要注意的是,它的構造函數是public FutureTask(Callable<V> callable),因此實例化FutureTask時需要Callable對象作為參數。

    關于這部分基礎知識的demo代碼在此處,有需要的可以跑起來看看。

  4. SimpleAsyncTask中的FutureTask
    介紹完Future和Callable的基礎知識后,我們回歸正題。FutureTask在AsyncTask里充當了線程的角色,因此耗時的后臺任務doInBackground應該在FutureTask中調用,同時我們還要提供線程池對象來執行FutureTask,代碼如下:

    public abstract class SimpleAsyncTask<Params, Progress, Result> {
    
     //...省略部分代碼
     private static final Executor EXECUTOR = Executors.newCachedThreadPool();
     private WorkerRunnable<Params, Result> mWorker;
     private FutureTask<Result> mFuture;
    
     public SimpleAsyncTask() {
         mWorker = new WorkerRunnable<Params, Result>() {
             @Override
             public Result call() throws Exception {
                 //調用模板方法2-執行后臺任務
                 Result result = doInBackground(mParams);
                 //提交結果給Handler
                 return postResult(result);
             }
         };
    
         //此為線程對象
         mFuture = new FutureTask<>(mWorker);
     }
    
     public void execute(Params params) {
         mWorker.mParams = params;
         //在線程啟動前調用預執行的模板方法,意味著它在調用AsyncTask.execute()的所在線程里執行,如果是在子線程中,則無法處理UI
         //調用模板方法1-預執行
         onPreExecute();
         //執行FutureTask啟動線程
         EXECUTOR.execute(mFuture);
     }
    
     private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
         Params mParams;
     }
     //...省略部分代碼
    }

四 Handler開發

到目前為止,我們已經消費掉了兩個模板方法,分別是onPreExecute和doInBackground,此時還剩下3個模板方法,他們都需要有UI交互的,因此他們將在Handler中被調用。

首先,對于終止線程和線程執行完畢這兩個方法,我們都稱之為線程finish了,所以我們先定義個finish(Result result)方法,如下:

private void finish(Result result) {
    if (isCancelled()) {
        //調用模板方法:終止線程執行
        onCancelled();
    } else {
        //調用模板方法:線程執行完畢
        onPostExecute(result);
    }
}

接下來,開始寫關鍵的Handler類對象,因為該Handler位于SimpleAsyncTask內部,因此把它命名為InternalHandler,這個靜態內部類有幾點要注意:

  1. 它是靜態的單實例,所有AsyncTask對象共享
  2. 它必須持有mainLooper對象,才能與主線程進行交互
  3. 注意它調用幾個模板方法的時機
    代碼如下:

    public abstract class SimpleAsyncTask<Params, Progress, Result> {
    
     //省略部分代碼
    
     /**
      * 一個靜態的單例對象,所有AsyncTask對象共享的
      */
     private static InternalHandler sHandler = new InternalHandler();
    
     private static class InternalHandler extends Handler {
         /**
          * 注:此為android 22之后的寫法,構造函數里默認指定mainLooper對象
          * 它的好處是無論Handler在什么時機被實例化,都可以與主線程進行交互
          * 相比之下,之前版本的AsyncTask必須在ActivityThread中執行AsyncTask.init()
          */
         public InternalHandler() {
             super(Looper.getMainLooper());
         }
    
         @Override
         public void handleMessage(Message msg) {
             AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
             switch (msg.what) {
                 case MESSAGE_POST_RESULT:
                     //調用模板方法4和5,取消或者完成
                     result.mTask.finish(result.mData);
                     break;
                 case MESSAGE_POST_PROGRESS:
                     //調用模板方法3-執行進度反饋
                     result.mTask.onProgressUpdate(result.mData);
                     break;
             }
         }
     }
    
     /**
      * 由于InternalHandler是靜態內部類,無法引用外部類SimpleAsyncTask的實例對象,
      * 因此需要將外部類對象作為屬性傳遞進來,所以封裝此類
      */
     private static class AsyncTaskResult<Data> {
         final SimpleAsyncTask mTask;
         final Data mData;
    
         AsyncTaskResult(SimpleAsyncTask task, Data data) {
             mTask = task;
             mData = data;
         }
     }
    
     //省略部分代碼
    }

    到目前為止5個模板方法都已經調用到了,其中四個方法均有觸發的時機和調用的時機,除了onProgressUpdate,它只有調用,但并沒有觸發的時機,因此,我們還要提供一個方法,供調用者主動觸發:

    protected final void publishProgress(Progress progress) {
     if (!isCancelled()) {
         AsyncTaskResult<Progress> taskResult = new AsyncTaskResult<>(this, progress);
         sHandler.obtainMessage(MESSAGE_POST_PROGRESS, taskResult).sendToTarget();
     }
    }

    寫到這里,我們這只簡化版的小麻雀基本上已經完成,當然對比源碼還是有一點點細微的不同,大家可以自行對比一下。通過自己寫個SimpleAsyncTask的方式可以幫助我們更好的理解源碼,以上所寫的完整代碼在此

五 關于其他細節

  • 串行or并行?
    在SimpleAsyncTask中,我們使用

    private static final Executor EXECUTOR = Executors.newCachedThreadPool()作為線程池,而實際上,源碼中的默認線程池是自定義的,這個類是SerialExecutor,從類的命名上看,Serial是串行的意思,所以很明顯,AsyncTask默認是串行的。除此之外,AsyncTask里還有個線程池 THREAD_POOL_EXECUTOR,實在需要并行的話我們就用這個線程池。

    如果都些都不滿足要求,我們也可以自定義符合自己業務要求的線程池,并通過setDefaultExecutor(Executor exec)改變默認的線程池。

  • 不能執行多次
    AsyncTask只能執行一次,類似這樣的代碼是會拋異常的:

    MyAsyncTask asyncTask = new MyAsyncTask();
    asyncTask.execute();
    asyncTask.execute();

    原理很簡單,AsyncTask會判斷當前狀態,如果是RUNNING或者FINISHED狀態,則直接拋異常:

    //execute最終會執行到executeOnExecutor
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
              Params... params) {
      if (mStatus != Status.PENDING) {
          switch (mStatus) {
              case RUNNING:
                  throw new IllegalStateException("Cannot execute task:"
                          + " the task is already running.");
              case FINISHED:
                  throw new IllegalStateException("Cannot execute task:"
                          + " the task has already been executed "
                          + "(a task can be executed only once)");
          }
      }
      //省略部分代碼
    }
  • AsyncTask是否只能在主線程創建和運行?
    比如,這樣的代碼能否正常使用:

    new Thread(new Runnable() {
      @Override
      public void run() {
          MyAsyncTask myAsyncTask = new MyAsyncTask();
          mSimpleAsyncTask.execute("task1");
      }
    }).start();

    從Android4.1(API 16)之后其實已經沒什么問題了,通過源碼來理解的話非常簡單,比如在我們自己寫的SimpleAsyncTask中,重寫了Handler的構造器,如下:

    public InternalHandler() {
      super(Looper.getMainLooper());
    }

    這樣一來,無論AsyncTask在什么時候創建,實例化出來的靜態InternalHandler對象都持有mainLooper,都能與主線程進行通訊。有一點小區別,如果在子線程中調用execute(),則onPreExecute不能執行UI的操作,否則會拋異常。

    要注意的是,這是Android 5.1(API 22)以及之后的寫法,API 16~API 21是另外一種寫法,參考下一點。

  • AsyncTask.init()
    AsyncTask中有個隱藏方法init()(API 22之后已經移除)

    /** @hide Used to force static handler to be created. */
    public static void init() {
      sHandler.getLooper();
    }

    與此對應的,在ActivityThread的main方法中會調用AsyncTask.init(),目的是什么?

    由于AsyncTask中的Handler是靜態的單實例對象,他會在類加載期間進行初始化,萬一調用者在子線程中加載AsyncTask,將會導致同一進程的所有AsyncTask無法使用。因此,系統先下手為強,一開始就直接加載,保證該Handler持有主線程的mainLooper,能正常進行UI交互。

  • postResultIfNotInvoked的作用是什么?
    AsyncTask有很多邏輯干擾了我們解讀源碼,postResultIfNotInvoked便是其中一個。它實際上是Google解決的一個bug,確保如果cancel()方法過早調用的場景下,onCancelled()仍然能順利的執行,參考stackoverflow這篇文章

    比如,我們自定義的SimpleAsyncTask,如果我們執行以下代碼,onCancelled()是不會執行的,而AsyncTask則可以正常執行:

    mSimpleAsyncTask = new MySimpleAsyncTask();
    mSimpleAsyncTask.execute("task1");
    //馬上終止線程
    mSimpleAsyncTask.cancel(true);

六 最后

AsyncTask作為一只命途多舛的小麻雀,他是一只純粹的麻雀,脫離了低級趣味的麻雀,值得好好品嘗的小麻雀。

參考文章:
http://blog.csdn.net/guolin_blog/article/details/11711405
http://droidyue.com/blog/2015/12/20/worker-thread-in-android/?droid_refer=ninki_posts
http://droidyue.com/blog/2014/11/08/bad-smell-of-asynctask-in-android/index.html
http://stackoverflow.com/questions/25322651/asynctask-source-code-questions


 

文/geniusmart(簡書作者)
 

 本文由用戶 LeonorStack 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!