Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android編程入門 >> 深入理解AsyncTask的工作原理

深入理解AsyncTask的工作原理

編輯:Android編程入門

一、為什麼需要工作者線程

    我們知道,Android應用的主線程(UI 線程)肩負著繪制用戶界面和及時響應用戶操作的重任,為了避免“用戶點擊按鈕後沒反應”這樣的糟糕用戶體驗,我們就要確保主線程時刻保持著較高的響應性。為了做到這一點,我們就要把耗時的任務移出主線程,那麼耗時的任務交給誰來完成呢?答案就是工作者線程。Android開發中我們通常讓主線程負責前台用戶界面的繪制以及響應用戶的操作,讓工作者線程在後台執行一些比較耗時的任務。Android中的工作者線程主要有AsyncTask、IntentService、HandlerThread,它們本質上都是對線程或線程池的封裝。關於線程和線程池相關知識的介紹,請參考這兩篇博文:Java核心技術點之多線程    深入理解Java之線程池

    總的來說,我們使用工作者線程是因為主線程已經有很多活要干了,累活就得交給別人干。AsyncTask是我們日常中廣泛使用的一種工作者線程,它的方便之處在於可以在後台任務執行完畢時根據返回結果相應的更新UI。下面我們來研究一下它的工作原理。

 

二、探索AsyncTask的工作原理

1. AsyncTask的使用簡介

    AsyncTask是對Handler與線程池的封裝。使用它的方便之處在於能夠更新用戶界面,當然這裡更新用戶界面的操作還是在主線程中完成的,但是由於AsyncTask內部包含一個Handler,所以可以發送消息給主線程讓它更新UI。另外,AsyncTask內還包含了一個線程池。使用線程池的主要原因是避免不必要的創建及銷毀線程的開銷。設想下面這樣一個場景:有100個只需要0.1ms就能執行完畢的任務,如果創建100個線程來執行這些任務,執行完任務的線程就進行銷毀。那麼創建與銷毀進程的開銷就很可能成為了影響性能的瓶頸。通過使用線程池,我們可以實現維護固定數量的線程,不管有多少任務,我們都始終讓線程池中的線程輪番上陣,這樣就避免了不必要的開銷。‘

    在這裡簡單介紹下AsyncTask的使用方法,為後文對它的工作原理的研究做鋪墊,關於AsyncTask的詳細介紹大家可以參考官方文檔或是相關博文。

    AsyncTask是一個抽象類,我們在使用時需要定義一個它的派生類並重寫相關方法。AsyncTask類的聲明如下:

public abstract class AsyncTask<Params, Progress, Result> 

    我們可以看到,AsyncTask是一個泛型類,它的三個類型參數的含義如下:

  • Params:doInBackground方法的參數類型;
  • Progress:AsyncTask所執行的後台任務的進度類型;
  • Result:後台任務的返回結果類型。

    我們再來看一下AsyncTask類主要為我們提供了哪些方法:

onPreExecute() //此方法會在後台任務執行前被調用,用於進行一些准備工作
doInBackground(Params... params) //此方法中定義要執行的後台任務,在這個方法中可以調用publishProgress來更新任務進度(publishProgress內部會調用onProgressUpdate方法)
onProgressUpdate(Progress... vaues) //由publishProgress內部調用,表示任務進度更新
onPostExecute(Result result) //後台任務執行完畢後,此方法會被調用,參數即為後台任務的返回結果
onCancelled() //此方法會在後台任務被取消時被調用

    以上方法中,除了doInBackground方法由AsyncTask內部線程池執行外,其余方法均在主線程中執行。

2. AsyncTask的局限性

    AsyncTask的有點在於執行完後台任務後可以很方便的更新UI,然而使用它存在著諸多的限制。先拋開內存洩漏問題,使用AsyncTask主要存在以下局限性:

  • 在Android 4.1版本之前,AsyncTask類必須在主線程中加載,這意味著對AsyncTask類的第一次訪問必須發生在主線程中;在Android 4.1以及以上版本則不存在這一限制,因為ActivityThread(代表了主線程)的main方法中會自動加載AsyncTask
  • AsyncTask對象必須在主線程中創建
  • AsyncTask對象的execute方法必須在主線程中調用
  • 一個AsyncTask對象只能調用一次execute方法

    接下來,我們從源碼的角度去探究一下AsyncTask的工作原理,並嘗試著搞清楚為什麼會存在以上局限性。

