Android 原始碼分析之 AsyncTask 原始碼分析

WngShhng發表於2019-03-04

1、AsyncTask的使用

使用 AsyncTask 可以更加簡單地實現任務的非同步執行,以及任務執行完畢之後與主執行緒的互動。它被設計用來執行耗時比較短的任務,通常是幾秒種的那種,如果要執行耗時比較長的任務,那麼就應該使用 JUC 包中的框架,比如 ThreadPoolExecutorFutureTask等。

AsyncTask 用來在後臺執行緒中執行任務,當任務執行完畢之後將結果傳送到主執行緒當中。它有三個重要的泛型別引數,分別是 ParamsProgressResult,分別用來指定引數、進度和結果的值的型別。
以及四個重要的方法,分別是 onPreExecute(), doInBackground(), onProgressUpdate()onPostExecute()
這四個方法中,除了 doInBackground(),其他三個都是執行在UI執行緒的,分別用來處理在任務開始之前、任務進度改變的時候以及任務執行完畢之後的邏輯,而 doInBackground() 執行在後臺執行緒中,用來執行耗時的任務。

一種典型的使用方法如下:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    
    @Override
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            if (isCancelled()) break;
        }
        return totalSize;
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    @Override
    protected void onPostExecute(Long result) {
        showDialog("Downloaded " + result + " bytes");
    }
}
複製程式碼

上面說 AsyncTask 有4個重要的方法,這裡我們覆寫了3個。doInBackground() 執行線上程當中,耗時的任務可以放在這裡進行;onProgressUpdate() 用來處理當任務的進度發生變化時候的邏輯;onPostExecute() 用來處理當任務執行完畢之後的邏輯。另外,這裡我們還用到了 publishProgress()isCancelled() 兩個方法,分別用來發布任務進度和判斷任務是否被取消。

然後,我們可以用下面的方式來使用它:

    new DownloadFilesTask().execute(url1, url2, url3);
複製程式碼

使用AsyncTask的時候要注意以下幾點內容:

  1. AsyncTask 的類必須在主執行緒中進行載入,當在4.1之後這個過程會自動進行;
  2. AsyncTask 的物件必須在主執行緒中建立;
  3. execute() 方法必須在UI執行緒中被呼叫;
  4. 不要直接呼叫 onPreExecute(), doInBackground(), onProgressUpdate()onPostExecute()
  5. 一個AsyncTask物件的 execute() 方法只能被呼叫一次;

Android 1.6 之前,AsyncTask 是序列執行任務的;1.6 採用執行緒池處理並行任務;從 3.0 開始,又採用一個執行緒來序列執行任務。
3.0 之後可以用 executeOnExecutor() 來並行地執行任務,如果我們希望在3.0之後能並行地執行上面的任務,那麼我們應該這樣去寫:

    new DownloadFilesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url1, url2, url3);
複製程式碼

這裡的 AsyncTask.THREAD_POOL_EXECUTOR 是 AsyncTask 內部定義的一個執行緒池,我們可以使用它來將 AsyncTask 設定成並行的。

2、AsyncTask原始碼分析

2.1 AsyncTask 的初始化過程

當初始化一個 AsyncTask 的時候,所有的過載構造方法都會呼叫下面的這個構造方法。這裡做了幾件事情:

  1. 初始化一個 Handler 物件 mHandler,該 Handler 用來將訊息傳送到它所在的執行緒中,通常使用預設的值,即主執行緒的 Handler;
  2. 初始化一個 WorkerRunnable 物件 mWorker。它是一個 WorkerRunnable 型別的例項,而 WorkerRunnable 又繼承自 Callable,因此它是一個可以被執行的物件。我們會把在該物件中回撥 doInBackground() 來將我們的業務邏輯放線上程池中執行。
  3. 初始化一個 FutureTask 物件 mFuture。該物件包裝了 mWorker 並且當 mWorker 執行完畢之後會呼叫它的 postResultIfNotInvoked() 方法來通知主執行緒(不論任務已經執行完畢還是被取消了,都會呼叫這個方法)。
    public AsyncTask(@Nullable Looper callbackLooper) {
        // 1. 初始化用來傳送訊息的 Handler
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        // 2. 封裝一個物件用來執行我們的任務
        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);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    // 傳送結果給主執行緒
                    postResult(result);
                }
                return result;
            }
        };

        // 3. 初始化一個 FutureTask,並且當它執行完畢的時候,會呼叫 postResultIfNotInvoked 來將訊息的執行結果傳送到主執行緒中
        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);
                }
            }
        };
    }
複製程式碼

當這樣設定完畢之後,我們就可以使用 execute() 方法來開始執行任務了。

2.2 AsyncTask 中任務的序列執行過程

我們從 execute() 方法開始分析 AsyncTask,

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

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
        if (mStatus != Status.PENDING) { // 1.判斷執行緒當前的狀態
            switch (mStatus) {
                case RUNNING: throw new IllegalStateException(...);
                case FINISHED: throw new IllegalStateException(...);
            }
        }
        mStatus = Status.RUNNING;
        onPreExecute();             // 2.回撥生命週期方法
        mWorker.mParams = params;   // 3.賦值給可執行的物件 WorkerRunnable
        exec.execute(mFuture);      // 4.線上程池中執行任務
        return this;
    }
複製程式碼

當我們呼叫 AsyncTask 的 execute() 方法的時候會立即呼叫它的 executeOnExecutor() 方法。這裡傳入了兩個引數,分別是一個 Executor 和任務的引數 params。從上面我們可以看出,當直接呼叫 execute() 方法的時候會使用預設的執行緒池 sDefaultExecutor,而當我們指定了執行緒池之後,會使用我們指定的執行緒池來執行任務。

在 1 處,會對 AsyncTask 當前的狀態進行判斷,這就對應了前面說的,一個任務只能被執行一次。在 2 處會呼叫 onPreExecute() 方法,如果我們覆寫了該方法,那麼它就會在這個時候被呼叫。在 3 處的操作是在為 mWorker 賦值,即把呼叫 execute 方法時傳入的引數賦值給了 mWorker。接下來,會將 mFuture 新增到執行緒池中執行。

當我們不指定任何執行緒池的時候使用的 sDefaultExecutor 是一個序列的執行緒池,它的定義如下:

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

    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 {
                        // 相當於對傳入的Runnable進行了一層包裝
                        r.run();
                    } finally {
                        // 分配下一個任務
                        scheduleNext();
                    }
                }
            });
            // 如果當前沒有正在執行的任務,那麼就嘗試從佇列中取出並執行
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            // 從佇列中取任務並使用THREAD_POOL_EXECUTOR執行
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
複製程式碼

從上面我們可以看出,我們新增到執行緒池中的任務實際上並沒有直接交給執行緒池來執行,而是對其進行了處理之後才執行的,SerialExecutor 通過內部維護了雙端佇列,每當一個 AsyncTask 呼叫 execute() 方法的時候都會被放在該佇列當中進行排隊。如果當前沒有正在執行的任務,那麼就從佇列中取一個任務交給 THREAD_POOL_EXECUTOR 執行;當一個任務執行完畢之後又會呼叫 scheduleNext() 取下一個任務執行。也就是說,實際上 sDefaultExecutor 在這裡只是起了一個任務排程的作用,任務最終還是交給 THREAD_POOL_EXECUTOR 執行的。

這裡的THREAD_POOL_EXECUTOR也是一個執行緒池,它在靜態程式碼塊中被初始化:

    static {
        // 使用指定的引數建立一個執行緒池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
複製程式碼

我們也可以直接將這個靜態的執行緒池作為我們任務執行的執行緒池而不是放在上面的佇列中被序列地執行。

2.3 將任務執行的結果傳送到其他執行緒

上面的 WorkerRunnable 中已經用到了 postResult 方法,它用來將任務執行的結果傳送給 Handler

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

mHandler 會在建立 AsyncTask 的時候初始化。我們可以通過 AsyncTask 的構造方法傳入 Handler 和 Looper 來指定該物件所在的執行緒。當我們沒有指定的時候,會使用 AsyncTask 內部的 InternalHandler 建立 Handler

    private final Handler mHandler;

    public AsyncTask(@Nullable Looper callbackLooper) {
        // 根據傳入的引數建立Handler物件
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() 
            ? getMainHandler() : new Handler(callbackLooper);
    }

    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                // 使用 InternalHandler 建立物件
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

    // AsyncTask 內部定義 的Handler 型別
    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @Override public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            // 根據傳入的訊息型別進行處理
            switch (msg.what) {
                case MESSAGE_POST_RESULT: result.mTask.finish(result.mData[0]); break;
                case MESSAGE_POST_PROGRESS: result.mTask.onProgressUpdate(result.mData); break;
            }
        }
    }
複製程式碼

3、總結

上面我們梳理了 AsyncTask 的大致過程,我們來梳理下:

每當我們例項化一個 AsyncTask 的時候都會在內部封裝成一個 Runnable 物件,該物件可以直接放線上程池中執行。這裡存在兩個執行緒池,一個是 SerialExecutor 一個是 THREAD_POOL_EXECUTOR,前者主要用來進行任務排程,即把交給執行緒的任務放在佇列中進行排隊執行,而時機上所有的任務都是在後者中執行完成的。這個兩個執行緒池都是靜態的欄位,所以它們對應於整個類的。也就是說,當使用預設的執行緒池的時候,例項化的 AsyncTask 會一個個地,按照加入到佇列中的順序依次執行。

當任務執行完畢之後,使用 Handler 來將訊息傳送到主執行緒即可,這部分的邏輯主要與 Handler 機制相關,可以通過這篇文章來了解:《Android 訊息機制:Handler、MessageQueue 和 Looper》

以上就是 AsyncTask 的主要內容。


如果您喜歡我的文章,可以在以下平臺關注我:

相關文章