Android 非同步任務知識梳理(1) AsyncTask 原始碼解析

澤毛發表於2017-12-21

一、概述

這篇文章中,讓我們從原始碼的角度看一下AsyncTask的原理,最後會根據原理總結一下使用AsyncTask中需要注意的點。

二、原始碼解析

AsyncTask中,有一個執行緒池 THREAD_POOL_EXECUTOR 和與這個執行緒池相關聯的 Executor,它負責執行我們的任務Runnable

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

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

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

//這是執行Runnable的地方,呼叫execute會執行它,如果當前已經有一個任務在執行,那麼就是把它放到佇列當中。
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();                
                }            
             }        
        });
        //如果為null,那麼立刻執行它;
        //如果不為null,說明當前佇列中還有任務,那麼等Runnable執行完之後,再由上面的scheduleNext()執行它。        
        if (mActive == null) {               
            scheduleNext();        
        }    
   }    

   protected synchronized void scheduleNext() {
        //第一次進來呼叫了offer方法,因此會走進去,執行上面Runnable的run()方法。        
        if ((mActive = mTasks.poll()) != null) {            
           THREAD_POOL_EXECUTOR.execute(mActive);        
        }    
    }
}
複製程式碼

從上面可以看出,每次呼叫sDefaultExecutor.execute時就是執行一個任務,這個任務會被加入到ArrayDeque中序列執行,我們看一下當我們呼叫AsyncTaskexecute方法時,任務是如何被建立並加入到這個佇列當中的:

//這個靜態方法,不會回撥 loadInBackground 等方法。
public static void execute(Runnable runnable) {    
    sDefaultExecutor.execute(runnable);
}

//這是我們最常使用的方法,引數可以由呼叫者任意指定。
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;
}
複製程式碼

在呼叫了executeOnExecutor之後,我們把傳入的引數賦值給了mWorker,並把mFuture傳入給Executor去執行,而從下面我們可以看到mFuture的建構函式中傳入的引數正是mWorker,這兩個東西其實才是AsyncTask的核心,它們是在AsyncTask的建構函式中被初始化的:

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;

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 {
            //要求mWorker的call方法沒有被呼叫,否則什麼也不做。            
            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);        
       }    
   }
};
複製程式碼

先看一下WorkerRunnable,實現了Callable<V>介面,增加了一個不定引數的成員變數用來傳給 doInBackground,這個不定引數就是我們在execute時傳入的,呼叫call時會執行器內部的函式,而call 時會呼叫doInBackground方法,這個方法執行完之後,呼叫postResult,注意到calldone都是在子執行緒當中執行的:

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

我們主要看一下FutureTask,還記得最開始我們的Executor最終執行的是傳入的Runnablerun方法,因此我們直接看一下它的run方法中做了什麼:

public interface Runnable {
   public void run();
}

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

public interface RunnableFuture<V> extends Runnable, Future<V> { 
    void run();
}

//我們只要關注run方法
public class FutureTask<V> implements RunnableFuture<V> {
    public void run() {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            result = c.call(); //mWorker.call()
            ran = true;
        }
        if (ran) {
            set(result);
        }
    }

    protected void set(V v) {
        finishCompletion();
    }

    private void finishCompletion() {
        done(); //mFuture.done()
        callable = null;
    }
}
複製程式碼

那麼結論就清晰了,整個的執行過程是下面這樣的:

  • 首先呼叫excuter.execute(mFuture),把mFuture加入到佇列當中
  • mFuture得到執行時,會回撥mFuturerun方法
  • mFuture#run是執行在子執行緒當中的,它在它所在的執行緒中執行的mWorker#call方法
  • mWorkder#call呼叫了doInBackground,使用者通過實現這個抽象方法來進行耗時操作
  • mFuture#call執行完後呼叫mFuture#done

在上面的過程當中,有兩個地方都呼叫了postResult,一個是mWorkder#call的最後,另一個是mFuture#done,但是區別在於後者在呼叫之前會判斷mTaskInvokedfalse時才會去執行,也就是在mWorkder#call沒有執行的情況下,這是為了避免call方法沒有被執行時(提前取消了任務),postResult沒有被執行,導致使用者收不到任何回撥。 postResult會通過InternalHandler把當前的AsyncTaskFutureTask的結果回撥到主執行緒當中,之後呼叫finish方法,它會根據呼叫者是否執行過cancel方法來回撥不同的函式:

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

呼叫者通過重寫onProgressUpdate就可以得到當前的最新進度,AsyncTask最終會把結果回撥到主執行緒當中:

protected final void publishProgress(Progress... values) {    
    if (!isCancelled()) {          
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget();    
    }
}

private static class InternalHandler extends Handler {
    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;
        }
    }
}
複製程式碼

三、結論

經過上面的原始碼分析,我們有這麼幾個結論:

  • 靜態方法execute(Runnable runnable)AsyncTask其實沒什麼太大關係,只是用到了裡面一個靜態的執行緒池而已,AsyncTask內部的狀態都和它無關。
  • 當我們對同一個AsyncTask例項呼叫execute(..)時,如果此時已經有任務正在執行,或者已經執行過任務,那麼會丟擲異常。
  • onPreExecute()執行時,任務還沒有被加入到佇列當中。
  • doInBackground是在子執行緒當中執行的,我們呼叫cancel後,並不一定會立即得到onCancel的回撥,這是因為cancel只保證了mFuturedone方法被執行,有這麼幾種情況:
  • 如果mWorkercall函式沒有執行,那麼這時mFuturedone方法被呼叫時,postResultIfNotInvoked是滿足條件的,呼叫者可以立即得到onCancel回撥。
  • 如果mWorkercall呼叫了,雖然mFuturedone執行了,但是它不滿足條件(!mTaskInvoked.get()),那麼會一直等到doInBackground執行完所有的操作才通過return postResult返回,所以我們需要在doInBackground中通過isCancel()來判斷是否需要提早返回,避免無用的等待。
  • postResult完畢之後, onCancelonPostExecute只會呼叫一個。
  • 任務是和AsyncTask例項繫結的,而如果AsyncTask又和Activity繫結了,如果在執行過程中這個 AsyncTask例項被銷燬了(例如Activity被重新建立),那麼呼叫者在新的Activity中是無法收到任何回撥的,因為那已經是另外一個AsyncTask了。
  • 關於AsyncTask最大的問題其實是記憶體洩漏,因為把它作為Activity的內部類時,會預設持有Activity的引用,那麼這時候如果有任務正在執行,那麼 Activity 是無法被銷燬的,這其實和Handler的洩漏類似,可以有以下這麼一些用法:
  • 不讓它持有Activity的引用或者持有弱引用。
  • 保證在Activity在需要銷燬時cancel了所有的任務。
  • 使AsyncTask的執行與Activity的生命週期無關,可以考慮通過建立一個沒有UIfragment來實現,因為在Activity重啟時,會自動儲存有之前add進去的Fragment的例項,Fragment持有的還是先前的 AsyncTask
  • 使用LoaderManager,把管理的活交給系統來執行。

相關文章