3. AsyncTask的工作原理

   首先,讓我們來看一下AsyncTask類的構造器都做了些什麼:

 public AsyncTask() {
         mWorker = new WorkerRunnable<Params, Result>() {
             public Result call() throws Exception {
                 mTaskInvoked.set(true);
 
                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                 //noinspection unchecked
                 Result result = doInBackground(mParams);
                 Binder.flushPendingCommands();
                 return postResult(result);
             }
         };
 
         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);
                 }
             }
         };
     }

 

    在第2行到第12行,初始化了mWorker,它是一個派生自WorkRunnable類的對象。WorkRunnable是一個抽象類,它實現了Callable<Result>接口。我們再來看一下第4行開始的call方法的定義,首先將mTaskInvoked設為true表示當前任務已被調用過,然後在第6行設置線程的優先級。在第8行我們可以看到,調用了AsyncTask對象的doInBackground方法開始執行我們所定義的後台任務,並獲取返回結果存入result中。最後將任務返回結果傳遞給postResult方法。關於postResult方法我們會在下文進行分析。

    接下來讓我們看看對mFuture的初始化。我們可以看到在構造mFuture對象時我們傳入了mWorker作為參數。也就是說,mFuture是一個封裝了我們的後台任務的FutureTask對象。

   

    我們再來看一下execute方法的源碼:

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

     我們可以看到它接收的參數是Params類型的參數,這個參數會一路傳遞到doInBackground方法中。execute方法僅僅是調用了executeOnExecutor方法,並將executeOnExecutor方法的返回值作為自己的返回值。我們注意到,傳入了sDefaultExecutor作為executeOnExecutor方法的參數,那麼sDefaultExecutor是什麼呢?簡單的說,它是AsyncTask的默認執行器。AsyncTask可以以串行(一個接一個的執行)或並行(一並執行)兩種方式來執行後台任務,在Android3.0及以後的版本中,默認的執行方式是串行。這個sDefaultExecutor就代表了默認的串行任務執行器。也就是說我們平常在AsyncTask對象上調用execute方法,使用的是串行方式來執行後台任務。

    我們再來看一下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;
     }

     從以上代碼的第4行到第12行我們可以知道,當AsyncTask對象的當前狀態為RUNNING或FINISHED時,調用execute方法會拋出異常,這意味著不能對正在執行任務的AsyncTask對象或是已經執行完任務的AsyncTask對象調用execute方法,這也就解釋了我們上面提到的局限中的最後一條。

    接著我們看到第17行存在一個對onPreExecute方法的調用,這表示了在執行後台任務前確實會調用onPreExecute方法。

    在第19行,把我們傳入的execute方法的params參數賦值給了mWorker的mParams成員變量;而後在第20行調用了exec的execute方法,並傳入了mFuture作為參數。exec就是我們傳進來的sDefaultExecutor。那麼接下來我們看看sDefaultExecutor就是是什麼。在AsyncTask類的源碼中,我們可以看到這句:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

 

     sDefaultExecutor被賦值為SERIAL_EXECUTOR,那麼我們來看一下SERIAL_EXECUTOR:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

 

    現在,我們知道了實際上sDefaultExecutor是一個SerialExecutor對象,我們來看一下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);
             }
         }
     }

 

     我們來看一下execute方法的實現。mTasks代表了SerialExecutor這個串行線程池的任務緩存隊列,在第6行,我們用offer方法向任務緩存隊列中添加一個任務,任務的內容如第7行到第13行的run方法定義所示。我們可以看到,第9行調用了mFuture的run方法,而mFuture的run方法內部會調用mWorker的call方法。正如我們看到的那樣,mWorker的call方法內調用了doInBackground方法。也就是說,最終在線程池中執行的內容是mWorker的call方法。

     第15行判斷mActive(當前正在執行的AsyncTask對象)是否為null,若為null,就調用scheduleNext方法。如第20行到24行所示,在scheduleNext方法中,若緩存隊列非空,則調用THREAD_POOL_EXECUTOR.execute方法執行從緩存隊列中取出的任務。

     通過以上的分析,我們可以知道SerialExecutor所完成的工作主要是把任務加到任務緩存隊列中,而真正執行任務的是THREAD_POOL_EXECUTOR。我們來看下THREAD_POOL_EXECUTOR是什麼:

 public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

 

    從上面的代碼我們可以知道,它是一個線程池對象。根據AsyncTask的源碼,我們可以獲取它的各項參數如下:

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

     由以上代碼我們可以知道:

  •  corePoolSize為CPU數加一;
  • maximumPoolSize為CPU數的二倍加一;
  • 存活時間為1秒;
  • 任務緩存隊列為LinkedBlockingQueue。

     現在,我們回過頭來看一看之前提到的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方法獲取AsyncTask對象內部包含的sHandler,然後通過它發送了一個MESSAGE_POST_RESULT消息。我們來看看sHandler的相關代碼:

 private static final InternalHandler sHandler = new InternalHandler();
 
 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;
             }
         }
 } 

     從以上代碼中我們可以看到,sHandler是一個靜態的Handler對象。我們知道創建Handler對象時需要當前線程的Looper,所以我們為了以後能夠通過sHandler將執行環境從後台線程切換到主線程,必須在主線程中創建sHandler。這也就解釋了為什麼必須在主線程中加載AsyncTask類,是為了完成sHandler這個靜態成員的初始化工作。

     在以上代碼第10行開始的handleMessage方法中,我們可以看到,當sHandler收到MESSAGE_POST_RESULT方法後,會調用finish方法,finish方法的源碼如下:

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

 

    在第2行,會通過調用isCancelled方法判斷AsyncTask任務是否被取消,若取消了則調用onCancelled方法,否則調用onPostExecute方法;在第7行,把mStatus設為FINISHED,表示當前AsyncTask對象已經執行完畢。

 

    經過了以上的分析,我們大概了解了AsyncTask的內部運行邏輯,知道了它默認使用串行方式執行任務。那麼如何讓它以並行的方式執行任務呢? 閱讀了以上的代碼後,我們不難得到結論,只需調用executeOnExecutor方法,並傳入THREAD_POOL_EXECUTOR作為其線程池即可。

 

三、參考資料

1. Android SDK Sources

2. 《Android開發藝術探索》

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved