深刻理解AsyncTask的工做原理

1、爲何須要工做者線程

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

    總的來講,咱們使用工做者線程是由於主線程已經有不少活要乾了,累活就得交給別人幹。AsyncTask是咱們平常中普遍使用的一種工做者線程,它的方便之處在於能夠在後臺任務執行完畢時根據返回結果相應的更新UI。下面咱們來研究一下它的工做原理。android

 

2、探索AsyncTask的工做原理

1. AsyncTask的使用簡介

    AsyncTask是對Handler與線程池的封裝。使用它的方便之處在於可以更新用戶界面,固然這裏更新用戶界面的操做仍是在主線程中完成的,可是因爲AsyncTask內部包含一個Handler,因此能夠發送消息給主線程讓它更新UI。另外,AsyncTask內還包含了一個線程池。使用線程池的主要緣由是避免沒必要要的建立及銷燬線程的開銷。設想下面這樣一個場景:有100個只須要0.001ms就能執行完畢的任務,若是建立100個線程來執行這些任務,執行完任務的線程就進行銷燬。那麼建立與銷燬進程的開銷就極可能成爲了影響性能的瓶頸。經過使用線程池,咱們能夠實現維護固定數量的線程,無論有多少任務,咱們都始終讓線程池中的線程輪番上陣,這樣就避免了沒必要要的開銷。‘編程

    在這裏簡單介紹下AsyncTask的使用方法,爲後文對它的工做原理的研究作鋪墊,關於AsyncTask的詳細介紹你們能夠參考官方文檔或是相關博文。緩存

    AsyncTask是一個抽象類,咱們在使用時須要定義一個它的派生類並重寫相關方法。AsyncTask類的聲明以下:多線程

public abstract class AsyncTask<Params, Progress, Result> 

    咱們能夠看到,AsyncTask是一個泛型類,它的三個類型參數的含義以下:併發

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

    咱們再來看一下AsyncTask類主要爲咱們提供了哪些方法:ide

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

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

2. AsyncTask的侷限性

    AsyncTask的優勢在於執行完後臺任務後能夠很方便的更新UI,然而使用它存在着諸多的限制。先拋開內存泄漏問題,使用AsyncTask主要存在如下侷限性:post

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

    接下來,咱們從源碼的角度去探究一下AsyncTask的工做原理,並嘗試着搞清楚爲何會存在以上侷限性。性能

3. AsyncTask的工做原理

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

 1 public AsyncTask() {
 2         mWorker = new WorkerRunnable<Params, Result>() {
 3             public Result call() throws Exception {
 4                 mTaskInvoked.set(true);
 5 
 6                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 7                 //noinspection unchecked
 8                 Result result = doInBackground(mParams);
 9                 Binder.flushPendingCommands();
10                 return postResult(result);
11             }
12         };
13 
14         mFuture = new FutureTask<Result>(mWorker) {
15             @Override
16             protected void done() {
17                 try {
18                     postResultIfNotInvoked(get());
19                 } catch (InterruptedException e) {
20                     android.util.Log.w(LOG_TAG, e);
21                 } catch (ExecutionException e) {
22                     throw new RuntimeException("An error occurred while executing doInBackground()",
23                             e.getCause());
24                 } catch (CancellationException e) {
25                     postResultIfNotInvoked(null);
26                 }
27             }
28         };
29     }

    在第2行到第12行,初始化了mWorker,它是一個派生自WorkRunnable類的對象。WorkRunnable是一個抽象類,它實現了Callable<Result>接口。咱們再來看一下第4行開始的call方法的定義,首先將mTaskInvoked設爲true表示當前任務已被調用過,而後在第6行設置線程的優先級。在第8行咱們能夠看到,調用了AsyncTask對象的doInBackground方法開始執行咱們所定義的後臺任務,並獲取返回結果存入result中。最後將任務返回結果傳遞給postResult方法。關於postResult方法咱們會在下文進行分析。由此咱們能夠知道,實際上AsyncTask的成員mWorker包含了AyncTask最終要執行的任務(即mWorker的call方法)。

    接下來讓咱們看看對mFuture的初始化。咱們能夠看到mFuture是一個FutureTask的直接子類(匿名內部類)的對象,在FutureTask的構造方法中咱們傳入了mWorker做爲參數。咱們使用的是FutureTask的這個構造方法:

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

   也就是說,mFuture是一個封裝了咱們的後臺任務的FutureTask對象,FutureTask類實現了FutureRunnable接口,經過這個接口能夠方便的取消後臺任務以及獲取後臺任務的執行結果,具體介紹請看這裏:Java併發編程:Callable、Future和FutureTask

    從上面的分析咱們知道了,當mWorker中定義的call方法被執行時,doInBackground就會開始執行,咱們定義的後臺任務也就真正開始了。那麼這個call方法何時會被調用呢?咱們能夠看到通過層層封裝,其實是mFuture對象封裝了call方法,當mFuture對象被提交到AsyncTask包含的線程池執行時,call方法就會被調用,咱們定義的後臺任務也就開始執行了。下面咱們來看一下mFuture是何時被提交到線程池執行的。

   

    首先來看一下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方法,使用的是串行方式來執行後臺任務。關於線程池更加詳細的介紹與分析請見:深刻理解Java之線程池

    咱們再來看一下executeOnExecutor方法都作了些什麼:

 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 
17         onPreExecute();
18 
19         mWorker.mParams = params;
20         exec.execute(mFuture);
21 
22         return this;
23     }

     從以上代碼的第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類的源碼:

 1 private static class SerialExecutor implements Executor {
 2         final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
 3         Runnable mActive;
 4 
 5         public synchronized void execute(final Runnable r) {
 6             mTasks.offer(new Runnable() {
 7                 public void run() {
 8                     try {
 9                         r.run();
10                     } finally {
11                         scheduleNext();
12                     }
13                 }
14             });
15             if (mActive == null) {
16                 scheduleNext();
17             }
18         }
19 
20         protected synchronized void scheduleNext() {
21             if ((mActive = mTasks.poll()) != null) {
22                 THREAD_POOL_EXECUTOR.execute(mActive);
23             }
24         }
25     }

     咱們來看一下execute方法的實現。mTasks表明了SerialExecutor這個串行線程池的任務緩存隊列,在第6行,咱們用offer方法向任務緩存隊列中添加一個任務,任務的內容如第7行到第13行的run方法定義所示。咱們能夠看到,run方法中:第9行調用了mFuture(第5行的參數r就是咱們傳入的mFuture)的run方法,而mFuture的run方法內部會調用mWorker的call方法,而後就會調用doInBackground方法,咱們的後臺任務也就開始執行了。那麼咱們提交到任務緩存隊列中的任務何時會被執行呢?咱們接着往下看。

     首先咱們看到第三行定義了一個Runnable變量mActive,它表明了當前正在執行的AsyncTask對象。第15行判斷mActive是否爲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的源碼,咱們能夠獲取它的各項參數以下:

 1 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
 2 private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
 3 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
 4 private static final int KEEP_ALIVE = 1;
 5 
 6 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
 7     private final AtomicInteger mCount = new AtomicInteger(1);
 8 
 9     public Thread newThread(Runnable r) {
10         return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
11     }
12 };
13 
14 private static final BlockingQueue<Runnable> sPoolWorkQueue =
15             new LinkedBlockingQueue<Runnable>(128);

     由以上代碼咱們能夠知道:

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

     

    如今,咱們已經瞭解到了從咱們調用AsyncTask對象的execute方法開始知道後臺任務執行完都發生了什麼。如今讓咱們回過頭來看一看以前提到的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的相關代碼:

 1 private static final InternalHandler sHandler = new InternalHandler();
 2 
 3 private static class InternalHandler extends Handler {
 4         public InternalHandler() {
 5             super(Looper.getMainLooper());
 6         }
 7 
 8         @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
 9         @Override
10         public void handleMessage(Message msg) {
11             AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
12             switch (msg.what) {
13                 case MESSAGE_POST_RESULT:
14                     // There is only one result
15                     result.mTask.finish(result.mData[0]);
16                     break;
17                 case MESSAGE_POST_PROGRESS:
18                     result.mTask.onProgressUpdate(result.mData);
19                     break;
20             }
21         }
22

     從以上代碼中咱們能夠看到,sHandler是一個靜態的Handler對象。咱們知道建立Handler對象時須要當前線程的Looper,因此咱們爲了之後可以經過sHandler將執行環境從後臺線程切換到主線程(即在主線程中執行handleMessage方法),咱們必須使用主線程的Looper,所以必須在主線程中建立sHandler。這也就解釋了爲何必須在主線程中加載AsyncTask類,是爲了完成sHandler這個靜態成員的初始化工做。

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

1 private void finish(Result result) {
2         if (isCancelled()) {
3             onCancelled(result);
4         } else {
5             onPostExecute(result);
6         }
7         mStatus = Status.FINISHED;
8 }

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

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

 

3、參考資料

1. Android SDK Sources

2. 《Android開發藝術探索》

相關文章
相關標籤/搜索