AsyncTask原始碼解析

HFW發表於2019-04-01

AsyncTask是android為我們提供執行非同步任務的一個輕量的類,可以用來處理耗時操作,並且能夠很方便的將執行結果返回給主執行緒。本篇文章將會通過原始碼分析來介紹AsyncTask的內部實現原理。

目錄

  1. 重要的成員變數
  2. AsyncTask構造分析
  3. 兩個執行緒池
  4. 圖解AsyncTask執行過程
  5. 執行結果是如何被傳遞到主執行緒
  6. onProgressUpdate()是什麼時候呼叫

1. 重要的成員變數

AsyncTask裡面幾個重要的成員變數變數分別為:

名稱 作用 建立 呼叫 備註
THREAD_POOL_EXECUTOR 真正執行任務的執行緒池 在靜態程式碼塊中被建立 在SerialExecutor執行緒池的scheduleNext方法中被呼叫 該執行緒成池的核心執行緒數量是根據手機cup核數-1確定的
sDefaultExecutor 內部建立佇列用於儲存非同步任務 建立類的成員變數的時候被建立 在AsyncTask的execute()中被作為引數傳遞 SerialExecutor類的scheduleNext方法中會將任務新增到THREAD_POOL_EXECUTOR執行緒池中執行
mWorker 任務最終執行方法,其內部的call方法會呼叫doInBackground()方法 在AsyncTask有參構造中建立 WorkerRunnable在FutureTask的run方法中被呼叫該類的call方法 其繼承自Callable方法,一般配合FutureTask使用
mFuture 在其內部會呼叫mWorker的call方法來執行任務 在AsyncTask有參構造中建立 FutureTask在SerialExecutor類的execute方法中被呼叫 該成員變數被AsyncTask的executeOnExecutor()中傳遞到SerialExecutor中
sHandler 用於將在結果返回到主執行緒 在AsyncTask有參構造中通過呼叫getMainHandler來建立 在postResult()中通過複用Message來呼叫 InternalHandler類的Looper是主執行緒的Looper

2. AsyncTask構造分析

在分析AsyncTask之前我們先看看他的構造,我們在使用AsyncTask經常使用空參構造的方式來建立該物件,這個構造方法內部會呼叫他的有參構造。首先有參會先根據是否有Looper來建立Handler。如果傳入的Looper為空或者傳入的Looper不是主執行緒的Looper,則呼叫getMainHandler()來建立Handler;如果是主執行緒的Looper則以此Looper重新new一個Handler。當Handler建立完畢後然後在以次建立WorkerRunnableFutureTask。下面為AsyncTask構造原始碼:

public AsyncTask(@Nullable Looper callbackLooper) {
    //建立Hanlder
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Result result = null;
            try {
                //將程式設定成標準後臺程式
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //獲取非同步執行結果
                result = doInBackground(mParams);
                //將程式中未執行的命令一併送往cup處理
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                //將處理結果返回到主執行緒
                postResult(result);
            }
            return result;
        }
    };
    //FutureTask間接呼叫了WorkerRunnable方法的call方法
    //來獲取執行結果
    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);
            }
        }
    };
}
複製程式碼

從原始碼中我們可以知道,mHandler實際上是InternalHandler,mWorker內部的call()方法會呼叫doInBackground,try塊不管執行結果如何,都會呼叫postResult()來呼叫Hanlder傳送訊息,通知主執行緒最Ui更新操作。先有一個問題,call()方法是在哪裡會被呼叫呢?其實是在mFuture內部的run()方法中呼叫mWorker他的call方法。具體程式碼讀者可以自行查詢專案原始碼,這裡就不多說了。上面提到的mWorker、mFuture會在execute()方法中被呼叫和傳遞,execute()是用於配置和啟動任務的方法,下面為該方法的部分程式碼。

/**
*在主執行緒中執行
*可傳入一個或多個引數
*/
@MainThread
public final AsyncTask。<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}
複製程式碼

3. 兩個執行緒池

executeOnExecutor(sDefaultExecutor, params);方法將引數params和sDefaultExecutor傳入該方法中,並返回一個AsyncTask。這個params我們知道它是我們傳進來的引數,但是sDefaultExecutor是什麼呢?它是一個執行緒池,是一個類的成員變數。

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
複製程式碼

