Architecture(1)AsyncTask原始碼分析

wustor發表於2018-01-08

概述

從事Android開發以來,研究過很多程式設計方面的東西,有程式設計基礎:網路程式設計,資料結構跟演算法,Java知識點:Java基礎,JVM,併發程式設計,Android知識點:Android基礎,Binder機制,效能優化等。這些都是一些具體的知識點,很零散,總想著把這些知識點串起來,不然很容易忘記,所以就打算開始研究一些框架,之前也零零散散看過一些框架,但沒有總結,自己也封裝過一些框架,所以打算系統的學習一些關於架構方面的知識,不再僅僅地從原理上知道為什麼要這麼寫,想從細節上來分析一下各個框架,打算重點研究一下AsyncTaskVolleyPicassoLitePal。這些是我剛剛從事Android開發的時候接觸到的幾個框架,雖然後來也用過OKHttp,Rxjava,Retrofit,Glide等,但是最終還是對這幾個框架進行分析分析,畢竟原理都是一樣的。

正文

註釋

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.
複製程式碼

AsyncTask讓對UI執行緒的操作變得簡單。這個類可以讓你在不需要控制多執行緒或者Handlers的情況下,可以在子執行緒中進行耗時操作並且將操作結果切換到UI執行緒

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}.
複製程式碼

AsyncTask被設計成為Thread跟Handler的幫助類並沒有產恆一個執行緒框架。AsyncTasks在理想情況是用於較短的耗時操作(最多幾秒鐘)。如果你需要讓執行緒持續執行很長一段時間,那麼強烈推薦你使用Java的concurrent包下類似Executor,ThreadPoolExecutor,FutureTask這些API。

An asynchronous task is defined by a computation that runs on a background thread and
whose result is published on the UI thread. An asynchronous task is defined by 3 generic
types, called Params, Progress and Result,and 4 steps, called onPreExecute, doInBackground,onProgressUpdate and onPostExecute
複製程式碼

非同步的任務是這麼被定義的:在子執行緒中進行計算,然後把計算結果顯示在UI執行緒中。非同步任務是通過Params, Progress and Result三個泛型引數,onPreExecute, doInBackground,onProgressUpdate and onPostExecute四個步驟來實現的

AsyncTask must be subclassed to be used. The subclass will override at least
 one method ({@link #doInBackground}), and most often will override a second one ({@link #onPostExecute}.)
複製程式碼

非同步任務必須在在實現類中進行使用,子類至少需要複寫doInBackground方法,通常也需要複寫onPostExecute方法

示例用法

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) 
               break;
         }
         return totalSize;
     }
 
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
 
     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }
//進行呼叫
new DownloadFilesTask().execute(url1, url2, url3);
複製程式碼

泛型引數

Params, the type of the parameters sent to the task uponexecution.
複製程式碼

引數,傳遞給即將執行任務的引數

Progress ,the type of the progress units published during the background computation.
複製程式碼

進度,在子執行緒中進行算回傳給UI執行緒的進度單元

Result, the type of the result of the background computation.
複製程式碼

執行結果,後臺計算的結果

執行步驟

onPreExecute(), invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
複製程式碼

onPreExecute,任務執行前在主執行緒中呼叫。這個步驟通常被用來對非同步任務進行設定,例如可以在使用者互動介面展示一個進度條。

doInBackground, invoked on the background thread immediately after {@ #onPreExecute()} finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must
 be returned by this step and will be passed back to the last step. This step can also use {@link #publishProgress} to publish one or more units of progress. These values are published on the UI thread, in the in the onProgressUpdate step。
複製程式碼

doInBackground,在子執行緒中被調,當onPreExecute執行完成後會立即被觸發。這個方法被用來執行一些耗時的計算。非同步任務執行需要的引數通過此方法進行傳遞。後臺任務執行的結果會通過此步驟進行返回,在這個方法中也可以進行呼叫publishProgress來實時顯示任務執行的百分比。publishProgress是在主執行緒中進行呼叫。

onProgressUpdate, invoked on the UI thread after a call to {@link #publishProgress}. The timing of the execution is undefined. This method is used to display any form of progress in the user
interface while the background computation is still executing. For instance,it can be used to animate a progress bar or show logs in a text field
複製程式碼

onProgressUpdate,在UI執行緒中被呼叫,當在doInBackground中進行呼叫publishProgress的時候會走此方法,這個方法的執行時間是不確定的。當後臺任務仍然在執行的時候,此方法被用來在使用者互動介面展示各種形式的進度條。例如,他可以被用來展示一個進度條或者在文字框裡面顯示log。

onPostExecute, invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter
複製程式碼

onPostExecute,在UI執行緒中呼叫,用來展示非同步任務的執行結果,非同步任務的後臺執行結果通過一個引數傳遞給這個方法。

Cancelling a task

A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking this method will cause subsequent calls to {@link #isCancelled()} to return true.After invoking this method, {@link #onCancelled(Object)}, instead of onPostExecute will be invoked after {@link #doInBackground(Object[])}returns. To ensure that a task is cancelled as quickly as possible, you should always check the return value of {@link #isCancelled()} periodically from
{@link #doInBackground(Object[])}, if possible (inside a loop for instance.)
複製程式碼

非同步任務可以在任何時間通過cancel來被取消。呼叫此方法回使得isCancelled返回true.當呼叫完此方法之後,當非同步任務執行之後就不會再呼叫onPostExecute方法而是呼叫onCancelled方法。為了保證非同步任務儘可能快地被終止,如果可能的話,需要週期性的每次在doInBackground中進行檢車isCanclled的返回值。

Threading rules

The AsyncTask class must be loaded on the UI thread. This is done automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
The task instance must be created on the UI thread.
Execute must be invoked on the UI thread.
Do not call onPreExecute(),  onPostExecute,doInBackground, onProgressUpdate manually.
The task can be executed only once (an exception will be thrown if a second execution is attempted.)
複製程式碼

非同步任務必須在主執行緒中進行呼叫(3.0之後,系統會幫我自動處理執行緒切換問題)

非同步任務例項必須在主執行緒中進行建立

Execute方法必須在主執行緒中被呼叫

不要在主執行緒中手動呼叫onPreExecute(), onPostExecute,doInBackground, onProgressUpdate方法

非同步任務只能被執行一次(如果被呼叫第二次將會丟擲exception)

Memory observability

 AsyncTask guarantees that all callback calls are synchronized in such a way that the following
 operations are safe without explicit synchronizations.
 Set member fields in the constructor or onPreExecute, and refer to them in link doInBackground.
 Set member fields in doInBackground, and refer to them in onProgressUpdate and link          onPostExecute.

複製程式碼

非同步任務保證所有的回撥都是同步的,這樣一來下面的操作在不加鎖的情況下都是執行緒安全的

在構造方法中跟onPreExecute設定成員變數,他們將會傳遞到doInBackground中

在doInBackground中設定引數,他們將會傳遞給onProgressUpdate跟onPostExecute

Order of execution

When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with HONEYCOMB, tasks are executed on a single thread to avoid common application errors caused by parallel execution.If you truly want parallel execution, you can invoke executeOnExecutor(Executor, Object[]) with THREAD_POOL_EXECUTOR
複製程式碼

當剛剛引進AsyncTask的時候,他是在單執行緒中序列執行的。從1.6開始,加入了執行緒池允許多工並行執行。在3.0之後,非同步任務又被放入了單執行緒中來避免併發執行中造成的應用異常。如果你確實想併發執行,那麼通過呼叫executeOnExecutor(Executor, Object[])方法,通過執行緒池來執行。

成員變數

	//CPU數量
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
	//核心執行緒數
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
	//最大執行緒數
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
	//執行緒沒有任務執行時最多保持30s會終止
    private static final int KEEP_ALIVE_SECONDS = 30;
	//執行緒是快取佇列,預設容量為128
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
	
	//執行緒池工廠
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
     /內建執行緒計數器
    private final AtomicInteger mCount = new AtomicInteger(1);
      public Thread newThread(Runnable r) {
      //建立執行緒後計數器加1
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
	//序列執行緒池
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
	//並行執行緒池
    public static final Executor THREAD_POOL_EXECUTOR;
	//預設的執行緒池,採用的是序列的執行緒池
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
	//任務執行完傳送的訊息標誌
    private static final int MESSAGE_POST_RESULT = 0x1;
	//任務執行過程中更新任務進度的訊息標誌
    private static final int MESSAGE_POST_PROGRESS = 0x2;

	//內部持有的Handler,用來切換執行緒
    private static InternalHandler sHandler;
	//實現了Callable介面的worker,可以拿到返回值的Runnable
    private final WorkerRunnable<Params, Result> mWorker;
	//FutureTask,實現了RunnableFuture介面
    private final FutureTask<Result> mFuture;
	//當前任務的狀態,採用volatile關鍵字修飾,保持記憶體可見性,預設為待執行狀態
    private volatile Status mStatus = Status.PENDING;
	//任務是否取消的標誌,用AtomicBoolean保持可見性
    private final AtomicBoolean mCancelled = new AtomicBoolean();
	//任務是否開始的標誌,用AtomicBoolean保持可見性
    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
複製程式碼

這些成員變數都比較好理解,不過需要注意的是在成員變數中有三個執行緒池,一個是序列的執行緒池,一個是並行的執行緒池,還有一個是執行緒池的引用。序列執行緒池已經初始化,但是並行的執行緒池在變數的宣告中並沒有初始化,他的初始化是在一個靜態程式碼塊中

 static {
   		//建立一個執行緒池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
   		//將執行緒池的引用傳遞給並行的執行緒池
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
複製程式碼

構造方法

 public AsyncTask() {
   		//建立一個Callable物件,將AsyncTask的實現類中的泛型Params,Result傳遞進去
        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;
            }
        };
    //建立一個FutureTask,將worker傳遞進去
        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);
                }
            }
        };
    }

複製程式碼

Execute相關

execute Runnable

@MainThread
public static void execute(Runnable runnable) {
    sDefaultExecutor.execute(runnable);
}

複製程式碼

僅僅呼叫預設執行緒池,去執行一個Runnable

executeOnExecutor

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    if (mStatus != Status.PENDING) {
    //判斷AsyncTask的狀態
        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)");
        }
    }
	//更改AsyncTask的狀態
    mStatus = Status.RUNNING;
    //呼叫執行前方法
    onPreExecute();
    //引數進行賦值
    mWorker.mParams = params;
    //將建立好的Future放入執行緒池
    exec.execute(mFuture);
    return this;
}
複製程式碼

