理解 AsyncTask 原理

本人只是 Android小菜一個,寫技術文檔只是爲了總結本身在最近學習到的知識,歷來不敢爲人師,若是裏面有些不正確的地方請你們盡情指出,謝謝!java

1. 概述

以前講解了可以在後臺工做線程中執行耗時任務的IntentService框架,在這裏咱們繼續學習Android提供的另一個異步執行任務的框架AsyncTask,它和IntentService既有類似點也有不一樣點,其類似之處在於都能在新的線程中執行耗時任務防止阻塞主線程,不一樣之處在於AsyncTask可以追蹤任務的執行過程和結果並在主線程中顯示出來。android

咱們先用一個簡單的示例演示下該如何使用AsyncTask,再經過源代碼分析其內部實現原理。bash

2. AsyncTask 的使用方式

在開始使用前,先來看下Android SDK中對AsyncTask的介紹,請聲明以下:併發

/** * <p>AsyncTask enables proper and easy use of the UI thread. This class allows you * to perform background operations and publish results on the UI thread without * having to manipulate threads and/or handlers.</p> * * <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} * and does not constitute a generic threading framework. AsyncTasks should ideally be * used for short operations (a few seconds at the most.) If you need to keep threads * running for long periods of time, it is highly recommended you use the various APIs * provided by the <code>java.util.concurrent</code> package such as {@link Executor}, * {@link ThreadPoolExecutor} and {@link FutureTask}.</p> */
public abstract class AsyncTask<Params, Progress, Result> { ... }
複製代碼

AsyncTask是一個基於ThreadHandler設計的幫助類,其容許在後臺線程執行耗時任務並把處理進度和結果發佈到主線程,不過通常適用於執行時間相對較短的任務,通常執行時間不要超過幾秒。app

2.1 使用示例

先來看一段示例代碼:框架

private class DownloadAsyncTask extends AsyncTask<String, Integer, Long> {
    @Override
    public void onPreExecute() {
        mProgress.setVisibility(View.VISIBLE);
        mProgress.setMax(100);
        mProgress.setProgress(0);
    }

    @Override
    public Long doInBackground(String... uris) {
        int count = uris.length;
        long size = 0;
        for (int i = 1; i <= count; i ++) {
            try {
                // 休眠5秒模擬下載過程
                Thread.sleep(5 * 1000);
                // 假設每一個下載文件的大小爲(序號*100)
                size += i * 100;
                // 發佈進度更新
                publishProgress( (100* i )/count);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
        return size;
    }

    @Override
    public void onProgressUpdate(Integer... progress) {
        mProgress.setProgress(progress[0]);
    }

    @Override
    public void onPostExecute(Long result) {
        mText.setText(String.valueOf(result));
    }
}
複製代碼

這段代碼主要是模擬了文件下載過程,在下載過程當中實時更新進度,並在下載完成後在界面顯示下載文件的總大小。less

經過這段代碼能夠看到要使用AsyncTask實行異步任務是很是容易的,只須要作兩件事:異步

  • 肯定在整個處理過程當中須要的參數類型,包括Params,ProgressResult,分別對應着輸入參數、進度參數和結果參數。
  • 實現必要的回調方法,其中必須是實現的是doInBackground,耗時任務正是在這裏進行處理的,能夠想象doInBackground必定是在子線程裏進行的;其餘可選實現方法包括onPreExecute,onProgressUpdateonPostExecute,這些在示例中都參與了UI的更新,因此必定是在主線程中進行的。

2.2 參數介紹

首先看下AsyncTask中對參數的聲明:async

public abstract class AsyncTask<Params, Progress, Result> { ... }
複製代碼

能夠發現AsyncTask中使用的都是泛型參數,在使用過程當中要根據需求選擇合適的參數類型,在示例中使用的參數類型分別是String,IntegerLong,若是某一個參數是不須要的,能夠用Void來表示,下面經過一個表格來對每一個參數進行說明:ide

參數聲明 含義 做用 產生處/調用處 注意事項
Params 輸入參數 任務開始執行時客戶端發送開始參數 execute()中發送,在doInBackground()中調用。 可變參類型
Progress 過程參數 任務後臺執行過程當中服務端發佈的當前執行進度 在doInBackground()中產生並經過publishProgess()發送,在onProgressUpdate()調用。 可變參類型
Result 結果參數 任務執行完成後服務端發送的執行結果 在doInBackground()中產生並在onPostExecute()中調用。

參數類型不能是基本數據類型,要使用對應的封裝類型,例如示例的ProgressResult參數使用的IntegerLong而不是intlong

2.3 回調接口

AsyncTask中有幾個重要的回調接口,下面分別介紹:

  • onPreExecute(): 在主線程中運行,主要是在後臺線程開始執行任務以前進行某些UI的初始化,例如進度條的顯示,可選擇實現,其聲明以下:
/** * Runs on the UI thread before {@link #doInBackground}. */
@MainThread
protected void onPreExecute() { }
複製代碼
  • doInBackground: 在後臺線程中運行,主要是接收客戶端發送過來的參數,在後臺執行耗時任務併發布執行進度和執行結果,例如文件下載任務,是在使用過程當中必須實現的接口,其聲明以下:
/** * Override this method to perform a computation on a background thread. The * specified parameters are the parameters passed to {@link #execute} * by the caller of this task. * * This method can call {@link #publishProgress} to publish updates * on the UI thread. * * @param params The parameters of the task. * * @return A result, defined by the subclass of this task. * */
@WorkerThread
protected abstract Result doInBackground(Params... params);
複製代碼
  • publishProgress: 在後臺線程中運行,主要是發佈任務的當前執行進度,以方便在主線程中顯示,不須要從新實現直接調用,其聲明以下:
/** * This method can be invoked from {@link #doInBackground} to * publish updates on the UI thread while the background computation is * still running. Each call to this method will trigger the execution of * {@link #onProgressUpdate} on the UI thread. * * {@link #onProgressUpdate} will not be called if the task has been * canceled. * * @param values The progress values to update the UI with. */
@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}
複製代碼
  • onProgressUpdate: 在主線程中運行,主要是更新當前的執行進度,例如更新進度條進度,可選擇實現,其聲明以下:
/** * Runs on the UI thread after {@link #publishProgress} is invoked. * The specified values are the values passed to {@link #publishProgress}. * * @param values The values indicating progress. */
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values) { }
複製代碼
  • onPostExecute: 在主線程中運行,主要是接收doInBackground返回的執行結果並在主線程中顯示,例如顯示下載文件大小,可選擇實現,其聲明以下:
/** * <p>Runs on the UI thread after {@link #doInBackground}. The * specified result is the value returned by {@link #doInBackground}.</p> * * <p>This method won't be invoked if the task was cancelled.</p> * * @param result The result of the operation computed by {@link #doInBackground}. */
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onPostExecute(Result result) { }
複製代碼

經過一個表格來總結下這些重要的回調方法:

回調方法 運行線程 做用 執行順序 是否須要從新實現
onPreExecute 主線程 在開始執行後臺任務前進行初始化 首先開始執行 可選
doInBackground 後臺線程 執行後臺耗時任務完成後返回結果 onPreExecute 執行完成後執行 必須實現
publishProgress 後臺線程 在執行任務過程當中發佈執行進度 在 doInBackground 中執行 無須實現,直接調用。
onProgressUpdate 主線程 接收進度並在主線程處理 在 publishProgress 以後執行 可選
onPostExecute 主線程 接收執行結果並在主線程處理 在 doInBackground 執行完成後執行 可選

3. AsyncTask 的原理

前面已經大體分析了AsyncTask的使用方法以及重要的回調方法,如今來看下其內部是如何實現的,主要關注兩方面的信息:如何進行線程的切換和如何組織調用上述的回調方法。

客戶端是經過AsyncTask.execute()來開啓異步任務的,咱們就以這個爲切入點來分析,首先看下execute()作了什麼:

/** * Executes the task with the specified parameters. The task returns * itself (this) so that the caller can keep a reference to it. * * <p>Note: this function schedules the task on a queue for a single background * thread or pool of threads depending on the platform version. When first * introduced, AsyncTasks were executed serially on a single background thread. * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed * to a pool of threads allowing multiple tasks to operate in parallel. Starting * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being * executed on a single thread to avoid common application errors caused * by parallel execution. If you truly want parallel execution, you can use * the {@link #executeOnExecutor} version of this method * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings * on its use. * * <p>This method must be invoked on the UI thread. */
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}
複製代碼

execute()內部直接調用executeOnExecutor()來開啓異步任務,這點咱們稍後分析,其聲明中有段話須要特別留意:這個任務是放在一個單獨的後臺線程中順序執行仍是放在線程池中並行執行,和具體平臺有關。在AsyncTask最初被引進的時候是在一個單獨的後臺線程中順序執行的,後續幾回改變,目前默認是順序執行的。若是確實想並行執行的話,就須要直接調用executeOnExecutor()而且傳入合適的Executor。 如今從新回到對execute()的分析,因爲其內部調用的是executeOnExecutor(sDefaultExecutor, params),那就來看下這個函數在作什麼?

/** * <p>This method must be invoked on the UI thread. * * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a * convenient process-wide thread pool for tasks that are loosely coupled. * @param params The parameters of the task. * * @return This instance of AsyncTask. * * @throws IllegalStateException If {@link #getStatus()} returns either * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. * */
@MainThread
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;
    // 回調方法中首先被調用的方法,因爲"execute()"是在主線程中執行的,
    // 目前爲止也沒有進行線程的切換,因此"onPreExecute"也是在主線程中執行的。
    onPreExecute();

    // 經過幾回「封裝」,把參數傳入"Executor"內部,等待執行。
    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}
複製代碼

整段代碼的前半部分比較好理解,就是進行狀態判斷和更新,而後調用onPreExecute接口,接下來的兩行代碼咋一看實在讓人費解。不要急不要慌,一步步來分析,首先要搞清楚mWorker是什麼,來看下相關代碼:

private final WorkerRunnable<Params, Result> mWorker;
    
mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        mTaskInvoked.set(true);
        Result result = null;
        try {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            result = doInBackground(mParams);
            Binder.flushPendingCommands();
        } catch (Throwable tr) {
            mCancelled.set(true);
            throw tr;
        } finally {
            postResult(result);
        }
        return result;
    }
};

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
    Params[] mParams;
}
複製代碼

能夠看到mWorkerWorkerRunnable的對象,而WorkerRunnable又拓展了Callable接口,看下Callable接口是什麼:

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
複製代碼

在這段說明裏,把CallableRunnable進行了對比,它們都是用來在其餘線程裏執行的,不一樣之處在於Callable中的call()方法可以返回結果和檢查異常,而Runnable中的run沒有這個功能,因此能夠簡單地把Callable當作Runnable來看待,只是被執行的方法是call()並且能夠返回結果。因爲WorkerRunnable繼承了Callable,又新增了參數,就能夠把mWorker當作一個既能夠接受參數有能夠在執行後返回結果的Runnable對象。 如今來看mFuture是什麼,相關代碼以下:

private final FutureTask<Result> mFuture;

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

能夠看到mFutureFutureTask對象而且是利用mWorker做爲參數進行構造的,看下FutureTask是什麼:

/** * A cancellable asynchronous computation. This class provides a base * implementation of {@link Future}, with methods to start and cancel * a computation, query to see if the computation is complete, and * retrieve the result of the computation. The result can only be * retrieved when the computation has completed; the {@code get} * methods will block if the computation has not yet completed. Once * the computation has completed, the computation cannot be restarted * or cancelled (unless the computation is invoked using * {@link #runAndReset}). * * <p>A {@code FutureTask} can be used to wrap a {@link Callable} or * {@link Runnable} object. Because {@code FutureTask} implements * {@code Runnable}, a {@code FutureTask} can be submitted to an * {@link Executor} for execution. */
public class FutureTask<V> implements RunnableFuture<V> { 
    // 核心方法,其餘方法省略。
    public void run() {
    if (state != NEW ||
        !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    // 執行封裝的 Callable.call() 方法並獲得計算結果
                    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);
        }
    }
}
複製代碼

FutureTask是用來進行異步計算的,這個計算能夠開始、取消、查詢等等,並且能夠用來封裝Callable,其中核心的方法就是run(),經過上面的代碼邏輯能夠看到在run()中最核心的就是調用所封裝的Callable.call()來進行計算並獲得結果。

mFuture所封裝的正是mWorker,因此其最終結果就是調用mWorkder.call()方法,如今在返回來看看以前不理解的那幾行代碼:

mWorker.mParams = params;
exec.execute(mFuture);
複製代碼

這兩行代碼就是把mFuture交給線程池來執行,完成了線程的切換,也就是說mFuture的執行是在後臺線程中進行的,mFuture最終會調用mWorker.call()方法,再回過頭來看看mWorker.call()作了什麼:

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        // 設置了任務調用的狀態爲「被調用」
        mTaskInvoked.set(true);
        Result result = null;
        try {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            // 執行耗時任務,這時候已經在新的線程裏了。
            result = doInBackground(mParams);
            Binder.flushPendingCommands();
        } catch (Throwable tr) {
            mCancelled.set(true);
            throw tr;
        } finally {
            // 分發執行結果
            postResult(result);
        }
        return result;
    }
}; 
複製代碼

最終會在新的線程裏執行doInBackground(),執行以後就經過postResult()分發執行結果:

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

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

    AsyncTaskResult(AsyncTask task, Data... data) {
        mTask = task;
        mData = data;
    }
}
複製代碼

postResult()會經過getHandler()來構建Message對象,最終把結果包裝成AsyncTaskResult進行分發,那getHandler()返回的是哪一個Handler呢?

private Handler getHandler() {
    return mHandler;
}

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);
...
}

// 調用這個構造函數來建立AsyncTask實例
public AsyncTask() {
    this((Looper) null);
}
複製代碼

從這段代碼能夠看到getHandler()返回的是mHandler對象,而mHandler是在AsyncTask裏進行建立的,因爲在建立AsyncTask實例是並無傳入callbackLooper,最終的效果就是mHandler是經過getMainHandler()實例化的,獲得的是主線程的Handler,其代碼以下:

private static Handler getMainHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler(Looper.getMainLooper());
        }
        return sHandler;
    }
}
複製代碼

兜兜轉轉,doInBackground()的執行結果會交給InternalHandler來處理:

private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }

    @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
                // 處理 doInBackground()的執行結果
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                // 處理 publishProgress()發佈的執行進度
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}
複製代碼

InternalHandler接收到doInBackground()的執行結果後,會調用result.mTask.finish()來繼續處理結果,實際調用的就是AsyncTask.finish(),來看看其代碼:

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        // 正常結束時調用 onPostExecute()處理執行結果
        onPostExecute(result);
    }
    // 更新狀態爲「完成」
    mStatus = Status.FINISHED;
}
複製代碼

在這裏終於看到onPostExecute()獲得調用了,因爲mHandler是用主線程HandleronPostExecute()也就是在主線程中執行的了。 到這裏,終於理清了execute()->onPreExecute()->doInBackground()->onPostExecute()的整個調用過程了,也明白了在整個過程當中的線程切換。

細心的讀者會發現onProgressUpdate()在哪裏獲得調用的並無說起,這個問題就留給你們去完成吧,相信經過前面層層解析,這個問題會很容易獲得解決!

4. 總結

本文先經過一個簡單的模擬下載任務的示例展現了AsyncTask的使用方法並分析其參數類型和重要回調方法,最後經過層層遞進地分析源碼,明白了AsyncTask的內部原理和在執行任務過程當中是如何進行線程切換的。固然,還有不少細節在文中並無說起,但這並不會影響對其原理的理解,「忽略無關細節」也是咱們平時學習源碼過程當中常常採用的方法。

相關文章
相關標籤/搜索