Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> AsyncTask的故事

AsyncTask的故事

編輯:關於Android編程

寫在前面:
AsyncTask不用多介紹,今天不說怎樣使用,我帶大家看看AsyncTask的進化史,希望大家能從中有所收獲。順便問一句:你認為你應用中實例化多個AsyncTask去execute,這些AsyncTask都在高效的並發運行嗎?

在很久很久以前(2.3以前)

一群可愛的程序猿發現了一個叫做AsyncTask的東西,覺得它很好用,比起Thread來方便多了。於是AsyncTask一夜間紅遍五大洲四大洋。可是用著用著,一個細心的程序猿(比如說我)發現了一個問題。在應用中使用了5個AsyncTask,並都調用了execute,當我再構造一個AsyncTask並execute時,第六個AsyncTask並沒有執行(至少沒有馬上執行)。why? Read The Fucking Source Code.

讓我們看看2.3時AsyncTask的成員變量有些什麼:

AsyncTask.java

public abstract class AsyncTask {
    private static final String LOG_TAG = "AsyncTask";

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 128;
    private static final int KEEP_ALIVE = 1;

    private static final BlockingQueue sWorkQueue =
            new LinkedBlockingQueue(10);

    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 ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

    private static final int MESSAGE_POST_RESULT = 0x1;
    private static final int MESSAGE_POST_PROGRESS = 0x2;
    private static final int MESSAGE_POST_CANCEL = 0x3;

    private static final InternalHandler sHandler = new InternalHandler();

    private final WorkerRunnable mWorker;
    private final FutureTask mFuture;

    private volatile Status mStatus = Status.PENDING;

    /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }
}

非常簡單,我們主要關注一下AsyncTask中真正執行任務的線程池。

private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

/*參數解釋:
CORE_POOL_SIZE 表示線程池中的核心線程數量,可理解為線程池中的最少線程數或者線程池中的不死線程(當然也可以配置保活時間,這裡我們不考慮)。
MAXIMUM_POOL_SIZE 表示線程池中最多能容納的線程數量。
KEEP_ALIVE 保活時間,指的是線程池中的超過CORE_POOL_SIZE定義的數量的線程在執行完任務處於空閒時保活的時間,超過這個時間,線程將回收。
TimeUnit.SECONDS 保活時間的單位。
sWorkQueue 線程池中的任務隊列。(此處是一個阻塞隊列)
sThreadFactory 用於線程池生成線程的線程工廠。
ThreadPoolExecutor的構造方法還有一個重載形式是多了一個超過線程池負載時處理策略的參數的。這裡默認的處理測略是拋出java.util.concurrent.RejectedExecutionException異常。

順便介紹一下線程池的工作流程,當調用線程池的execute方法執行一個任務時,假設線程池中已經存在的線程數量為Tn,線程池的任務隊列sWorkQueue中保存的任務數量為Wn,sWorkQueue的size為Wsize,有以下幾種情況:
1.當Tn < CORE_POOL_SIZE時,不管此時線程池中已存在的線程是否空閒,都會新建一個線程去執行任務。因為線程池要保證它擁有的線程至少有CORE_POOL_SIZE個。
2.當CORE_POOL_SIZE < Tn < MAXIMUM_POOL_SIZE且Wn < Wsize時,線程池只會把要執行的任務放到sWorkQueue中。並不會新建一個線程去執行當前的任務。
3.當CORE_POOL_SIZE < Tn < MAXIMUM_POOL_SIZE且Wn = Wsize時,線程池才會新建一個線程去執行任務。
4.當Tn = MAXIMUM_POOL_SIZE且Wn = Wsize時,通過上面說的構造線程池時傳入的表示超過線程池負載時處理策略的參數去完成處理,默認是拋出異常java.util.concurrent.RejectedExecutionException。

最重要的是這裡的線程池的static的。那意味著一個進程空間中所有的AsyncTask類的實例都將共用這一個線程池!!!
*/

我把關於線程池參數的解釋和線程池分配任務的策略在上面的注釋裡寫的已經很清楚了,一定要仔細看完注釋,看完後再分析AsyncTask時就豁然開朗了。

了解了線程池的相關知識後我們先看一下這個版本的AsyncTask的構造方法:

AsyncTask.java

public AsyncTask() {
        mWorker = new WorkerRunnable() {
            public Result call() throws Exception {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                return doInBackground(mParams);
            }
        };

        mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                Message message;
                Result result = null;

                try {
                    result = get();
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
                            new AsyncTaskResult(AsyncTask.this, (Result[]) null));
                    message.sendToTarget();
                    return;
                } catch (Throwable t) {
                    throw new RuntimeException("An error occured while executing "
                            + "doInBackground()", t);
                }

                message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                        new AsyncTaskResult(AsyncTask.this, result));
                message.sendToTarget();
            }
        };
}

構造方法比較簡單,主要是生成了一個待執行的任務。這裡的任務是通過WorkerRunnable封裝的,WorkerRunnable實現了Callable接口。Callable是什麼?你可以大致把理解為Callable + FutureTask = Runnable,同Runnable一樣它也是描述一個要執行的任務,Runnable跑run方法,而Callable跑call方法。只是它提供了獲取執行結果的回調處理,這一部分就是通過FutureTask來完成的,所以你只要粗略理解Callable + FutureTask = Runnable就可以了。在call方法中去跑一下doInBackground()(哇,多麼熟悉的名字)。

任務生成了,讓一切跑起來,看看execute方法做了什麼:

AsyncTask.java

public final AsyncTask execute(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;
        sExecutor.execute(mFuture);

        return this;
}

首先check一下狀態,接著跑一下onPreExecute()回調,把參數賦值給我們構造方法中創建的任務後,一切就交由線程池去搞定了。

好了,那麼問題來了。在2.3的AsyncTask中CORE_POOL_SIZE為5,MAXIMUM_POOL_SIZE為128,sWorkQueue長度為10,前面說已經跑了5個AsyncTask,他們都是共用線程池的,那麼這5個任務如果都很耗時,因為5 < 6 < 128並且此時線程池任務隊列是空的,第6個任務必然被阻塞,阻塞是因為根本沒有線程去執行它,它只會被獨自扔在線程池的任務隊列中,一個人慢慢老去…

還有一個隱患,如果線程某個時刻業務量很大,線程池中的線程個數已滿,達到MAXIMUM_POOL_SIZE,並且任務隊列也滿了,此時再往線程池扔任務,必然只能通過超出負載處理策略來處理,而這裡默認拋出java.util.concurrent.RejectedExecutionException,這時候如果我們沒有捕獲該異常。那就等著用戶卸載吧…

當然,這是發生在很久很久以前的故事。優化是永不停歇的話題。所以

終於有一天(4.0准確說應該是3.0吧)

可愛的程序猿們發現AsyncTask變了:

AsyncTask.java

public abstract class AsyncTask {
    private static final String LOG_TAG = "AsyncTask";

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 128;
    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 sPoolWorkQueue =
            new LinkedBlockingQueue(10);

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

    /**
     * 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 final int MESSAGE_POST_RESULT = 0x1;
    private static final int MESSAGE_POST_PROGRESS = 0x2;

    private static final InternalHandler sHandler = new InternalHandler();

    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    private final WorkerRunnable mWorker;
    private final FutureTask mFuture;

    private volatile Status mStatus = Status.PENDING;

    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

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

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

}

主要的變化在於:

1.多了一個SerialExecutor,並且將該執行器作為默認的AsyncTask的執行器。

2.開放了一個executeOnExecutor接口,我們其實可以通過這個接口定制適用於自己應用的線程池來完成默認線程池的工作。

3.將2.3中負責執行任務的線程池public了。

先來看SerialExecutor,它定義了一個mTasks存放需要執行的任務,它其實就是一個跑在調用execute的線程的任務分發器,串行的將任務加入到mTasks中,不過其最終還是使用老的THREAD_POOL_EXECUTOR來執行任務,但此時THREAD_POOL_EXECUTOR其實只有一個線程在工作,類似一個生產者消費者模型,SerialExecutor此時充當一個生產者,將任務往mTasks裡面塞,THREAD_POOL_EXECUTOR類似一個消費者,往mTasks裡面拿任務並執行。但只有當第一次執行任務或任務執行完時才會去mTasks中取下一個任務,即此時雖然是一個線程池在工作,但同時運行的只有一個任務。

上面說的SerialExecutor是AsyncTask默認的Executor。比起2.3時代的直接用線程池跑任務。這種策略不會把線程池塞爆,不會出現任務在線程池的任務隊列中等待終老的情況,但是此時任務串行執行,即同一個進程中的AsyncTask都是串行執行的,下一個AsyncTask只能在上一個AsyncTask執行完後才會開始執行。也許這裡本來就是個嘈點,於是大神們干脆給了executeOnExecutor接口,言外之意即是“默認的不滿意你們愛咋整自己整去吧”。看下4.0中AsyncTask的execute方法你就明白了:

public final AsyncTask

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