Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android AsyncTask工作原理

Android AsyncTask工作原理

編輯:關於Android編程

AsyncTask的描述是這樣的:

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask能夠適當簡單的使用在UI線程,在沒有任務線程和handler的情況下,這個類也允許執行後台操作並將結果顯示在UI線程上。

AsyncTask的引入,我們在執行完耗時的後台任務後可以很方便的更新UI元素。相信大多數同學對AsyncTask的用法都已經很熟悉,那這裡不在敘述他的基本用法了。

AsyncTask的實現原理是封裝線程池和消息機制,我已經在自己的博客寫過了線程池和消息機制的。感興趣的同學可以去閱讀下。

那麼直接進入AsyncTask的源碼分析,一起從源碼的角度徹底理解。

先從AsyncTask的構造方法看起

/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 */
public AsyncTask() {
    mWorker = new WorkerRunnable() {
        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(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);
            }
        }
    };
}

從方法的注釋我們知道,創建一個異步任務,這個構造方法必須要在UI線程中調用。

AsyncTask的構造方法中,首先創建了一個WorkerRunnable對象並賦值給mWorker,那麼這個WorkerRunnable是什麼呢?

private static abstract class WorkerRunnable implements Callable {
    Params[] mParams;
}

從上面的代碼中可知道WorkerRunnable其實就是Callable。接著又創建了一個FutureTask對象,並將mWorker傳進。AsyncTask的構造方法只是做了初始化的工作。

關於Callable和FutureTask我已經在線程池系列的博客中介紹過了。簡單說就是FutureTask是Runnable的實現類,其中封裝了Callable,當FutureTask的run方法被調用時,內部實際調用的是Callable的call方法。那麼我們等下就尋找FutureTask的run方法在哪裡被調用了, doInBackground方法也就是在那裡開始被調用的。

那麼接下來就是跟蹤AsyncTask的 execute方法了,源碼如下:

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

內部調用了executeOnExecutor方法,這個方法顧名思義就是在線程池中執行,傳入sDefaultExecutor和在參數params。

那麼sDefaultExecutor又是什麼呢?先不管。

我覺的剛開始分析execute方法不要一直在研究從四面八方出現的變量。因為讓我們感到莫名其妙出現的變量實在太多了,我們一直去跟蹤思路很容易會散了。稍後會分析sDefaultExecutor。

那麼繼續跟進executeOnExecutor方法看看:

public final AsyncTask 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;
}

關於Status是一個枚舉類型,他有三種狀態:PENDING,RUNNING, FINISHED,分別標明了當前AsyncTask的狀態。當然初始化的時候是PENDING准備就緒狀態,從

private volatile Status mStatus = Status.PENDING;

這行代碼也可以看出。那麼繼續往下看這個executeOnExecutor方法,之後會將Status的狀態設置為RUNNING正在運行狀態。AsyncTask如果在RUNNING, FINISHED狀態執行executeOnExecutor方法就會拋出異常了,也表明一個AsyncTask實例只能執行一次任務。接著就會回調onPreExecute方法,所以我們可以重寫onPreExecute這個方法做一些准備工作。

好,重頭戲要開始了。

exec.execute(mFuture);

exec調用了execute方法並將mFuture(FutureTask對象)傳入,之前我們已經在構造方法中初始化了FutureTask對象並賦值給mFuture。不過還需要知道exec,回到最初,我們是在AsyncTask的execute方法裡,將sDefaultExecutor傳了進來executeOnExecutor方法,所以exec就是sDefaultExecutor。

那麼就需要知道sDefaultExecutor的execute方法裡的內部實現是怎麼樣的,才能解開我們心中的疑惑了。

現在才是搞明白sDefaultExecutor是什麼的正確時機,那我們就先來瞧瞧sDefaultExecutor是什麼先?

/**
 * 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();

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

由此可知道,sDefaultExecutor是SerialExecutor的實例,從名字可以看出SerialExecutor是一個串行執行任務的線程池。我在Android中的線程池(二)那篇博客中也詳細講了各種線程池。

那就來看看SerialExecutor的源碼:

private static class SerialExecutor implements Executor {
    final ArrayDeque mTasks = new ArrayDeque();
    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);
        }
    }
}

SerialExecutor確實是一個線程池,很重要。這段代碼也是我覺得最不好理解的。裡面實現了Executor 接口的execute方法。

SerialExecutor的execute方法內部,首先向ArrayDeque隊列中提交一個Runnable對象,在其run方法裡調用r的run方法,r就是mFuture。

public void run() {
    try {
        r.run();//等價於mFuture.run()
    } finally {
        scheduleNext();
    }
}

有點繞,但容易理解的。其實就是這個run方法運行時,就調用FutureTask的run方法嘛。

當第一次執行execute方法,mActive一定是為空的,那麼就會通過if (mActive == null)的判斷調用到scheduleNext方法。

可以看到scheduleNext這個方法從隊列的頭部取出一個元素並賦值給mActive,如果mActive不為空,即隊列裡有任務。就調用THREAD_POOL_EXECUTOR的execute方法將mActive傳進去執行任務。mActive經過第一次賦值之後就不為空了。之後就會進入finally塊。也就是說scheduleNext還是會被調用。這樣後續的任務又得到了處理。

總結一下:可以這樣理解,假設我有10個AsyncTask任務,全部進入隊列,肯定會有其中一個AsyncTask任務是第一個執行的吧,當第一個AsyncTask任務執行完畢,就會進入finally塊,調用scheduleNext方法繼續取出隊列中的任務,這樣下一個任務又得到了處理。

只有當一個任務執行完畢了,下一個任務才會執行。所以為什麼說這種線程池是串行執行任務。

AsyncTask有兩個線程池的,SerialExecutor和THREAD_POOL_EXECUTOR。前者負責任務的排隊,後者用於任務的執行。THREAD_POOL_EXECUTOR的配置如下:

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

這些核心線程數,最大線程數,任務隊列等參數如下:

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 BlockingQueue sPoolWorkQueue =
        new LinkedBlockingQueue(128);

那我們跟進THREAD_POOL_EXECUTOR的execute方法,看看是怎樣執行任務的。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

這裡的流程,我也在之前的博客分析過了,不過這裡為了更好閱讀,我還是在分析一遍吧。這個execute方法裡的邏輯不簡單,所以我們要結合AsyncTask使用到的線程池的參數配置,抓主要信息閱讀。

那主要是將mFuture加入將要執行的隊列中,我們只需要跟進addWorker方法即可。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:

    //代碼省略

    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
                    //代碼省略
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

創建一個Worker對象並對傳進的FutureTask進行包裝,從Worker對象中取出線程並賦值給t,然後將Worker對象添加進工作線程隊列等待執行,最後線程t調用start方法。即調用Worker裡的run方法,在Worker裡的run方法裡又調用了runWorker方法,如下:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
               //代碼省略
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } 
        //代碼省略
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

取出剛才在Worker對象中封裝的FutureTask對象,並賦值給task,最後task調用run方法。這樣mFuture的run方法就得到了調用。

在FutureTask對象run方法裡會調用Callable的call方法,FutureTask對象run方法如下:

public void run() {
    //代碼省略
    try {
        Callable c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            }
    //代碼省略
}

這個Callable也就是我們最初在AsyncTask構造方法中創建的WorkerRunnable對象mWorker。現在我們可以看到mWorker的call方法了

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

饒了地球一圈,終於執行到了doInBackground方法了。這裡也可以證實,onPreExecute方法是第一個被調用的,而第二個回調的是doInBackground方法。

那繼續前進吧,將doInBackground返回的結果傳進了postResult方法,postResult方法源碼如下:

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

終於看到消息機制了,getHandler方法獲取到InternalHandler對象,並將MESSAGE_POST_RESULT字段和一個包裝了result的AsyncTaskResult對象發送給handleMessage方法接收。

private static class AsyncTaskResult {
    final AsyncTask mTask;
    final Data[] mData;

    AsyncTaskResult(AsyncTask task, Data... data) {
        mTask = task;
        mData = data;
    }
}

AsyncTaskResult是一個用於封裝AsyncTask實例和結果集的內部類。

那麼我們現在直接到handleMessage方法一看究竟。

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

在InternalHandler的構造方法中,獲取到UI線程的Looper對象,這就是為什麼AsyncTask要在主線程創建,因為我要關聯主線程的Looper,消息隊列。

剛才發的消息是MESSAGE_POST_RESULT,那我們看到這個分支即可,這裡調用了AsyncTask的finish方法並傳進result。

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

在一路順暢不拋異常的正常情況下,AsyncTask沒有被取消,會將result傳入onPostExecute方法,執行完onPostExecute後,整個AsyncTask的生命周期也就結束了。

還有一個知識點沒講到的是,在doInBackground方法中調用publishProgress方法,可以在onProgressUpdate方法中更新UI,比如更新進度條的進度。

protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult
(this, values)).sendToTarget(); } }

其實這個publishProgress方法也很簡單,就是將當前的進度值values發送到主線程。

如果我們想要並行執行任務,可以調用AsyncTask的executeOnExecutor方法。如下:

new AsyncTask(){

    @Override
    protected Void doInBackground(Void... params) {
        return null;
    }

}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,null);

其實很好理解的。直接執行executeOnExecutor方法,傳入AsyncTask.THREAD_POOL_EXECUTOR線程池。這樣就繞過了SerialExecutor任務排隊的過程。

以上就是根據源碼的角度分析的AsyncTask的工作原理。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved