《Android開發藝術探索》 -- AsyncTask 工作原理

前言


AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主線程並在主線程中更新UI。從實現上來說,AsyncTask封裝了Thread和Handler,通過AsyncTask可以更加方便地執行後臺任務以及在主線程中訪問UI,但是AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時的任務來說,建議使用線程池。

AsyncTask 泛型參數

AsyncTask是一個抽象的泛型類,它提供了Params、Progress和Result這三個泛型參數。
  1. Params:表示參數的類型;
  2. Progress:表示後臺任務的執行進度的類型;
  3. Result:表示後臺任務的返回結果的類型;
如果AsyncTask確實不需要傳遞具體的參數,那麼這三個泛型參數可以用Void來代替。AsyncTask這個類的聲明如下所示。
  1. public abstract class AsyncTask<Params, Progress, Result>

AsyncTask 核心方法

AsyncTask提供了5個核心方法,它們的含義如下所示。
  1. onPreExecute():在主線程中執行,在異步任務執行之前,此方法會被調用,一般可以用於做一些準備工作。
  2. doInBackground(Params... params):在線程池中執行,此方法用於執行異步任務,params參數表示異步任務的輸入參數。在此方法中可以通過publishProgress方法來更新任務的進度,publishProgress方法會調用onProgressUpdate方法。另外此方法需要返回計算結果給onPostExecute方法。
  3. onProgressUpdate(Progress... values):在主線程中執行,當後臺任務的執行進度發生改變時此方法會被調用。
  4. onPostExecute(Result result):在主線程中執行,在異步任務執行之後,此方法會被調用,其中result參數是後臺任務的返回值,即doInBackground的返回值。
  5. Cancel(Boolean mayInterruptIfRunning):取消當前任務,mayInterruptIfRunning參數代表是否中斷,true:中斷,false:不中斷。
上面這幾個方法,onPreExecute先執行,接着是doInBackground,最後纔是onPostExecute。除了上述四個方法以外,AsyncTask還提供了onCanceled()方法,它同樣在主線程中執行,當異步任務被取消時,onCanceled()方法會被調用,這個時候onPostExecute則不會被調用。

AsyncTask 使用示例

下面提供一個典型的示例,如下所示。
  1. private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
  2. protected Long doInBackground(URL... urls) {
  3. int count = urls.length;
  4. long totalSize = 0;
  5. for(int i=0; i<count; i++) {
  6. totalSize += Downloader.downloadFile(urls[i]);
  7. // 更新進度,就是通過handler發送進度給主線程,會回調onProgressUpdate方法
  8. publishProgress((int) (i / (float)count) * 100));
  9. if(isCanceled()) {
  10. break;
  11. }
  12. }
  13. return totalSize;
  14. }
  15. protected void onProgressUpdate(Integer... progress) {
  16. setProgressPercent(progress[0]);
  17. }
  18. protected void onPostExecute(Long result) {
  19. showDialog("Download " + result + " bytes");
  20. }
  21. }
在上面的代碼中,實現了一個具體的AsyncTask類,這個類主要用於模擬文件的下載過程,它的輸入參數類型爲URL,後臺任務的進度參數爲Integer,而後臺任務的返回結果爲Long參數。注意到doInBackground和onProgressUpdate方法它們的參數中均包含...的字樣,在Java中...表示參數的數量不定,它是一種數組型參數,...的概念和C語言中的...是一致的。當要執行上述下載任務時,可以通過如下方式來完成:
  1. new DownloadFilesTask().execute(url1, url2, url3);
在DownloadFilesTask中,doInBackground用來執行具體的下載任務並通過publishProgress方法來更新下載的進度,同時還要判斷下載任務是否被外界取消了。當下載任務完成後,doInBackground會返回結果,即下載的總字節數。需要注意的是,doInBackground是在線程池中執行的。onProgressUpdate用於更新界面中下載的進度,它運行在主線程,當publishProgress被調用時,此方法就會被調用。當下載任務完成後,onPostExecute方法就會被調用,它也是運行在主線程中,這個時候我們就可以在界面上做出一些提示,比如彈出一個對話框告知用戶下載已經完成。

AsyncTask 條件限制

AsyncTask在具體的使用過程中也是有一些條件限制的,主要有如下幾點:
  • AsyncTask 的類必須在主線程中加載,這就意味着第一次訪問AsyncTask必鬚髮生在主線程,當然這個過程在Android 4.1及以上版本中已經被系統自動完成。
  • AsyncTask的對象必須在主線程中創建。
  • AsyncTask的ececute方法必須在UI線程調用。
  • 不要在程序中直接調用onPreExecute()、onPostExecute()、doInBackground()和onProgressUpdate()方法。
  • 一個AsyncTask對象只能執行一次,即只能調用一次execute()方法,否則會報運行時異常。
  • 在Android 1.6之前,AsyncTask是串行執行任務的,Android 1.6的時候AsyncTask開始採用線程池處理並行任務,但是從Android 3.0 開始,爲了避免AsyncTask所帶來的併發錯誤,AsyncTask又採用一個線程來串行執行任務。儘管如此,在Android 3.0以及後續的版本中,我們仍然可以通過AsyncTask的executeOnExecutor方法來並行執行任務。

AsyncTask 工作原理

爲了分析AsyncTask的工作原理,我們從它的execute方法開始分析,execute方法又會調用executeOnExecutor方法,它們的實現如下所示:
  1. public final AsyncTask<Params, Progress, Result> execute(Params... params) {
  2. return executeOnExecutor(sDefaultExecutor, params);
  3. }
  1. public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
  2. Params... params) {
  3. if (mStatus != Status.PENDING) {
  4. switch (mStatus) {
  5. case RUNNING:
  6. throw new IllegalStateException("Cannot execute task:"
  7. + " the task is already running.");
  8. case FINISHED:
  9. throw new IllegalStateException("Cannot execute task:"
  10. + " the task has already been executed "
  11. + "(a task can be executed only once)");
  12. }
  13. }
  14. // 設置爲運行狀態
  15. mStatus = Status.RUNNING;
  16. // 調用子類的onPreExecute方法,在主線程中執行
  17. onPreExecute();
  18. // 把參數傳遞給mWorker工作線程
  19. mWorker.mParams = params;
  20. exec.execute(mFuture);
  21. return this;
  22. }
在上面的代碼中,sDefaultExecutor實際上是一個串行的線程池,一個進程中所有的AsyncTask全部在這個串行的線程池中排隊執行,這個排隊執行的過程後面會再進程分析。在executeOnExecutor方法中,AsyncTask的onPreExecute方法最先執行,然後線程池開始執行。下面分析線程池的執行過程,如下所示:
  1. public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
  2. private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
  3. private static class SerialExecutor implements Executor {
  4. // 一個個執行 接口的大小可變數組的實現。數組雙端隊列沒有容量限制,不是線程安全
  5. final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
  6. // 即將執行的任務
  7. Runnable mActive;
  8. public synchronized void execute(final Runnable r) {
  9. // 插入元素到尾端
  10. mTasks.offer(new Runnable() {
  11. public void run() {
  12. try {
  13. r.run();
  14. } finally {
  15. // 做完一個任務執行下一個任務
  16. scheduleNext();
  17. }
  18. }
  19. });
  20. if (mActive == null) {
  21. scheduleNext();
  22. }
  23. }
  24. protected synchronized void scheduleNext() {
  25. // 取出第一個任務並移除
  26. if ((mActive = mTasks.poll()) != null) {
  27. // 線程池去執行任務
  28. THREAD_POOL_EXECUTOR.execute(mActive);
  29. }
  30. }
  31. }
