從原始碼角度一步步分析AsyncTask的用法與原理

LeBron_Six發表於2016-10-08

AsyncTask 是Android特有的一個輕量級非同步抽象類,在類中通過doInBackground()在子執行緒執行耗時操作,執行完畢在主執行緒呼叫onPostExecute()

前言

眾所周知,Android檢視的繪製、監聽、事件等都UI執行緒(主執行緒,Main Thread)執行,如果執行訪問網路請求、資料庫等耗時操作,可能會阻塞主執行緒,若阻塞時間超過5秒,可能會引起系統的ANR異常(Application Not Responding)。所以耗時操作需要放在子執行緒(也稱為Worker Thread)執行,這樣就避免了主執行緒的阻塞,然而線上程是不能有更新UI的操作的,比如在子執行緒呼叫TextView.setText()就會引發以下錯誤:

Only the original thread that created a view hierarchy can touch its views.

故而可以用 “Handle + Thread”的方式,子執行緒執行耗時操作,通過Handler通知主執行緒更新UI。但是這個方式略微麻煩,於是便引入了AsyncTask。

AsyncTask特點

AsyncTask

AsyncTask簡單使用

public void request() {

    AsyncTask<String, Integer, Integer> task = new AsyncTask<String, Integer, Integer>() {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.i("AsyncTask", "準備執行後臺任務");
        }

        @Override
        protected Integer doInBackground(String... params) {
            String url_1 = params[0];

            doRequest(url_1);

            publishProgress(50);

            String url_2 = params[1];

            doRequest(url_2);

            publishProgress(100);

            return 1;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            Log.i("AsyncTask", "當前進度" + values[0] + "%");
        }

        @Override
        protected void onPostExecute(Integer ret) {
            super.onPostExecute(ret);
            Log.i("AsyncTask", "執行完畢,執行結果" + ret);
        }
    };

    String url_1 = "https://api.github.com/users/smuyyh";
    String url_2 = "https://api.github.com/users/smuyyh/followers";

    task.execute(url_1, url_2); // 開啟後臺任務
}

程式碼較為簡單,就不做過多的解釋了。後面著重介紹AsyncTask的內部實現機制。

原理分析

這一節將從原始碼的角度來分析一下AsyncTask。以下原始碼基於Android-23.

  • 開啟後臺任務之前,首先需要建立AsyncTask的例項,所以還得從建構函式說起。
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            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);
            }
        }
    };
}

在建構函式中,初始化了兩個變數,分別是mWorker與mFuture,mFuture建立的時候傳入了mWorker引數,而mWorker本身是一個Callable物件。那麼,mFutrue是個什麼東西呢?

mFuture是一個FutureTask物件,FutureTask實際上是一個任務的操作類,它並不啟動新執行緒,並且只負責任務排程。任務的具體實現是構造FutureTask時提供的,實現自Callable介面,也就是剛才的mWorker。

  • AsyncTask物件穿件完畢之後呼叫execute(Params...)執行,跟進看看
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

只有一句話,可知是呼叫executeOnExecutor進行執行。這裡就有個疑問了,sDefaultExecutor是個什麼東西?在說這個之前,需要明確一下一下三個事:

1、Android3.0之前部分程式碼

private static final int CORE_POOL_SIZE = 5;  
private static final int MAXIMUM_POOL_SIZE = 128;  
private static final int KEEP_ALIVE = 10;  

private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,  MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sWorkQueue, sThreadFactory); 

2、在Android3.0之前,AsyncTask執行中最終觸發的是把任務交給線池THREAD_POOL_EXECUTOR來執行,提交的任務並行的線上程池中執行,但這些規則在3.0之後發生了變化,3.0之後提交的任務是預設序列執行的,執行完一個任務才執行下一個!

3、在Android3.0以前執行緒池裡核心執行緒有5個,任務佇列的任務數最大不能超過128個,執行緒池裡的執行緒都是並行執行的,在3.0以後,直接呼叫execute(params)觸發的是sDefaultExecutor的execute(runnable)方法,而不是原來的THREAD_POOL_EXECUTOR。在Android4.4以後,執行緒池大小等於 cpu核心數 + 1,最大值為cpu核心數 * 2 + 1。這些變化大家可以自行對比一下。

跟進原始碼不難發現,sDefaultExecutor實際上是指向SerialExecutor的一個例項,從名字上看是一個順序執行的executor,並且它在AsyncTask中是以常量的形式存在的,因此在整個應用程式中的所有AsyncTask例項都會共用同一個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);
        }
    }
}

SerialExecutor是使用ArrayDeque這個佇列來管理Runnable物件的。當Executor提交一個任務,執行一次execute(),在這裡向mTasks佇列新增一個Runnable物件。初次新增任務時mActive為null,故接下來會執行scheduleNext(),將mActive指向剛剛新增的runbale,並提交到THREAD_POOL_EXECUTOR中執行。