execute Params

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
  //間接呼叫executeOnExecutor方法
    return executeOnExecutor(sDefaultExecutor, params);
}
複製程式碼

序列執行緒池

private static class SerialExecutor implements Executor {
	//採用ArrayDeque來存放即將要執行的任務
    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();
                }
            }
        });
      //如果mActive為空,從佇列中取任務執行
        if (mActive == null) {
            scheduleNext();
        }
    }
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
          //如果佇列不為空,則將任務丟進執行緒池中進行執行
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}
複製程式碼

從SerialExecutor的原始碼進行分析可以知道,實際上他並不是真正意義上的執行緒池,只是用來存放加進來的任務而已,最終起作用的還是並行的執行緒池,此時的並行的執行緒池的執行的每一個任務都是在上一個任務執行完畢後才進行執行的,所以也就是所謂的序列執行。

AsyncTask表面上看起來有三種啟動方式,實際上只有兩種,一種是把AsyncTask當成執行緒池,直接執行一個Runnable,這個有些大材小用,還有一種就是通過傳遞引數,也是我們使用地最多的那種。如果我們沒有傳入執行緒池,那麼AsyncTask採用自己預設的序列執行緒池,如果自定義了執行緒池,那麼就會採用我們自己的執行緒池,當然我們也可以不用定義,因為在成員變數的分析過程中,已經有一個定義好的執行緒池THREAD_POOL_EXECUTOR,作為一個靜態變數,是可以直接使用的。

流程梳理

onPreExecute

此方法最先在execute的時候最先呼叫,然後不管是序列還是並行的執行緒池,都開始執行任務

doInBackground

此方法在構造方法中執行下面程式碼的時候會被呼叫

result = doInBackground(mParams);
複製程式碼

onProgressUpdated

當doInBackground中呼叫publishProgress的時候,會通過Handler傳送訊息

@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}
複製程式碼

在Handler中處理訊息

private static class InternalHandler extends Handler {
    public InternalHandler() {
      //在主執行緒中建立handler
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_PROGRESS:
               //呼叫onProgressUpdated方法
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}
複製程式碼

onPostExecute

當Callable中的任務執行完拿到返回結果的時候

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        mTaskInvoked.set(true);
        Result result = null;
        try {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //拿到返回結果
            result = doInBackground(mParams);
            Binder.flushPendingCommands();
        } catch (Throwable tr) {
            mCancelled.set(true);
            throw tr;
        } finally {
          //通知主執行緒任務執行完畢
            postResult(result);
        }
        return result;
    }
};
複製程式碼

通過postResult,也就是通過Handler切換執行緒

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

複製程式碼

Handler處理訊息

@Override
public void handleMessage(Message msg) {
    AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
    switch (msg.what) {
        case MESSAGE_POST_RESULT:
            // There is only one result
            //呼叫finish方法
            result.mTask.finish(result.mData[0]);
            break;
        case MESSAGE_POST_PROGRESS:
            result.mTask.onProgressUpdate(result.mData);
            break;
    }
}
複製程式碼

finish方法

private void finish(Result result) {
    if (isCancelled()) {
    //如果任務被取消,呼叫onCancelled方法
        onCancelled(result);
    } else {
    //如果任務沒有被取消,就呼叫onPostExecute方法
        onPostExecute(result);
    }
    //更新AsyncTask任務的狀態
    mStatus = Status.FINISHED;
}
複製程式碼

cancel方法

public final boolean cancel(boolean mayInterruptIfRunning) {
  //更改AsyncTask的狀態
    mCancelled.set(true);
    return mFuture.cancel(mayInterruptIfRunning);
}
複製程式碼

cancel可以傳遞一個布林值mayInterruptIfRunning,其實很好理解,就是如果任務正在進行是否中斷,然後最後呼叫的是mFuture的cancel方法,很好理解

總結

之前一直是在網上看別人的部落格介紹AsyncTask,這次從原始碼的註釋開始,系統的分析了一下他的原始碼,其實還是很簡單的,關於一些注意事項跟各版本之間的差異原始碼的註釋中已經很詳細了,網上的分析也都是基於原始碼的註釋而來,整體來講,AsyncTask算是一個簡單的網路請求框架,內部採用執行緒池,Handler,以及Callable實現,比較輕量級,適用於耗時不太長的非同步操作,沒有設計到快取,定向取消請求等複雜的操作。使用起來比較簡單,原始碼也很容易讀懂,不過在使用的時候需要注意幾點

記憶體洩漏

由於AsyncTask是Activity的內部類,所以會持有外部的一個引用,如果Activity已經退出,但是AsyncTask還沒有執行完畢,那麼Activity就無法釋放導致記憶體洩漏。對於這個問題我們可以把AsyncTask定義為靜態內部類並且採用弱引用,同時還可以在Activity的destroy方法中呼叫cancel方法來終止AsyncTask。

版本差異

在1.6~3.0的版本中,AsyncTask預設的執行緒池是並行執行的,1.6之前以及3.0之後的版本都是序列執行的,但是可以通過setDefaultExecutor傳入自定義的執行緒池,依然可以是的執行緒併發執行。

相關文章