Android應用AsyncTask處理機制詳解及原始碼分析

工匠若水發表於2015-07-11

1 背景

Android非同步處理機制一直都是Android的一個核心,也是應用工程師面試的一個知識點。前面我們分析了Handler非同步機制原理(不瞭解的可以閱讀我的《Android非同步訊息處理機制詳解及原始碼分析》文章),這裡繼續分析Android的另一個非同步機制AsyncTask的原理。

當使用執行緒和Handler組合實現非同步處理時,當每次執行耗時操作都建立一條新執行緒進行處理,效能開銷會比較大。為了提高效能我們使用AsyncTask實現非同步處理(其實也是執行緒和handler組合實現),因為其內部使用了java提供的執行緒池技術,有效的降低了執行緒建立數量及限定了同時執行的執行緒數,還有一些針對性的對池的優化操作。所以說AsyncTask是Android為我們提供的方便編寫非同步任務的工具類。

2 例項演示

先看下使用AsyncTask模擬下載的效果圖:

這裡寫圖片描述

看下程式碼,如下:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        new TestAsyncTask(this).execute();
    }

    static final class TestAsyncTask extends AsyncTask<Void, Integer, Boolean> {
        //如上三個泛型引數從左到右含義依次為:
        //1. 在執行AsyncTask時需要傳入的引數,可用於在後臺任務中使用。
        //2. 後臺任務執行時,如果需要在介面上顯示當前的進度,則使用這個。
        //3. 當任務執行完畢後,如果需要對結果進行返回,則使用這個。
        private Context mContext = null;
        private ProgressDialog mDialog = null;
        private int mCount = 0;

        public TestAsyncTask(Context context) {
            mContext = context;
        }

        //在後臺任務開始執行之間呼叫,用於進行一些介面上的初始化操作
        protected void onPreExecute() {
            super.onPreExecute();
            mDialog = new ProgressDialog(mContext);
            mDialog.setMax(100);
            mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            mDialog.show();
        }

        //這個方法中的所有程式碼都會在子執行緒中執行,我們應該在這裡去處理所有的耗時任務
        protected Boolean doInBackground(Void... params) {
            while (mCount < 100) {
                publishProgress(mCount);
                mCount += 20;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }

        //當在後臺任務中呼叫了publishProgress(Progress...)方法後,這個方法就很快會被呼叫
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            mDialog.setProgress(values[0]);
        }

        //當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被呼叫
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if (aBoolean && mDialog != null && mDialog.isShowing()) {
                mDialog.dismiss();
            }
        }
    }
}

可以看見Android幫我們封裝好的AsyncTask還是很方便使用的,我們們不做過多說明。接下來直接分析原始碼。

3 Android5.1.1(API 22)AsyncTask原始碼分析

通過原始碼可以發現AsyncTask是一個抽象類,所以我們在在上面使用時需要實現它。

那怎麼下手分析呢?很簡單,我們就依據上面示例的流程來分析原始碼,具體如下。

3-1 AsyncTask例項化原始碼分析

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

        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 occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

看見註釋沒有,AsyncTask的例項化只能在UI執行緒中。然後整個建構函式就只初始化了兩個AsyncTask類的成員變數(mWorker和mFuture)。mWorker為匿名內部類的例項物件WorkerRunnable(實現了Callable介面),mFuture為匿名內部類的例項物件FutureTask,傳入了mWorker作為形參(重寫了FutureTask類的done方法)。

3-2 AsyncTask的execute方法原始碼分析

正如上面例項一樣,得到AsyncTask例項化物件之後就執行了execute方法,所以看下execute方法的原始碼,如下:

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

可以看見,execute調運了executeOnExecutor方法,executeOnExecutor方法除過傳入了params形參以外,還傳入了一個static的SerialExecutor物件(SerialExecutor實現了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;
    }

首先判斷AsyncTask非同步任務的狀態,當處於RUNNING和FINISHED時就報IllegalStateException非法狀態異常。由此可以看見一個AsyncTask的execute方法只能被調運一次。接著看見17行onPreExecute();沒有?看下這個方法原始碼,如下:

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

空方法,而且通過註釋也能看見,這不就是我們AsyncTask中第一個執行的方法嗎?是的。

回過頭繼續往下看,看見20行exec.execute(mFuture);程式碼沒?exec就是形參出入的上面定義的static SerialExecutor物件(SerialExecutor實現了Executor介面),所以execute就是SerialExecutor靜態內部類的方法嘍,在執行execute方法時還傳入了AsyncTask建構函式中例項化的第二個成員變數mFuture。我們來看下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);
            }
        }
    }

在原始碼中可以看見,SerialExecutor在AsyncTask中是以常量的形式被使用的,所以在整個應用程式中的所有AsyncTask例項都會共用同一個SerialExecutor物件。

接著可以看見,SerialExecutor是使用ArrayDeque這個佇列來管理Runnable物件的,如果我們一次性啟動了很多個任務,首先在第一次執行execute()方法的時候會呼叫ArrayDeque的offer()方法將傳入的Runnable物件新增到佇列的最後,然後判斷mActive物件是不是等於null,第一次執行是null,然後呼叫scheduleNext()方法,在這個方法中會從佇列的頭部取值,並賦值給mActive物件,然後呼叫THREAD_POOL_EXECUTOR去執行取出的取出的Runnable物件。之後如果再有新的任務被執行時就等待上一個任務執行完畢後才會得到執行,所以說同一時刻只會有一個執行緒正在執行,其餘的均處於等待狀態,這就是SerialExecutor類的核心作用。

我們再來看看上面用到的THREAD_POOL_EXECUTOR與execute,如下:

public abstract class AsyncTask<Params, Progress, Result> {
    ......
    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);
    ......
}

看見沒有,實質就是在一個執行緒池中執行,這個THREAD_POOL_EXECUTOR執行緒池是一個常量,也就是說整個App中不論有多少AsyncTask都只有這一個執行緒池。也就是說上面SerialExecutor類中execute()方法的所有邏輯就是在子執行緒中執行,注意SerialExecutor的execute方法有一個Runnable引數,這個引數就是mFuture物件,所以我們看下FutureTask類的run()方法,如下原始碼:

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

看見沒有?第7行的c = callable;其實就是AsyncTask建構函式中例項化FutureTask物件時傳入的引數mWorker。12行看見result = c.call();沒有?其實就是調運WorkerRunnable類的call方法,所以我們回到AsyncTask建構函式的WorkerRunnable匿名內部內中可以看見如下:

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

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

看見沒有?在postResult()方法的引數裡面,我們可以看見doInBackground()方法。所以這驗證了我們上面例子中使用的AsyncTask,首先在主執行緒執行onPreExecute方法,接著在子執行緒執行doInBackground方法,所以這也就是為什麼我們可以在doInBackground()方法中去處理耗時操作的原因了,接著等待doInBackground方法耗時操作執行完畢以後將返回值傳遞給了postResult()方法。所以我們來看下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拿到的是哪個Handler吧,如下:

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

看見沒有,拿到的是MainLooper,也就是說在在UI執行緒中的Handler(不清楚的請閱讀《Android非同步訊息處理機制詳解及原始碼分析》文章)。所以上面的方法其實就是將子執行緒的資料傳送到了UI來處理,也就是通過MESSAGE_POST_RESULT在handleMessage來處理。所以我們繼續看handleMessage中的result.mTask.finish(result.mData[0]);就會發現finish的程式碼如下:

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

看見沒有?依據返回值true與false回撥AsyncTask的onPostExecute或者onCancelled方法。

到此是不是會好奇onProgressUpdate方法啥時候調運的呢?繼續往下看可以發現handleMessage方法中的MESSAGE_POST_PROGRESS不就是回撥我們UI Thread中的onProgressUpdate方法嗎?那怎麼樣才能讓他回撥呢?追蹤MESSAGE_POST_PROGRESS訊息你會發現如下:

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

額,沒意思了。這不就是我們上面例子中的在子執行緒的doInBackground耗時操作中調運通知回撥onProgressUpdate的方法麼。

看見沒有,AsyncTask的實質就是Handler非同步訊息處理機制(不清楚的請閱讀《Android非同步訊息處理機制詳解及原始碼分析》文章),只是對執行緒做了優化處理和封裝而已。

4 為當年低版本AsyncTask的臭名正身

接觸Android比較久的可能都知道,在Android 3.0之前是並沒有SerialExecutor這個類的(上面有分析)。那些版本的程式碼是直接建立了指定大小的執行緒池常量來執行task的。其中MAXIMUM_POOL_SIZE = 128;,所以那時候如果我們應用中一個介面需要同時建立的AsyncTask執行緒大於128(批量獲取資料,譬如照片瀏覽瀑布流一次載入)程式直接就掛了。所以當時的AsyncTask因為這個原因臭名昭著。

回過頭來看看現在高版本的AsyncTask,是不是沒有這個問題了吧?因為現在是順序執行的。而且更勁爆的是現在的AsyncTask還直接提供了客戶化實現Executor介面功能,使用如下方法執行AsyncTask即可使用自定義Executor,如下:

    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        ......
        return this;
    }

可以看出,在3.0以上版中AsyncTask已經不存在那個臭名昭著的Bug了,所以可以放心使用了,媽媽再也不用擔心我的AsyncTask出Bug了。

5 AsyncTask與Handler非同步機制對比

前面文章也分析過Handler了,這裡也分析了AsyncTask,現在把他們兩拽一起來比較比較。具體如下:

  1. AsyncTask是對Handler與Thread的封裝。
  2. AsyncTask在程式碼上比Handler要輕量級別,但實際上比Handler更耗資源,因為AsyncTask底層是一個執行緒池,而Handler僅僅就是傳送了一個訊息佇列。但是,如果非同步任務的資料特別龐大,AsyncTask執行緒池比Handler節省開銷,因為Handler需要不停的new Thread執行。
  3. AsyncTask的例項化只能在主執行緒,Handler可以隨意,只和Looper有關係。

6 AsyncTask總結

到此整個Android的AsyncTask已經分析完畢,相信你現在對於AsyncTask會有一個很深入的理解與認識了。

相關文章