Android源碼分析—帶你認識不一樣的AsyncTask

來源:互聯網
上載者:User

 

前言

什麼是AsyncTask,相信搞過android開發的朋友們都不陌生。AsyncTask內部封裝了Thread和Handler,可以讓我們在後台進行計算並且把計算的結果及時更新到UI上,而這些正是Thread+Handler所做的事情,沒錯,AsyncTask的作用就是簡化Thread+Handler,讓我們能夠通過更少的代碼來完成一樣的功能,這裡,我要說明的是:AsyncTask只是簡化Thread+Handler而不是替代,實際上它也替代不了。同時,AsyncTask從最開始到現在已經經過了幾次代碼修改,任務的執行邏輯慢慢地發生了改變,並不是大家所想象的那樣:AsyncTask是完全並存執行的就想多個線程一樣,其實不是的,所以用AsyncTask的時候還是要注意,下面會一一說明。另外本文主要是分析AsyncTask的原始碼以及使用時候的一些注意事項,如果你還不熟悉AsyncTask,請先閱讀android之AsyncTask 來瞭解其基本用法。

這裡先給出AsyncTask的一個例子:

 

private class DownloadFilesTask extends AsyncTask {     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));             // Escape early if cancel() is called             if (isCancelled()) break;         }         return totalSize;     }     protected void onProgressUpdate(Integer... progress) {         setProgressPercent(progress[0]);     }     protected void onPostExecute(Long result) {         showDialog(Downloaded  + result +  bytes);     } }

 

使用AsyncTask的規則

 

AsyncTask的類必須在UI線程載入(從4.1開始系統會幫我們自動完成)AsyncTask對象必須在UI線程建立execute方法必須在UI線程調用
不要在你的程式中去直接調用onPreExecute(), onPostExecute, doInBackground, onProgressUpdate方法
一個AsyncTask對象只能執行一次,即只能調用一次execute方法,否則會報運行時異常AsyncTask不是被設計為處理耗時操作的,耗時上限為幾秒鐘,如果要做長耗時操作,強烈建議你使用Executor,ThreadPoolExecutor以及FutureTask在1.6之前,AsyncTask是串列執行任務的,1.6的時候AsyncTask開始採用線程池裡處理並行任務,但是從3.0開始,為了避免AsyncTask所帶來的並發錯誤,AsyncTask又採用一個線程來串列執行任務

 

AsyncTask到底是串列還是並行?

給大家做一下實驗,請看如下實驗代碼:代碼很簡單,就是點擊按鈕的時候同時執行5個AsyncTask,每個AsyncTask休眠3s,同時把每個AsyncTask執行結束的時間列印出來,這樣我們就能觀察出到底是串列執行還是並存執行。

 

    @Override    public void onClick(View v) {        if (v == mButton) {            new MyAsyncTask(AsyncTask#1).execute();            new MyAsyncTask(AsyncTask#2).execute();            new MyAsyncTask(AsyncTask#3).execute();            new MyAsyncTask(AsyncTask#4).execute();            new MyAsyncTask(AsyncTask#5).execute();        }    }    private static class MyAsyncTask extends AsyncTask {        private String mName = AsyncTask;        public MyAsyncTask(String name) {            super();            mName = name;        }        @Override        protected String doInBackground(String... params) {            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            return mName;        }        @Override        protected void onPostExecute(String result) {            super.onPostExecute(result);            SimpleDateFormat df = new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);            Log.e(TAG, result + execute finish at  + df.format(new Date()));        }    }
我找了2個手機,系統分別是4.1.1和2.3.3,按照我前面的描述,AsyncTask在4.1.1應該是串列的,在2.3.3應該是並行的,到底是不是這樣呢?請看Log

 

Android 4.1.1上執行:從下面Log可以看出,5個AsyncTask共耗時15s且時間間隔為3s,很顯然是串列執行的

Android 2.3.3上執行:從下面Log可以看出,5個AsyncTask的結束時間是一樣的,很顯然是並存執行


結論:從上面的兩個Log可以看出,我前面的描述是完全正確的。下面請看源碼,讓我們去瞭解下其中的原理。

源碼分析

 

/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package android.os;import java.util.ArrayDeque;import java.util.concurrent.BlockingQueue;import java.util.concurrent.Callable;import java.util.concurrent.CancellationException;import java.util.concurrent.Executor;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadFactory;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;import java.util.concurrent.TimeoutException;import java.util.concurrent.atomic.AtomicBoolean;import java.util.concurrent.atomic.AtomicInteger;public abstract class AsyncTask {    private static final String LOG_TAG = AsyncTask;//擷取當前的cpu核心數    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;//ThreadFactory 線程工廠,通過Factory 方法newThread來擷取新線程    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());        }    };//靜態阻塞式隊列,用來存放待執行的任務,初始容量:128個    private static final BlockingQueue sPoolWorkQueue =            new LinkedBlockingQueue(128);    /**     * 靜態並發線程池,可以用來並存執行任務,儘管從3.0開始,AsyncTask預設是串列執行任務 * 但是我們仍然能構造出並行的AsyncTask     */    public static final Executor THREAD_POOL_EXECUTOR            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);    /**     * 靜態串列任務執行器,其內部實現了串列控制, * 迴圈的取出一個個任務交給上述的並發線程池去執行     */    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();//訊息類型:發送結果    private static final int MESSAGE_POST_RESULT = 0x1;//訊息類型:更新進度    private static final int MESSAGE_POST_PROGRESS = 0x2;/**靜態Handler,用來發送上述兩種通知,採用UI線程的Looper來處理訊息 * 這就是為什麼AsyncTask必須在UI線程調用,因為子線程 * 預設沒有Looper無法建立下面的Handler,程式會直接Crash */    private static final InternalHandler sHandler = new InternalHandler();//預設任務執行器,被賦值為串列任務執行器,就是它,AsyncTask變成串列的了    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;//如下兩個變數我們先不要深究,不影響我們對整體邏輯的理解    private final WorkerRunnable mWorker;    private final FutureTask mFuture;//任務的狀態 預設為掛起,即等待執行,其類型標識為易變的(volatile)    private volatile Status mStatus = Status.PENDING;    //原子布爾型,支援高並發訪問,標識任務是否被取消    private final AtomicBoolean mCancelled = new AtomicBoolean();//原子布爾型,支援高並發訪問,標識任務是否被執行過    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();/*串列執行器的實現,我們要好好看看,它是怎麼把並行轉為串列的 *目前我們需要知道,asyncTask.execute(Params ...)實際上會調用 *SerialExecutor的execute方法,這一點後面再說明。也就是說:當你的asyncTask執行的時候, *首先你的task會被加入到任務隊列,然後排隊,一個個執行 */    private static class SerialExecutor implements Executor {//線性雙向隊列,用來儲存所有的AsyncTask任務        final ArrayDeque mTasks = new ArrayDeque();//當前正在執行的AsyncTask任務        Runnable mActive;        public synchronized void execute(final Runnable r) {//將新的AsyncTask任務加入到雙向隊列中            mTasks.offer(new Runnable() {                public void run() {                    try {//執行AsyncTask任務                        r.run();                    } finally {//當前AsyncTask任務執行完畢後,進行下一輪執行,如果還有未執行任務的話//這一點很明顯體現了AsyncTask是串列執行任務的,總是一個任務執行完畢才會執行下一個任務                        scheduleNext();                    }                }            });//如果當前沒有任務在執行,直接進入執行邏輯            if (mActive == null) {                scheduleNext();            }        }        protected synchronized void scheduleNext() {//從任務隊列中取出隊列頭部的任務,如果有就交給並發線程池去執行            if ((mActive = mTasks.poll()) != null) {                THREAD_POOL_EXECUTOR.execute(mActive);            }        }    }    /**     * 任務的三種狀態     */    public enum Status {        /**         * 任務等待執行         */        PENDING,        /**         * 任務正在執行         */        RUNNING,        /**         * 任務已經執行結束         */        FINISHED,    }    /** 隱藏API:在UI線程中調用,用來初始化Handler */    public static void init() {        sHandler.getLooper();    }    /** 隱藏API:為AsyncTask設定預設執行器 */    public static void setDefaultExecutor(Executor exec) {        sDefaultExecutor = exec;    }    /**     * 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                return postResult(doInBackground(mParams));            }        };        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 occured while executing doInBackground(),                            e.getCause());                } catch (CancellationException e) {                    postResultIfNotInvoked(null);                }            }        };    }    private void postResultIfNotInvoked(Result result) {        final boolean wasTaskInvoked = mTaskInvoked.get();        if (!wasTaskInvoked) {            postResult(result);        }    }//doInBackground執行完畢,發送訊息    private Result postResult(Result result) {        @SuppressWarnings(unchecked)        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,                new AsyncTaskResult(this, result));        message.sendToTarget();        return result;    }    /**     * 返回任務的狀態     */    public final Status getStatus() {        return mStatus;    }    /** * 這個方法是我們必須要重寫的,用來做後台計算 * 所線上程:後台線程     */    protected abstract Result doInBackground(Params... params);    /** * 在doInBackground之前調用,用來做初始化工作 * 所線上程:UI線程     */    protected void onPreExecute() {    }    /** * 在doInBackground之後調用,用來接受後台計算結果更新UI * 所線上程:UI線程     */    protected void onPostExecute(Result result) {    }    /**     * Runs on the UI thread after {@link #publishProgress} is invoked.     /** * 在publishProgress之後調用,用來更新計算進度 * 所線上程:UI線程     */    protected void onProgressUpdate(Progress... values) {    }     /** * cancel被調用並且doInBackground執行結束,會調用onCancelled,表示任務被取消 * 這個時候onPostExecute不會再被調用,二者是互斥的,分別表示任務取消和任務執行完成 * 所線上程:UI線程     */    @SuppressWarnings({UnusedParameters})    protected void onCancelled(Result result) {        onCancelled();    }            protected void onCancelled() {    }    public final boolean isCancelled() {        return mCancelled.get();    }    public final boolean cancel(boolean mayInterruptIfRunning) {        mCancelled.set(true);        return mFuture.cancel(mayInterruptIfRunning);    }    public final Result get() throws InterruptedException, ExecutionException {        return mFuture.get();    }    public final Result get(long timeout, TimeUnit unit) throws InterruptedException,            ExecutionException, TimeoutException {        return mFuture.get(timeout, unit);    }    /**     * 這個方法如何執行和系統版本有關,在AsyncTask的使用規則裡已經說明,如果你真的想使用並行AsyncTask, * 也是可以的,只要稍作修改 * 必須在UI線程調用此方法     */    public final AsyncTask execute(Params... params) {//串列執行        return executeOnExecutor(sDefaultExecutor, params);//如果我們想並存執行,這樣改就行了,當然這個方法我們沒法改//return executeOnExecutor(THREAD_POOL_EXECUTOR, params);    }    /**     * 通過這個方法我們可以自訂AsyncTask的執行方式,串列or並行,甚至可以採用自己的Executor * 為了實現並行,我們可以在外部這麼用AsyncTask: * asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Params... params); * 必須在UI線程調用此方法     */    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會最先執行        onPreExecute();        mWorker.mParams = params;//然後後台計算#doInBackground才真正開始        exec.execute(mFuture);//接著會有#onProgressUpdate被調用,最後是#onPostExecute        return this;    }    /**     * 這是AsyncTask提供的一個靜態方法,方便我們直接執行一個runnable     */    public static void execute(Runnable runnable) {        sDefaultExecutor.execute(runnable);    }    /** * 列印多工緩衝計算進度,onProgressUpdate會被調用     */    protected final void publishProgress(Progress... values) {        if (!isCancelled()) {            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,                    new AsyncTaskResult(this, values)).sendToTarget();        }    }//任務結束的時候會進行判斷,如果任務沒有被取消,則onPostExecute會被調用    private void finish(Result result) {        if (isCancelled()) {            onCancelled(result);        } else {            onPostExecute(result);        }        mStatus = Status.FINISHED;    }//AsyncTask內部Handler,用來發送後台計算進度更新訊息和計算完成訊息    private static class InternalHandler extends Handler {        @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;            }        }    }    private static abstract class WorkerRunnable implements Callable {        Params[] mParams;    }    @SuppressWarnings({RawUseOfParameterizedType})    private static class AsyncTaskResult {        final AsyncTask mTask;        final Data[] mData;        AsyncTaskResult(AsyncTask task, Data... data) {            mTask = task;            mData = data;        }    }}
讓你的AsyncTask在3.0以上的系統中並行起來

通過上面的源碼分析,我已經給出了在3.0以上系統中讓AsyncTask並存執行的方法,現在,讓我們來試一試,代碼還是之前採用的測試代碼,我們要稍作修改,調用AsyncTask的executeOnExecutor方法而不是execute,請看:

 

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)    @Override    public void onClick(View v) {        if (v == mButton) {            new MyAsyncTask(AsyncTask#1).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,);            new MyAsyncTask(AsyncTask#2).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,);            new MyAsyncTask(AsyncTask#3).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,);            new MyAsyncTask(AsyncTask#4).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,);            new MyAsyncTask(AsyncTask#5).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,);        }    }    private static class MyAsyncTask extends AsyncTask {        private String mName = AsyncTask;        public MyAsyncTask(String name) {            super();            mName = name;        }        @Override        protected String doInBackground(String... params) {            try {                Thread.sleep(3000);            } catch (InterruptedException e) {                e.printStackTrace();            }            return mName;        }        @Override        protected void onPostExecute(String result) {            super.onPostExecute(result);            SimpleDateFormat df = new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);            Log.e(TAG, result + execute finish at  + df.format(new Date()));        }    }
下面是系統為4.1.1手機列印出的Log:很顯然,我們的目的達到了,成功的讓AsyncTask在4.1.1的手機上並行起來了,很高興吧!如果你覺得這篇文章對你有用,請在下面評論表示支援吧!

 


 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.