AsyncTask機制詳解

istoneyou發表於2017-11-28

  AsyncTask是Android提供的一個輕量級非同步任務機制,使用AsyncTask可以方便的執行非同步任務,並將結果更新到main thread。AsyncTask中是通過Handler機制來讓work thread和main thread通訊的。
  在這篇文章中我們將瞭解AsyncTask的基本用法以及從原始碼的角度來分析AsyncTask機制,首先我們來了解下開發過程中AsyncTask的使用
AsyncTask類的基本方法:

public abstract class AsyncTask<Params, Progress, Result> {
/**
     * Override this method to perform a computation on a background thread. The
     * specified parameters are the parameters passed to {@link #execute}
     * by the caller of this task.
     *
     * This method can call {@link #publishProgress} to publish updates
     * on the UI thread.
     *
     * @param params The parameters of the task.
     *
     * @return A result, defined by the subclass of this task.
     *
     * @see #onPreExecute()
     * @see #onPostExecute
     * @see #publishProgress
     */
    @WorkerThread
    protected abstract Result doInBackground(Params... params);

    /**
     * Runs on the UI thread before {@link #doInBackground}.
     *
     * @see #onPostExecute
     * @see #doInBackground
     */
    @MainThread
    protected void onPreExecute() {
    }

    /**
     * <p>Runs on the UI thread after {@link #doInBackground}. The
     * specified result is the value returned by {@link #doInBackground}.</p>
     * 
     * <p>This method won't be invoked if the task was cancelled.</p>
     *
     * @param result The result of the operation computed by {@link #doInBackground}.
     *
     * @see #onPreExecute
     * @see #doInBackground
     * @see #onCancelled(Object) 
     */
    @SuppressWarnings({"UnusedDeclaration"})
    @MainThread
    protected void onPostExecute(Result result) {
    }

    /**
     * Runs on the UI thread after {@link #publishProgress} is invoked.
     * The specified values are the values passed to {@link #publishProgress}.
     *
     * @param values The values indicating progress.
     *
     * @see #publishProgress
     * @see #doInBackground
     */
    @SuppressWarnings({"UnusedDeclaration"})
    @MainThread
    protected void onProgressUpdate(Progress... values) {
    }
}複製程式碼

  從上面AsyncTask的程式碼可以看到,AsyncTask是一個抽象類,我們如果想要使用的話必須先建立它的子類,並實現它的抽象方法doInBackground,根據功能需要我們還可以重寫onPreExecute、onPostExecute、onProgressUpdate。從註釋我們就可以知道onPreExecute、onPostExecute、onProgressUpdate這幾個方法是執行在main thread,doInBackground是執行在workthread中的。我們把耗時的操作放在doInBackground進行,當doInBackground執行完之後會把結果返回給onPostExecute,我們可以在onPostExecute做一些更新UI的操作。onPreExecute在doInBackground之前執行,用於執行準備工作,onProgressUpdate用來更新後臺任務的執行進度。
  AsyncTask有三個泛型引數,分別是Params, Progress, Result。Params是指後臺任務執行的引數,用於在doInBackground使用,Progress是用來指示後臺任務執行進度的單位,Result是後臺任務的返回結果,在doInBackground方法中返回,交給onPostExecute處理。這些引數不需要時可以用Void代替。
  在本文中我們自定義一個簡單的AsyncTask類,程式碼定義如下:

public class TestAsyncTask extends AsyncTask<Void, Integer, Void> {
    private String taskName;

    public TestAsyncTask(String name) {
        super();
        taskName = name;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        Log.v("stone", "task = " + taskName + " onPostExecute in " + Thread.currentThread().toString());
    }

    @Override
    protected Void doInBackground(Void... params) {
        Log.v("stone", "task = " + taskName + " doInBackground in " + Thread.currentThread().toString());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.toString();
        }
        return null;
    }

}

//AsyncTask的兩種啟動方式
new TestAsyncTask("task_" + i).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new TestAsyncTask("tasktesk_" + i).execute();複製程式碼

  到目前為止我我們已經大致瞭解了AsyncTask的使用,以及啟動後臺任務的兩種方式,execute和executeOnExecutor。在executeOnExecutor中我們使用了AsyncTask自定義的THREAD_POOL_EXECUTOR,它是一個ThreadPoolExecutor。這兩種後臺任務的啟動方式有什麼區別呢?一個應用中最多可以new多個AsyncTask呢?多個任務是並行處理還是序列處理呢?最多同時有多少個任務在處理呢?下面我們將從原始碼的角度來回答上面的問題,由於AsyncTask從出現到現在已經有了多次改動,下面我的分析將是基於API 23的原始碼來進行的,請各位注意不同API版本的AsyncTask實現是有差異的。

#AsyncTask機制詳解#
  通過上文我們已經知道了AsyncTask的簡單用法,下面我將從原始碼的角度來分析,一個AsyncTask從建立到執行的過程。首先一個任何AsyncTask子類的建立都會呼叫AsyncTask的預設建構函式AsyncTask(),下面我們來看一下這個方法的定義:

     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);
                }
            }
        };
    }複製程式碼

  程式碼很簡單,初始化了mWorker 和mFuture 這兩個物件,我們暫時先不管立面的具體實現,只要知道這個mFuture 會交給Executor去執行。現在AsyncTask物件已經建立好了,我們來看AsyncTask的執行過程。我們先來分析execute()方法,該方法的定義如下:

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

  execute的程式碼實際上直接呼叫了executeOnExecutor方法,並且傳入了一個叫做sDefaultExecutor的Executor物件。接著我們來看下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;
    }複製程式碼

  在executeOnExecutor中,我們先呼叫了onPreExecute()方法,這就解釋了為什麼onPreExecute方法是最新被呼叫的。我們把執行引數params賦給mWorker物件,mWorker實現了callable介面。把mFuture交給我們傳入的Executor來執行。對面我們前文提到了AsyncTask的兩種啟動方式,他們執行方式的不同是因為傳入的Executor不同導致的。我們前面提到過execute方法傳給executeOnExecutor的是一個sDefaultExecutor物件,通過原始碼來看一下sDefaultExecutor是什麼:

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

  我們看到sDefaultExecutor 被賦值為SERIAL_EXECUTOR,我們來看一下SERIAL_EXECUTOR是如何定義的:   

    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();複製程式碼

  SERIAL_EXECUTOR 是一個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啟動AsyncTask任務,會把任務一個一個新增到mTasks中,在執行完一個任務後才會去執行下一個任務,mTasks的大小沒有限制,所以理論上通過execute啟動AsyncTask任務這種方式可以建立無數個task,並且所有的task是序列執行的。這個runnable引數就是我們在AsyncTask建構函式中初始化的mFuture。我們接著再來看一下建構函式的原始碼:   

     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);
                }
            }
        };
    }複製程式碼

  我們看到在初始化mWorker 物件時,在它的call方法裡面呼叫了doInBackground方法,執行完doInBackground之後會呼叫postResult(result)來傳遞doInBackground的返回結果。我們來分析下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方法中主要是生成了一個MESSAGE_POST_RESULT訊息,並將訊息傳送給了target handler。現在我們就要來看看這個Handler物件是什麼,就知道是把訊息傳送到了哪個執行緒中。我們來看一下getHandler()方法的實現:   

    private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }複製程式碼

  看到這裡,我們知道這個handler是一個InternalHandler物件,我們接著來看一下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;
            }
        }
    }複製程式碼

  看到這裡已經很清楚,這個handler是繫結了主執行緒loop的handler,所以接下來的工作就從工作執行緒切換到主執行緒中去執行。在handleMessage中可以看到MESSAGE_POST_RESULT會導致呼叫result.mTask.finish(result.mData[0])。這個就是AsyncTask的finish方法,我們來看一下finish方法的實現:   

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

  finish方法裡面會做判斷,如果 isCancelled()返回的是true,就會去執行onCancelled,否則就會呼叫onPostExecute方法。至此我們就走完了execute方式的啟動流程。通過以上的分析,我們可以知道這種方式下AsyncTask的物件可以建立任意多個並執行,且是順序執行的。
  對於executeOnExecutor啟動方式,他的執行方式和傳入的Executor相關,每個任務的執行邏輯和execute啟動方式是一樣的。executeOnExecutor方式中我們傳入的引數是AsyncTask.THREAD_POOL_EXECUTOR,這是AsyncTask預設幫我們配置的Executor。我們來具體看下這個Executor的配置情況:   

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);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);複製程式碼

  這個ThreadPoolExecutor配置如下:核心執行緒數為CPU核個數+1,最大執行緒數是2倍的CPU核個數+1,任務排隊佇列大小為128,所以假設我們的手機CPU核的個數為8,在核心執行緒數為9,最大執行緒數為17。所以根據ThreadPoolExecutor的特點我們可以知道,最大的任務數為128+17,超過這個數量就會丟擲RejectedExecutionException。在排隊佇列未滿之前,最多有9個執行緒在執行,當排隊佇列滿了之後,最多有17個執行緒在執行。

相關文章