既然我們知道sDefaultExecutor是一個執行緒池,也就是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() {
        //mTask.pll()刪除佇列中的第一個元素,並返回該元素的值
        if ((mActive = mTasks.poll()) != null) {
            //呼叫執行緒池執行非同步
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}
複製程式碼

從上面的程式碼我們可以知道,SerialExecutor類中建立一個雙端佇列ArrayDeque, 用於儲存非同步任務。他還有execute()scheduleNext()方法,execute()內部呼叫了mTasks.offer用於將傳入的非同步任務新增到佇列中,然後在呼叫 scheduleNext()方法。scheduleNext()方法呼叫mTask.poll()方法取出並刪除第一個元素,最後將取出的元素放到執行緒池中。不知道讀者有沒有發現AsyncTask內部其實是有兩個執行緒池SerialExecutorTHREAD_POOL_EXECUTOR,其中SerialExecutor執行緒池主要是用於將任務新增到佇列中,而任務真正的執行是在THREAD_POOL_EXECUTOR執行緒池中。

4. 圖解AsyncTask執行過程

要想知道執行結果是如何被傳遞到執行緒中,我們先搞明白AsyncTask的執行過程。其實讀者從上面的內容中或許能改猜到它的大概執行過程。其實它的執行過程也不復雜我們可以結果下面這張圖進行分析:

AsyncTask執行流程圖

我們在使用AsyncTask的時候會先建立物件,然後呼叫execute()方法傳入引數執行任務:

//建立AcyncTask封裝類
TestAsyncTask asyncTask = new TestAsyncTask();
//傳入引數,執行任務
asyncTask.execute(5,6,7);
複製程式碼

我們在通過上面操作執行任務的時候,其實AsyncTask內部做了一下幾個操作:

  1. 在構造中建立Handler、WorkerRunnable、FutureTask
  2. executeOnExecutor()中校驗該任務是否在任務棧中執行、或者是否已完成過
  3. 如果該未任務在執行,或者未完成過。將會包裝傳入的引數然後再將FutureTask新增到執行緒池中呼叫execute()方法執行非同步
  4. SerialExecutor執行緒池的execute()方法建立Runnable,並新增到佇列中。
  5. scheduleNext()方法取出佇列中的第一個Runnable,加他新增到THREAD_POOL_EXECUTOR執行緒池中開始執行任務
  6. Runnable呼叫FutureTask的run()方法執行WorkerRunnable的call()方法
  7. WorkerRunnable的call()方法執行完,SerialExecutor執行緒池的execute()方法再次呼叫scheduleNext()執行下個任務。

結合上面的執行流程圖我們知道,在經過上面7個步驟非同步任務一個一個的線上程池中被完成。既然我們知道了AsyncTask的大致執行過程,那麼它是如何將執行結果返回到主執行緒呢?下面我們將會來分析。

5. 執行結果是如何被傳遞到主執行緒

我們知道doInBackground()函式是我們的任務具體執行函式。這個函式是在WorkerRunnable的call()函式中被呼叫,從上面的執行過程介紹中我們知道call()方法是在FutureTask的run方法執行的時候被呼叫的。當call()方法在執行完doInBackground()方法得到結果後,會將該結果傳遞給postResult()方法:

private Result postResult(Result result) {
    //obtainMessage方法是從Message池中獲取一個Message物件,避免重複建立。
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    //傳送訊息
    message.sendToTarget();
    return result;
}
複製程式碼

postResult()方法內程式碼也很簡單,首先它會通過Hanlder(注:從文章開始部分我們可以知道,這個Handler的Looper是主執行緒的Looper)在訊息佇列中獲取一個Message物件,然後將結果和定義的標記包裝到Massage中,最後在通過Message物件呼叫sendToTarget()將訊息發出。既然訊息傳送出去了,那麼訊息是在哪裡執行呢?答案是:在InternalHandler類中的handleMessage()中被執行。why?因為getHandler()獲取的是Hanlder是我們在文章開始介紹的建構函式中被getMainHandler()賦值的mHandler,而getMainHandler()中返回的就是InternalHandler。既然我們知道了訊息在哪裡被處理,那麼我們可以看一看它的具體處理邏輯:

public void handleMessage(Message msg) {
    AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
    switch (msg.what) {
        case MESSAGE_POST_RESULT:
            // There is only one result
            result.mTask.finish(result.mData[0]);
            break;
        case MESSAGE_POST_PROGRESS:
            result.mTask.onProgressUpdate(result.mData);
            break;
    }
}
複製程式碼

handleMessage()內部有兩個判斷,如果標識是MESSAGE_POST_RESULT則將結果傳遞給finish()方法。如果標識是MESSAGE_POST_PROGRESS則呼叫onProgressUpdate()用於更新進度。下面我們先看finish()方法的原始碼:


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

finish()方法會判斷是否取消了該任務,如果使用者呼叫了cancel()函式那麼isCancelled()返回的就是true,當使用者取消了任務那麼將會回撥onCancelled(result)函式而onPostExecute()則不會呼叫,反之則會呼叫。

6. onProgressUpdate()是什麼時候呼叫

在分析handleMessage()方法的時候我們留了一個小尾巴,MESSAGE_POST_PROGRESS這個標記訊息在什麼時候發出的?在回答這個問題之前,我們先回憶一下我們在使用doInBackground()的時候,是否有在其內部呼叫publishProgress()函式來更新進入?回憶到這裡答案就很明顯了:通過Handler發生更新進度訊息的操作是在publishProgress()函式中完成的。下面為該函式的原始碼:

@WorkerThread
protected final void publishProgress(Progress... values) {
    //如果任務沒有取消,則發生訊息更新進度
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}
複製程式碼

從上面的原始碼我們可以知道,更新進度的訊息是在子執行緒中傳送的,如果該任務沒有被取消那麼就可以發現訊息。這種通過複用Message物件傳送資訊的方式對效能上有起到優化的作用。讀者可以在文章結尾的參考連結中找到相關的介紹,筆者就不介紹了。

總結

文章到這裡對與AsyncTask的原始碼分析也就介紹完了。在AsyncTask中比較重要的成員變數為:WorkerRunnable、FutureTask已經兩個執行緒池,能夠真正理解AsyncTask的執行過程一定要搞明白他們幾個的呼叫過程。最後感謝您能在百忙之中抽出時間閱讀這篇文章,下一篇文章將會寫一下HandlerThead和IntentService。

參考

ArrayDeque類的使用詳解

Android執行緒優先順序

剖析Android中程式與執行緒排程之nice

帶你輕鬆看原始碼---AsyncTask(非同步任務)

Android Message和obtainMessage的區別

相關文章