AsyncTask實現原理

李納斯小盒發表於2018-05-21

Android開發知識 onGithub

說到AsyncTask,它幾乎能夠用最簡單的方式將操作非同步執行,再呈現給UI執行緒。你不需要自己寫一個執行緒,然後通過Handler去將結果返回給UI執行緒。只要簡單的重寫 onPreExecutedoInBackgroundonProgressUpdate,onPostExecute四個方法,然後呼叫execute方法,是不是超級簡單。

可是,你瞭解AsyncTask是如何操作你的任務的嗎?它是如何封裝Handler將非同步任務執行結果返回給UI執行緒的?使用AsyncTask有哪些需要注意的?本文從原始碼分析AsyncTask的工作原理,部分內容來自原始碼。

1.任務執行方式

目前的AsyncTask預設的任務處理是在單執行緒中順序執行,之前有過一段時間可以線上程池中執行,不信你看execute方法的註釋:

AsyncTask實現原理

我簡單的翻譯一下,execute方法將佇列中的任務在一個後臺的單執行緒或執行緒池中執行。AsyncTask的第一個版本是順序執行。在1.6(DONUT)版本後,改成多工的執行緒池中執行。但在3.2(HONEYCOMB)後,為了避免一些執行緒同步的錯誤,又改回在單執行緒中執行。如果想線上程池中執行,可以這樣:

new AsynTask().executeOn(AsyncTask.THREAD_POOL_EXECUTOR,"")
複製程式碼

顯然這種方法,並不建議。

既然說到AsyncTask.THREAD_POOL_EXECUTOR,它是什麼呢?

public static final Executor THREAD_POOL_EXECUTOR  = 
      new ThreadPoolExecutor(CORE_POOL_SIZE, 
          MAXIMUM_POOL_SIZE, 
          KEEP_ALIVE,                
          TimeUnit.SECONDS, 
          sPoolWorkQueue, 
          sThreadFactory);
複製程式碼

THREAD_POOL_EXECUTOR是一個執行緒池的執行器(有關執行緒池的可以參考 這篇文章。在這裡你只要瞭解它是一個核心執行緒數量是CPU數+1,最大執行緒數量是2*CPU數量+1就可以了。

SerialExecutor

話說回來,單執行緒順序執行是如何執行的?請看:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
複製程式碼

這個sDefaultExecutor是AsyncTask任務執行器。看過原始碼你會發現有這樣一個方法:

/** @hide */
public static void setDefaultExecutor(Executor exec) {        
    sDefaultExecutor = exec;
}
複製程式碼

sDefaultExecutor是可以設定的,只不過你呼叫不了,被隱藏了(@hide)。

那麼SERIAL_EXECUTOR是什麼呢?它是一個SerialExecutor的例項。

AsyncTask實現原理

可以看到,在execute中會呼叫offer方法會將Runnable r包裝一下放到ArrayDeque佇列裡,包裝的新Runnable保證原來的Runnable執行之後會去取佇列裡的下一個Runnable,從而不會導致中斷。 scheduleNext做了什麼呢?可以看到scheduleNext是從佇列中取出Runnable然後交給THREAD_POOL_EXECUTOR執行。也就是說SerialExecutor只是將任務按先後順序排列到佇列中,真正執行任務的是THREAD_POOL_EXECUTOR

2.任務執行過程

在你呼叫execute的時候會這樣:

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {    
  return executeOnExecutor(sDefaultExecutor, params);
}
複製程式碼

你看,最後還是會呼叫到executeOnExecutor,預設傳了一個SERIAL_EXECUTOR。並且,看見那個@MainThread了吧,execute一定在主執行緒呼叫。

請看executeOnExecutor:

AsyncTask實現原理

每個AsyncTask都有一個Status,代表這個AsyncTask的狀態,Status是一個列舉變數,每一個狀態在這個Task的生命週期裡賦值一次,也就是這個Task一定會經歷 PENDING -> RUNNING -> FINISHED 的過程。 PENDING代表Task還沒有被執行,RUNNING代表當前任務正在執行,FINISHED代表的是onPostExecute方法已經執行完了,而不是doInBackground

/** 
  * Indicates the current status of the task. Each status will be set only once 
  * during the lifetime of a task. 
  */
 public enum Status {       
    PENDING,    
    RUNNING,   
    FINISHED,
}
複製程式碼

話說回到executeOnExecutor中,如果當前的Task的狀態不是PENDING,那麼就會丟擲異常。也就是同一個Task,你只能execute一次,直到它的非同步任務執行完成,你才可以再次呼叫他的execute方法,否則一定會報錯。 然後呼叫onPreExecute方法,之後會提交給SERIAL_EXECUTOR執行。但是這個mWorker是什麼?mFuture是什麼?

mWorker

mWorkerWorkerRunnable的具體實現,實現Callable介面,相當於一個能夠儲存引數,返回結果的Runnable

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {    
  Params[] mParams;
}
複製程式碼

當你建立一個AsyncTask的時候就會建立mWorkermFutureTask

AsyncTask實現原理

可以看到,mWorkercall方法主要的工作是設定call是否被呼叫,呼叫你重寫的doInBackground方法,獲得Result(這個Result的型別就是你宣告AsyncTask時傳入的型別),再將Result呼叫postResult方法返回。關於postResult請往下看。

mFuture

可以看到mFuture中有一個postResultIfNotInvoked(get());方法,通過get方法獲得mWorker的執行結果,然後呼叫postResultIfNotInvoked方法,由於某些原因,mWorkercall可能沒有執行,所以在postResultIfNotInvoked中能夠保證postResult一定會執行一次,要不在mWorkercall中執行,要不在postResultIfNotInvoked中執行。

private void postResultIfNotInvoked(Result result) {    
      final boolean wasTaskInvoked = mTaskInvoked.get();    
      if (!wasTaskInvoked) {        
          postResult(result);    
      }
}
複製程式碼

那麼這個postResult是幹什麼的?

private Result postResult(Result result) {        
    @SuppressWarnings("unchecked")    
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,  
                  new AsyncTaskResult<Result>(this, result));    
    message.sendToTarget();    
    return result;
}
複製程式碼

