Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 從源碼角度一步步分析AsyncTask的用法與原理

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

編輯:關於Android編程

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 task = new AsyncTask() {

        @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() {
        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(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 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 mTasks = new ArrayDeque();
    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 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 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(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
(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的過程中,有許多需要注意的地方。

由於Handler需要和主線程交互,而Handler又是內置於AsnycTask中的,所以,AsyncTask的創建必須在主線程,execute的執行也應該在主線程。 AsyncTask的doInBackground(Params… Params)方法運行在子線程中,其他方法運行在主線程中,可以操作UI組件。 盡量不要手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute 這些方法,避免發生不可預知的問題。 一個任務AsyncTask任務只能被執行一次。否則會拋IllegalStateException異常 在doInBackground()中要檢查isCancelled()的返回值,如果你的異步任務是可以取消的話。cancel()僅僅是給AsyncTask對象設置了一個標識位,雖然運行中可以隨時調用cancel(boolean mayInterruptIfRunning)方法取消任務,如果成功調用isCancelled()會返回true,並且不會執行 onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。但是!!!值得注意的一點是,如果這個任務在執行之後調用cancel()方法是不會真正的把任務結束的,而是繼續執行,只不過改變的是執行之後的回調方法是 onPostExecute還是onCancelled。可以在doInBackground裡面去判斷isCancle,如果取消了,那就直接return result; 當然,這種方式也並非非常完美。 Asynctask的生命周期和它所在的activity的生命周期並非一致的,當Activity終止時,它會以它自有的方式繼續運行,即使你退出了整個應用程序。另一方面,要注意Android屏幕切換問題,因為這時候Activity會重新創建。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved