1. 程式人生 > >理解AsyncTask的工作原理

理解AsyncTask的工作原理

Android中子執行緒主要有AsyncTask、IntentService、HandlerThread。

來看一看AsyncTask的原理,在使用的時候,AsyncTask在4.1版本之前是需要在主執行緒內建立的,4.1之後就可以隨便在哪裡建立了,原因在於AsyncTask和主執行緒通訊也是通過mainlooper的handler傳送訊息實現的,因此可以知道第一次使用的時候需要建立和主執行緒相關的handler,4.1之後不需要是因為在ActivityThread中已經建立了AsyncTask的handler。

AsyncTask的方法有:

onPreExecute() //此方法會在後臺任務執行前被呼叫,用於進行一些準備工作
doInBackground(Params... params) //此方法中定義要執行的後臺任務,在這個方法中可以呼叫publishProgress來更新任務進度(publishProgress內部會呼叫onProgressUpdate方法)
onProgressUpdate(Progress... values) //由publishProgress內部呼叫,表示任務進度更新
onPostExecute(Result result) //後臺任務執行完畢後,此方法會被呼叫,引數即為後臺任務的返回結果
onCancelled() //此方法會在後臺任務被取消時被呼叫
這些方法在AsyncTask建構函式中就有體現,如下,在workerRunnable建構函式引數是params,result;重寫了call()方法,先設定這個Runnable是否被呼叫過,在呼叫doinBackground方法和postResult方法,然後workerRunnable又被作為引數傳遞給了FutureTask的建構函式,這樣我們的外部任務就封裝成了FutureTask。

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        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是一個封裝了我們的後臺任務的FutureTask物件,FutureTask類實現了FutureRunnable介面,通過這個介面可以方便的取消後臺任務以及獲取後臺任務的執行結果

    從上面的分析我們知道了,當mWorker中定義的call方法被執行時,doInBackground就會開始執行,我們定義的後臺任務也就真正開始了。那麼這個call方法什麼時候會被呼叫呢?我們可以看到經過層層封裝,實際上是mFuture物件封裝了call方法,當mFuture物件被提交到AsyncTask包含的執行緒池執行時,call方法就會被呼叫,我們定義的後臺任務也就開始執行了。下面我們來看一下mFuture是什麼時候被提交到執行緒池執行的。

   首先來看一下execute方法的原始碼:

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

我們可以看到它接收的引數是Params型別的引數,這個引數會一路傳遞到doInBackground方法中。execute方法僅僅是呼叫了executeOnExecutor方法,並將executeOnExecutor方法的返回值作為自己的返回值。我們注意到,傳入了sDefaultExecutor作為executeOnExecutor方法的引數,那麼sDefaultExecutor是什麼呢?簡單的說,它是AsyncTask的預設執行器(執行緒池)。AsyncTask可以以序列(一個接一個的執行)或並行(一併執行)兩種方式來執行後臺任務,在Android3.0及以後的版本中,預設的執行方式是序列。這個sDefaultExecutor就代表了預設的序列執行器(執行緒池)。也就是說我們平常在AsyncTask物件上呼叫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)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

從以上程式碼的第4行到第12行我們可以知道,當AsyncTask物件的當前狀態為RUNNING或FINISHED時,呼叫execute方法會丟擲異常,這意味著不能對正在執行任務的AsyncTask物件或是已經執行完任務的AsyncTask物件呼叫execute方法,這也就解釋了我們上面提到的侷限中的最後一條。

    接著我們看到第17行存在一個對onPreExecute方法的呼叫,這表示了在執行後臺任務前確實會呼叫onPreExecute方法。

    在第19行,把我們傳入的execute方法的params引數賦值給了mWorker的mParams成員變數;而後在第20行呼叫了exec的execute方法,並傳入了mFuture作為引數。exec就是我們傳進來的sDefaultExecutor。那麼接下來我們看看sDefaultExecutor究竟是什麼。在AsyncTask類的原始碼中,我們可以看到這句:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

sDefaultExecutor被賦值為SERIAL_EXECUTOR,那麼我們來看一下SERIAL_EXECUTOR:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
現在,我們知道了實際上sDefaultExecutor是一個SerialExecutor物件,我們來看一下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);
            }
        }
    }

我們來看一下execute方法的實現。mTasks代表了SerialExecutor這個序列執行緒池的任務快取佇列,在第6行,我們用offer方法向任務快取佇列中新增一個任務,任務的內容如第7行到第13行的run方法定義所示。我們可以看到,run方法中:第9行呼叫了mFuture(第5行的引數r就是我們傳入的mFuture)的run方法,而mFuture的run方法內部會呼叫mWorker的call方法,然後就會呼叫doInBackground方法,我們的後臺任務也就開始執行了。那麼我們提交到任務快取佇列中的任務什麼時候會被執行呢?我們接著往下看。

     首先我們看到第三行定義了一個Runnable變數mActive,它代表了當前正在執行的AsyncTask物件。第15行判斷mActive是否為null,若為null,就呼叫scheduleNext方法。如第20行到24行所示,在scheduleNext方法中,若快取佇列非空,則呼叫THREAD_POOL_EXECUTOR.execute方法執行從快取佇列中取出的任務,這時我們的後臺任務便開始你真正執行了。

     通過以上的分析,我們可以知道SerialExecutor所完成的工作主要是把任務加到任務快取佇列中,而真正執行任務的是THREAD_POOL_EXECUTOR。我們來看下THREAD_POOL_EXECUTOR是什麼:

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

  從上面的程式碼我們可以知道,它是一個執行緒池物件。根據AsyncTask的原始碼,我們可以獲取它的各項引數如下:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;


private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);


    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};


private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
    由以上程式碼我們可以知道:
  •  corePoolSize為CPU數加一;
  • maximumPoolSize為CPU數的二倍加一;
  • 存活時間為1秒;
  • 任務快取佇列為LinkedBlockingQueue。

  現在,我們已經瞭解到了從我們呼叫AsyncTask物件的execute方法開始知道後臺任務執行完都發生了什麼。現在讓我們回過頭來看一看之前提到的postResult方法的原始碼:

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

     在以上原始碼中,先呼叫了getHandler方法獲取AsyncTask物件內部包含的sHandler,然後通過它傳送了一個MESSAGE_POST_RESULT訊息。我們來看看sHandler的相關程式碼:

private static final InternalHandler sHandler = new InternalHandler();

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

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        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;
            }
        }
}


     從以上程式碼中我們可以看到,sHandler是一個靜態的Handler物件。我們知道建立Handler物件時需要當前執行緒的Looper,所以我們為了以後能夠通過sHandler將執行環境從後臺執行緒切換到主執行緒(即在主執行緒中執行handleMessage方法),我們必須使用主執行緒的Looper,因此必須在主執行緒中建立sHandler。這也就解釋了為什麼必須在主執行緒中載入AsyncTask類,是為了完成sHandler這個靜態成員的初始化工作。

     在以上程式碼第10行開始的handleMessage方法中,我們可以看到,當sHandler收到MESSAGE_POST_RESULT方法後,會呼叫finish方法,finish方法的原始碼如下:

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

    在第2行,會通過呼叫isCancelled方法判斷AsyncTask任務是否被取消,若取消了則呼叫onCancelled方法,否則呼叫onPostExecute方法;在第7行,把mStatus設為FINISHED,表示當前AsyncTask物件已經執行完畢。

    經過了以上的分析,我們大概瞭解了AsyncTask的內部執行邏輯,知道了它預設使用序列方式執行任務。那麼如何讓它以並行的方式執行任務呢? 閱讀了以上的程式碼後,我們不難得到結論,只需呼叫executeOnExecutor方法,並傳入THREAD_POOL_EXECUTOR作為其執行緒池即可。


總體的邏輯思路就如下圖所示: