轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/11711405
我們都知道,Android UI是線程不安全的,如果想要在子線程裏進行UI操作,就需要藉助Android的異步消息處理機制。之前我也寫過了一篇文章從源碼層面分析了Android的異步消息處理機制,感興趣的朋友可以參考 Android Handler、Message完全解析,帶你從源碼的角度徹底理解 。
不過爲了更加方便我們在子線程中更新UI元素,Android從1.5版本就引入了一個AsyncTask類,使用它就可以非常靈活方便地從子線程切換到UI線程,我們本篇文章的主角也就正是它了。
AsyncTask很早就出現在Android的API裏了,所以我相信大多數朋友對它的用法都已經非常熟悉。不過今天我還是準備從AsyncTask的基本用法開始講起,然後我們再來一起分析下AsyncTask源碼,看看它是如何實現的,最後我會介紹一些關於AsyncTask你所不知道的祕密。
AsyncTask的基本用法
首先來看一下AsyncTask的基本用法,由於AsyncTask是一個抽象類,所以如果我們想使用它,就必須要創建一個子類去繼承它。在繼承時我們可以爲AsyncTask類指定三個泛型參數,這三個參數的用途如下:
1. Params
在執行AsyncTask時需要傳入的參數,可用於在後臺任務中使用。
2. Progress
後臺任務執行時,如果需要在界面上顯示當前的進度,則使用這裏指定的泛型作爲進度單位。
3. Result
當任務執行完畢後,如果需要對結果進行返回,則使用這裏指定的泛型作爲返回值類型。
因此,一個最簡單的自定義AsyncTask就可以寫成如下方式:
- class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
- ……
- }
當然,目前我們自定義的DownloadTask還是一個空任務,並不能進行任何實際的操作,我們還需要去重寫AsyncTask中的幾個方法才能完成對任務的定製。經常需要去重寫的方法有以下四個:
1. onPreExecute()
這個方法會在後臺任務開始執行之間調用,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。
2. doInBackground(Params...)
這個方法中的所有代碼都會在子線程中運行,我們應該在這裏去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。
3. onProgressUpdate(Progress...)
當在後臺任務中調用了publishProgress(Progress...)方法後,這個方法就很快會被調用,方法中攜帶的參數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應的更新。
4. onPostExecute(Result)
當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作爲參數傳遞到此方法中,可以利用返回的數據來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。
因此,一個比較完整的自定義AsyncTask就可以寫成如下方式:
- class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
- @Override
- protected void onPreExecute() {
- progressDialog.show();
- }
- @Override
- protected Boolean doInBackground(Void... params) {
- try {
- while (true) {
- int downloadPercent = doDownload();
- publishProgress(downloadPercent);
- if (downloadPercent >= 100) {
- break;
- }
- }
- } catch (Exception e) {
- return false;
- }
- return true;
- }
- @Override
- protected void onProgressUpdate(Integer... values) {
- progressDialog.setMessage("當前下載進度:" + values[0] + "%");
- }
- @Override
- protected void onPostExecute(Boolean result) {
- progressDialog.dismiss();
- if (result) {
- Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();
- }
- }
- }
- new DownloadTask().execute();
分析AsyncTask的源碼
雖然AsyncTask這麼簡單好用,但你知道它是怎樣實現的嗎?那麼接下來,我們就來分析一下AsyncTask的源碼,對它的實現原理一探究竟。注意這裏我選用的是Android 4.0的源碼,如果你查看的是其它版本的源碼,可能會有一些出入。
從之前DownloadTask的代碼就可以看出,在啓動某一個任務之前,要先new出它的實例,因此,我們就先來看一看AsyncTask構造函數中的源碼,如下所示:
- public AsyncTask() {
- mWorker = new WorkerRunnable<Params, Result>() {
- public Result call() throws Exception {
- mTaskInvoked.set(true);
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- return postResult(doInBackground(mParams));
- }
- };
- mFuture = new FutureTask<Result>(mWorker) {
- @Override
- protected void done() {
- try {
- final Result result = get();
- postResultIfNotInvoked(result);
- } 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);
- } catch (Throwable t) {
- throw new RuntimeException("An error occured while executing "
- + "doInBackground()", t);
- }
- }
- };
- }
接着如果想要啓動某一個任務,就需要調用該任務的execute()方法,因此現在我們來看一看execute()方法的源碼,如下所示:
- public final AsyncTask<Params, Progress, Result> execute(Params... params) {
- return executeOnExecutor(sDefaultExecutor, params);
- }
- 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;
- }
- public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
- ……
- private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
- 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);
- }
- }
- }
- void innerRun() {
- if (!compareAndSetState(READY, RUNNING))
- return;
- runner = Thread.currentThread();
- if (getState() == RUNNING) { // recheck after setting thread
- V result;
- try {
- result = callable.call();
- } catch (Throwable ex) {
- setException(ex);
- return;
- }
- set(result);
- } else {
- releaseShared(0); // cancel
- }
- }
- public Result call() throws Exception {
- mTaskInvoked.set(true);
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- return postResult(doInBackground(mParams));
- }
- private Result postResult(Result result) {
- Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
- new AsyncTaskResult<Result>(this, result));
- message.sendToTarget();
- return result;
- }
- 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 void finish(Result result) {
- if (isCancelled()) {
- onCancelled(result);
- } else {
- onPostExecute(result);
- }
- mStatus = Status.FINISHED;
- }
我們注意到,在剛纔InternalHandler的handleMessage()方法裏,還有一種MESSAGE_POST_PROGRESS的消息類型,這種消息是用於當前進度的,調用的正是onProgressUpdate()方法,那麼什麼時候纔會發出這樣一條消息呢?相信你已經猜到了,查看publishProgress()方法的源碼,如下所示:
- protected final void publishProgress(Progress... values) {
- if (!isCancelled()) {
- sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
- new AsyncTaskResult<Progress>(this, values)).sendToTarget();
- }
- }
讀到這裏,相信你對AsyncTask中的每個回調方法的作用、原理、以及何時會被調用都已經搞明白了吧。
關於AsyncTask你所不知道的祕密
不得不說,剛纔我們在分析SerialExecutor的時候,其實並沒有分析的很仔細,僅僅只是關注了它會調用mFuture中的run()方法,但是至於什麼時候會調用我們並沒有進一步地研究。其實SerialExecutor也是AsyncTask在3.0版本以後做了最主要的修改的地方,它在AsyncTask中是以常量的形式被使用的,因此在整個應用程序中的所有AsyncTask實例都會共用同一個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);
- }
- }
- }
那麼後面添加的任務豈不是永遠得不到處理了?當然不是,看一看offer()方法裏傳入的Runnable匿名類,這裏使用了一個try finally代碼塊,並在finally中調用了scheduleNext()方法,保證無論發生什麼情況,這個方法都會被調用。也就是說,每次當一個任務執行完畢後,下一個任務纔會得到執行,SerialExecutor模仿的是單一線程池的效果,如果我們快速地啓動了很多任務,同一時刻只會有一個線程正在執行,其餘的均處於等待狀態。Android照片牆應用實現,再多的圖片也不怕崩潰 這篇文章中例子的運行結果也證實了這個結論。
不過你可能還不知道,在Android 3.0之前是並沒有SerialExecutor這個類的,那個時候是直接在AsyncTask中構建了一個sExecutor常量,並對線程池總大小,同一時刻能夠運行的線程數做了規定,代碼如下所示:
- private static final int CORE_POOL_SIZE = 5;
- private static final int MAXIMUM_POOL_SIZE = 128;
- private static final int KEEP_ALIVE = 10;
- ……
- private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
- MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
因此在3.0版本中AsyncTask的改動還是挺大的,在3.0之前的AsyncTask可以同時有5個任務在執行,而3.0之後的AsyncTask同時只能有1個任務在執行。爲什麼升級之後可以同時執行的任務數反而變少了呢?這是因爲更新後的AsyncTask已變得更加靈活,如果不想使用默認的線程池,還可以自由地進行配置。比如使用如下的代碼來啓動任務:
- Executor exec = new ThreadPoolExecutor(15, 200, 10,
- TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
- new DownloadTask().executeOnExecutor(exec);
好了,到這裏我們就已經把關於AsyncTask的所有重要內容深入淺出地理解了一遍,相信在將來使用它的時候能夠更加得心應手。