可以看到postResult實際上是獲得了一個AsyncTask內部的一個Handler,將result包裝在AsyncTaskResult中,並將它放在message傳送給Handler。

那麼AsyncTaskResult是如何封裝的?

private static class AsyncTaskResult<Data> {    
      final AsyncTask mTask;    
      final Data[] mData;    
      AsyncTaskResult(AsyncTask task, Data... data) {        
          mTask = task;        
          mData = data;    
      }
}
複製程式碼

可以看到包含AsyncTask的例項(mTask)和資料(mData)。當將任務執行的結果返回時,mData儲存的是Result,當更新進度的時候mData儲存的是和Progress型別一樣的資料。你可以往下看。

獲取執行結果和更新執行的進度

先說一說Handler

AsyncTask實現原理

每個AsyncTask都會獲得一個InternalHandler的例項。可以看到,InternalHandler繫結到了主執行緒的Looper中(關於Looper與Handler的關係,可以參考這篇文章,所以你在非同步執行緒中執行的結果最終都可以通過InternalHandler交給主執行緒處理。再看handlerMessage方法,獲得AsyncTaskResult物件,如果傳的是MESSAGE_POST_RESULT型別,就呼叫AsyncTask的finish方法(別忘了result.mTask其實就是當前的AsyncTask)。

finish做了什麼?

private void finish(Result result) {    
      if (isCancelled()) {        
          onCancelled(result);    
      } else {        
          onPostExecute(result);    
       }    
      mStatus = Status.FINISHED;
}
複製程式碼

可以看到,判斷你是否取消了任務,取消則優先執行onCancelled回撥,否則執行onPostExecute,並更改Task的狀態。

如果是一個MESSAGE_POST_PROGRESS,就會執行onProgressUpdate方法。那MESSAGE_POST_PROGRESS的資訊是誰去傳送的呢?請看:

protected final void publishProgress(Progress... values) {    
    if (!isCancelled()) {        
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,                
               new AsyncTaskResult<Progress>(this, values)).sendToTarget();    
    }
}
複製程式碼

也就是當你呼叫publishProgress的時候,會將傳遞的values包裝成AsyncTaskResultAsyncTaskResult的mData會儲存進度的資料,將message傳送給handler。

有個方法需要說明一下,就是cancle方法。

public final boolean cancel(boolean mayInterruptIfRunning) {    
      mCancelled.set(true);    
      return mFuture.cancel(mayInterruptIfRunning);
}
複製程式碼

作用是設定被取消的狀態,然後取消FutureTask的執行。當task已經執行完了,或已經被取消,或因為某些原因不能被取消,會返回false。如果任務已經執行,那麼根據mayInterruptIfRunning決定是否打斷(interrupt)當前正在執行Task的執行緒。 呼叫這個方法會在doInBackground返回後回撥onCancelled方法,並且onPostExecute不會執行,所以當你需要取消Task的時候記得在doInBackground通過isCancelled檢查返回值。


注意事項

1. 由於AsyncTask是單執行緒順序執行的,所以不要用AsyncTask執行耗時太久的操作,如果有很多耗時太久的執行緒,最好使用執行緒池。

2. onPreExecuteonProgressUpdateonPostExecute都是在UI執行緒呼叫的,doInBackground在後臺執行緒執行。

3. 呼叫cancel方法取消任務執行,這個時候onPostExecute就不會執行了,取而代之的是cancel方法,所以為了儘快的退出任務的執行,在doInBackground中呼叫isCancelled檢查是否取消的狀態。

4. 其他

  • AsyncTask類一定要在主執行緒載入
  • AsyncTask類的例項一定在主執行緒建立
  • execute方法一定在主執行緒呼叫
  • 不要主動呼叫onPreExecute等方法
  • 任務只能在完成前執行一次。

相關文章