當AsyncTask不斷提交任務時,那麼此時mActive不為空了,所以後續新增的任務能得到執行的唯一條件,就是前一個任務執行完畢,也就是r.run()。所以這就保證了SerialExecutor的順序執行。這個地方其實也是一個坑,初學者很容易在這裡踩坑,同時提交多個任務,卻無法同步執行。

如果想讓其並行執行怎麼辦?AsyncTask提供了一下兩種方式:

task.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 

task.executeOnExecutor(executor, params); //可以自己指定執行緒池
  • 繼續跟進executeOnExecutor(Executor exec, Params... params)程式碼
@MainThread
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;
}

不難看出,最後是呼叫Executor的execute(Runnable command)方法啟動mFuture。預設情況下,sDefaultExecutor就是SerialExecutor類,所以為序列執行。當然使用者也可以提供自己的Executor來改變AsyncTask的執行方式。最後在THREAD_POOL_EXECUTOR真正啟動任務執行的Executor。

上面已經提到,Execute執行是呼叫Runnable的run()方法,也就是mFuture的run方法,繼續跟進程式碼

public void run() {
    if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

從第10行程式碼可發現,最後是呼叫callable的call()方法。那麼這個callable是什麼呢?就是初始化mFuture傳入的mWorker物件。在前面的建構函式那邊可以發現call()方法,我們單獨分析一下這個方法

public Result call() throws Exception {
    mTaskInvoked.set(true);

    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    Result result = doInBackground(mParams);
    Binder.flushPendingCommands();
    return postResult(result);
}

看了這麼久,終於發現了doInBackground(),深深鬆了一口氣。執行完之後得到的結果,傳給postResult(result)。繼續跟進

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

可以發現,最後是通過Handler的方式,把訊息傳送出去,訊息中攜帶了MESSAGE_POST_RESULT常量和一個表示任務執行結果的AsyncTaskResult物件。而getHandler()返回的sHandler是一個InternalHandler物件,InternalHandler原始碼如下所示:

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @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;
        }
    }
}

這裡對訊息的型別進行了判斷,如果是MESSAGE_POST_RESULT,就執行finish(),如果是MESSAGE_POST_PROGRESS,就onProgressUpdate()方法。那麼什麼時候觸發如果是MESSAGE_POST_PROGRESS訊息呢?就是在publishProgress()方法呼叫的時候,publishProgress()方法用finial標記,說明子類不能重寫他,不過可以手動呼叫,通知進度更新,這就表明了publishProgress可在子執行緒執行。

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

然後看一下finish()的程式碼。

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

不難發現,如果當前任務被取消掉了,就會呼叫onCancelled()方法,如果沒有被取消,則呼叫onPostExecute()方法,這樣當前任務的執行就全部結束了。並且,當你再次呼叫execute的時候,這個時候mStatus的狀態為Status.FINISHED,表示已經執行過了,那麼此時就會拋異常,這也就是為什麼一個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內部實現的邏輯。

總結

可以看出,在使用AsyncTask的過程中,有許多需要注意的地方。

  1. 由於Handler需要和主執行緒互動,而Handler又是內建於AsnycTask中的,所以,AsyncTask的建立必須在主執行緒,execute的執行也應該在主執行緒。
  2. AsyncTask的doInBackground(Params… Params)方法執行在子執行緒中,其他方法執行在主執行緒中,可以操作UI元件。
  3. 儘量不要手動的去呼叫AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute 這些方法,避免發生不可預知的問題。
  4. 一個任務AsyncTask任務只能被執行一次。否則會拋IllegalStateException異常
  5. 在doInBackground()中要檢查isCancelled()的返回值,如果你的非同步任務是可以取消的話。cancel()僅僅是給AsyncTask物件設定了一個標識位,雖然執行中可以隨時呼叫cancel(boolean mayInterruptIfRunning)方法取消任務,如果成功呼叫isCancelled()會返回true,並且不會執行 onPostExecute() 方法了,取而代之的是呼叫 onCancelled() 方法。但是!!!值得注意的一點是,如果這個任務在執行之後呼叫cancel()方法是不會真正的把任務結束的,而是繼續執行,只不過改變的是執行之後的回撥方法是 onPostExecute還是onCancelled。可以在doInBackground裡面去判斷isCancle,如果取消了,那就直接return result; 當然,這種方式也並非非常完美。
  6. Asynctask的生命週期和它所在的activity的生命週期並非一致的,當Activity終止時,它會以它自有的方式繼續執行,即使你退出了整個應用程式。另一方面,要注意Android螢幕切換問題,因為這時候Activity會重新建立。

相關文章