從SerialExecutor的實現可以分析AsyncTask的排隊執行的過程。
1、首先系統會把AsyncTask的Params參數封裝爲FutureTask對象,FutureTask是一個併發類,在這裏充當了Runnable的作用。
2、接着這個FutureTask會交給SeerialExecutor的execute方法去處理,SerialExecutor的execute方法首先會把FutureTask對象插入到任務隊列mTasks中,
3、如果這個時候沒有正在活動的AsyncTask任務,那麼就會調用SerialExecutor的scheduleNext方法來執行下一個AsyncTask任務。
4、同時當一個AsyncTask任務執行完後,AsyncTask會繼續執行其他任務直到所有的任務都被執行爲止,從這一點可以看出,在默認情況下,AsyncTask是串行執行的。
AsyncTask中有兩個線程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler),其中線程池SerialExecutor用於任務的排隊,而線程池THREAD_POOL_EXECUTOR用於真正地執行任務,InternalHandler用於將執行環境從線程池切換到主線程。在AsyncTask的構造方法中有如下這麼一段代碼,由於FutureTask的run方法會調用mWork的call方法(FutureTask源碼解析:http://blog.csdn.net/xhbxhbsq/article/details/53608164),因此mWorker的call方法最終會在線程池中執行。
  1. public AsyncTask() {
  2. mWorker = new WorkerRunnable<Params, Result>() {
  3. public Result call() throws Exception {
  4. mTaskInvoked.set(true);
  5. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  6. //noinspection unchecked
  7. Result result = doInBackground(mParams);
  8. Binder.flushPendingCommands();
  9. return postResult(result);
  10. }
  11. };
  12. mFuture = new FutureTask<Result>(mWorker) {
  13. @Override
  14. protected void done() {
  15. try {
  16. postResultIfNotInvoked(get());
  17. } catch (InterruptedException e) {
  18. android.util.Log.w(LOG_TAG, e);
  19. } catch (ExecutionException e) {
  20. throw new RuntimeException("An error occurred while executing doInBackground()",
  21. e.getCause());
  22. } catch (CancellationException e) {
  23. postResultIfNotInvoked(null);
  24. }
  25. }
  26. };
  27. }
  1. private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
  2. Params[] mParams;
  3. }
在mWorker的call方法中,首先將mTaskInvoked設爲true,表示當前任務已經被調用過了,然後執行AsyncTask的doInBackground方法,接着將其返回值傳遞給postResult方法,它的實現如下所示。
  1. private Result postResult(Result result) {
  2. @SuppressWarnings("unchecked")
  3. Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
  4. new AsyncTaskResult<Result>(this, result));
  5. message.sendToTarget();
  6. return result;
  7. }
在上面的代碼中,postResult方法會通過getHandler發送一個MESSAGE_POST_RESULT的消息,這個getHandler的定義如下所示。
  1. private static InternalHandler sHandler;
  1. private static Handler getHandler() {
  2. // 單例模式,餓漢式
  3. synchronized (AsyncTask.class) {
  4. if (sHandler == null) {
  5. sHandler = new InternalHandler();
  6. }
  7. return sHandler;
  8. }
  9. }
  1. private static class InternalHandler extends Handler {
  2. public InternalHandler() {
  3. // Looper.getMainLooper()獲取主線程的Looper
  4. super(Looper.getMainLooper());
  5. }
  6. @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
  7. @Override
  8. public void handleMessage(Message msg) {
  9. AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
  10. switch (msg.what) {
  11. case MESSAGE_POST_RESULT:
  12. // There is only one result
  13. result.mTask.finish(result.mData[0]);
  14. break;
  15. case MESSAGE_POST_PROGRESS:
  16. result.mTask.onProgressUpdate(result.mData);
  17. break;
  18. }
  19. }
  20. }
可以發現,sHandler是一個靜態的Handler對象,爲了能夠將執行環境切換到主線程,這就要求sHandler這個對象必須在主線程中創建。由於靜態成員會在加載類的時候進行初始化,因此這就變相要求AsyncTask的類必須在主線程中加載,否則同一個進程中的AsyncTask都將無法正常工作。sHandler收到MESSAGE_POST_RESULT這個消息後會調用AsyncTask的finish方法,如下所示。
  1. private void finish(Result result) {
  2. // 是否設置了取消
  3. if (isCancelled()) {
  4. onCancelled(result);
  5. } else {
  6. onPostExecute(result);
  7. }
  8. // 設置狀態爲FINISHED
  9. mStatus = Status.FINISHED;
  10. }
  1. public final boolean cancel(boolean mayInterruptIfRunning) {
  2. // 設置取消
  3. mCancelled.set(true);
  4. // mayInterruptIfRunning 是否中斷true:中斷,false:不中斷
  5. return mFuture.cancel(mayInterruptIfRunning);
  6. }
AsyncTask的finish方法的邏輯比較簡單,如果AsyncTask被取消執行了,那麼就調用onCanceled方法,否則就會調用onPostExecute方法,可以看到doInBackground的返回結果會傳遞給onPostExecute方法,到這裏AsyncTask的整個工作過程就分析完畢了。















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章