Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> AsynTask異步任務源代碼解析

AsynTask異步任務源代碼解析

編輯:關於Android編程

AsyncTask異步任務的簡單小demo

在Android中很多人都使用過AsyncTask,我們先從下面的一段代碼代碼來簡單熟悉一下異步任務的使用過程吧~~

package com.asyn.task;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by shion on 2016/7/31.
 */
public class MainActivity extends Activity implements View.OnClickListener {
    private Button mButton;
    ProgressDialog mDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.btnProgress);
        mButton.setOnClickListener(this);
        mDialog = new ProgressDialog(this);
        mDialog.setTitle("標題");
        mDialog.setMessage("開始下載");
        mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mDialog.setCancelable(false);
        mDialog.setCanceledOnTouchOutside(false);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnProgress:
                /**開始異步任務*/
                new MyAsynTask().execute(0);
                break;
        }
    }

    /**
     * 聲明AsyntTask的實現類
     */
    class MyAsynTask extends AsyncTask {
        public MyAsynTask() {
            super();
        }

        /**
         * 運行在UI線程,異步任務開始前的回調的方法,這裡可以初始化一些基本的數據
         */
        @Override
        protected void onPreExecute() {
            mDialog.setMax(100);
            mDialog.show();
        }
         /**
         * 運行在子線程,異步任務執行的方法
         */
        @Override
        protected Integer doInBackground(Integer... params) {
            /**模擬異步任務耗時的過程*/
            while (true) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                params[0] += 10;
                if (params[0] == 100) {
                    break;
                }
                /**在執行異步任務的過程中不斷更新進度值*/
                publishProgress(params[0]);
            }
            return params[0];
        }

        /**
         * 運行在UI線程,不斷更新,通過publishProgress()方法
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            mDialog.setProgress(values[0]);
            mDialog.setMessage(values[0] + "/" + 100);
        }

       /**
       *運行在UI線程,異步任務最終執行完畢之後回調
       */
        @Override
        protected void onPostExecute(Integer integer) {
            mDialog.setMessage("下載完成!");
            mDialog.cancel();
        }
    }
}

代碼運行後會有效果如下:
vcjOzvFkZW1v" src="/uploadfile/Collfiles/20160805/201608050945441.gif" title="\" />
上面代碼運行之後點擊Button按鈕,會開啟一個模擬耗時任務的異步請求的時候主要涉及下面的四個方法:
onPreExecute()方法會進行一些信息初始化,比如這裡設置Dialog進度最大值;
doInBackground()會執行耗時操作請求,比如上面模擬耗時加載,實際開發中這裡要加載網絡請求,
這個方法運行在子線程;
onProgressUpdate()專門用來來更新耗時任務的進度,它是通過publishProgress()方法傳值;
onPostExecute是在異步任務執行完成後才在UI中最後調用的方法;
另外,在使用AsyncTask的時候,我們通常需要注意一下幾點要求:
1.AsyncTask的初始化必須在UI線程中進行;為什麼?
2.AsyncTask的execute()方法也必須要在UI線程中進行;
為什麼?
3.AsyncTask的doInBackground()方法運行在子線程裡面,所以不能更新UI;為什麼?
4.AsynTask的實例對象只允許被執行一次,否則會有異常;
為什麼?
帶著這些為什麼,下面我們從源代碼的角度來逐一分析吧!


AsyncTask源代碼分析

對於異步任務,我們分別使用Android2.2和Android5.0源代碼來分析,這是因為從Android3.0之前和之後AsyncTask的源代碼發生了改變,確切的說是改變了對線程池的使用;3.0以前所有任務是並發執行,效率很高,但是有Bug,就是當任務數量超過138個有就會拋異常。3.0以後任務是按順序執行,效率比之前低了點,但是任務的數量沒有限制;下面我們從源代碼的角度慢慢看看;

Android2.2 AsyncTask源代碼分析


我們首先來看AsyncTask實例對象的源代碼new AsyncTask

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable() {
            public Result call() throws Exception {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                return doInBackground(mParams);
            }
        };

        mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                Message message;
                Result result = null;

                try {
                    result = get();
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
                            new AsyncTaskResult(AsyncTask.this, (Result[]) null));
                    message.sendToTarget();
                    return;
                } catch (Throwable t) {
                    throw new RuntimeException("An error occured while executing "
                            + "doInBackground()", t);
                }

                message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                        new AsyncTaskResult(AsyncTask.this, result));
                message.sendToTarget();
            }
        };
    }

這個類有點長,其實就是兩個內部類的創建;一個是mWorker實例(Callable類型),一個是mFuture實例(FutureTask類型);
第1-3行是注釋,意思是創建見一個異步任務對象,這個構造方法必須執行的UI線程中;
第5-10行是mWorker對象的構造,我們說了,它是一個Callable類型的任務,這點可以從源碼中看出

 private static abstract class WorkerRunnable implements Callable {
        Params[] mParams;
    }

在mWorker中我們重寫了它的call方法,當這個任務被執行的時候call()方法會被調用,在哪裡?在線程池中,這個後面會知道,再看第8行調用了doInBackGround(),並且返回結果是call方法的返回值;
第12-39行是mFuture對象,它是一個FutureTask類型的實例,並且構造中傳入了mWorker.先說說FutureTask是專門用於控制和管理任務的執行,它封裝了Callable類型任務(它管理Callable類型的任務),當任務執行的時候,他可以獲取任務的執行結果。當前mFurureTask只重寫了done()方法,這個方法在Callable類型的任務(mWorker)被取消或者執行完畢的時候,都會被調用的。
上面我們只是大體分析了一些AsyncTask的構造方法裡面的初始化的變量信息,至於call()方法和done()方法裡面,我們先不管。只看著這麼一點我們很難了解真相的,所以在繼續往下看execute()方法;


AsyncTask的執行execute()方法的源代碼

 public final AsyncTask execute(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;
        sExecutor.execute(mFuture);

        return this;
    }

首先這個方法是final修飾,這表明它不允許子類重寫這個方法,也就是不能改變異步任務的執行過程;
第2行,mStatus是什麼鬼?看看他所屬的類,就知道這是一個枚舉類,有三個對象,而且mStatus默認是Status.PENDING(待執行狀態);

     private volatile Status mStatus = Status.PENDING;
     /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

通過類注釋,我們就可以知道這個枚舉類是用於指明當前任務的狀態,在一個任務的生命周期內每種狀態只會被設置一次;這裡明顯只有三種狀態PENDING(任務未執行狀態)RUNNING(任務執行狀態)FINISHED(任務完成狀態)
上面的mStatus默認是Status.PENDING,
所以第2行為false,if條件不會執行~~~
緊接著第14行,mStatus=Status.RUNNING,置為運行狀態;
我們在回看第4-12行,就會發現一旦mStatus變為可運行狀態之後,便不可以載調用了execute()方法了,否則會有異常,也就說AsynTask的實例對象只允許被執行一次,否則會有異常
第16行,onPreExecute()是一個空方法,需要AsyncTask的子類實現這個方法
第18行,為mWorker的params的屬性賦值,記得mWorker是Callable類型的任務;
第19行,sExecutor.execute(mFuture),在線程池sExecutor中執行了任務;
sExecutor是一個線程池,這個我們後面再來分析;
第21行, return返回對象引用本身;

好了,這段代碼好像也還是說明不了什麼太多的東西,那我們趕緊看看sExecutor.


跟創建sExecutor線程池相關代碼

    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 BlockingQueue sWorkQueue =
            new LinkedBlockingQueue(10);

    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 ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

這部分源代碼主要和sExecutor線程池相關,它首先是ThreadPoolExecutor類型的,這個構造中每個參數的含義如下:
CORE_POOL_SIZE:表示的是線程池中核心線程的數量,這裡是5個,會一直存活在線程池中,即使空閒狀態;
MAXIMUM_POOL_SIZE:表示的是線程池中最大的線程數量這裡是128個;
KEEP_ALIVE:當線程池中線程的數量超過了核心線程的數量的時候,KEEP_ALIVE是非核心線程(超出核心線程的那部分)被銷毀之前可以等待新任務到來的最大時間;這裡顯然是指的線程次處於空閒狀態的時候;
TimeUnit.SECONDS:是指的KEEP_ALIVE的時間單位,這裡是秒;
sWorkQueue:是一個阻塞隊列,它的容量為10個;
sThreadFactory:是一個線程工廠,專門用於生產線程,這裡生產的線程名稱都是AsyncTask#+數字
線程池的這部分就介紹完了,下面看看具體任務是怎麼執行的;

這裡總結一下,當前線程池最大容量是128,當超過128個的時候,阻塞隊列容量是10,再多會怎麼辦?
再往下面看看線程池的執行就明白了。


線程池的執行 sExecutor.execute(mFuture)
我們要看的是這個方法,線程池的執行,它執行的是線程池中的任務,也就是mFuturn封裝的Callable(還記得嗎,就是上面最開始的mWorker),就是下面這個代碼縱call()方法內部的消息!看到了doInBackground,因為執行在線程池的線程裡,所以說doInBackground運行在子線程中,所以說doInBackground()方法中不可以更新UI

 mWorker = new WorkerRunnable() {
            public Result call() throws Exception {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                return doInBackground(mParams);
            }
        };

我們這裡不分析線程池的execute方法的內部執行流程了,反正添加到線程池的任務是運行在子線程裡面的,我們看下線程池execute方法的注釋吧!

 /**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) 

看第5-7行注釋:這裡說了,如果任務不能被提交執行,要麼由於執行已關閉或者線程池的容量已經滿了。這時候任務會被RejectedExecutionHandler,也就是會拋出異常。這裡也就是說當任務的數量超過線程容量和阻塞隊列的容量時,會拋出異常。
開始的時候提到,Android3.0之前的AsyncTask是有bug的,當前線程池容量是128加上阻塞隊列是10,也就是138個任務,如果我們一下同時運行超過138個任務的話,馬上就會拋異常了。這個地方是AsyncTask異步任務的一個bug,這個bug在Android3.0以後版本被修復了。
再接著call()方法被執行完畢的時候,mFuture就會回調自身的done()方法,這個方法的說明任務已經執行完畢了,我們再看mFuture,

mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                Message message;
                Result result = null;

                try {
                    result = get();
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,
                            new AsyncTaskResult(AsyncTask.this, (Result[]) null));
                    message.sendToTarget();
                    return;
                } catch (Throwable t) {
                    throw new RuntimeException("An error occured while executing "
                            + "doInBackground()", t);
                }

                message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                        new AsyncTaskResult(AsyncTask.this, result));
                message.sendToTarget();
            }
        };

第8行,通過get方法獲取到任務的執行結果,也就doBackground()方法的返回值;
第24-26行,通過sHandler發送效果到主線程來更新結果;
message的what是MESSAGE_POST_RESULT
message的Obj是new AsyncTaskResult(AsyncTask.this, result)),這裡是使用AsyncTaskResult封裝了AsyncTask,和doBackGround的返回值result;

再接著看sHandler

 private static final InternalHandler sHandler = new InternalHandler();
    private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @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;
                case MESSAGE_POST_CANCEL:
                    result.mTask.onCancelled();
                    break;
            }
        }
    }

這裡我們看到了MESSAGE_POST_RESULT,我們順著這一路慢慢往下看看吧
第6行,先獲取AsyncTaskResult的實例對象result,
第10行,result.mTask.finish(result.mData[0]);我們先看看AsyncTaskResult這個類,封裝了AsyncTask和泛型T,

 private static class AsyncTaskResult {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

在接著看result.mTask的finish(result.mData[0]),這個finish是怎麼實現的?看代碼

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

第2行判空,
第3行,onPostExecute(),
第4行, mStatus = Status.FINISHED;標記狀態為任務完成狀態。

我們再來看 MESSAGE_POST_PROGRESS,它下面執行的是result.mTask.onProgressUpdate(result.mData),它何時會被執行!我們是不是忘了publishProgress方法了,看下面代碼,,

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

看看第2行,消息的what等於MESSAGE_POST_PROGRESS,
消息的obj等於new AsyncTaskResult(this, values),然後傳遞給到sHandler來處理;
到這裡,我們就把AsyncTask總體上分析完了。
但是還有兩個疑問沒有回答呀!AsyncTask為什麼必須在UI中創建呀以及它的execute()方法為什麼要運行在UI線程中呀?
其實這和Handler的消息機制有關系,如果你不懂的話,你可以看看我的這篇博文消息機制學習一下;
如果在子線程中更新常見的AsyncTask,那麼它內部的屬性sHandler也就是相當於在子線程中被創建,但是子線程中並沒有創建Looper對象呀!這個時候就會拋異常 “Can’t create handler inside thread that has not called Looper.prepare()”;
至於execute()也必須要求在UI線程中執行,就有點牽強了,但是為什麼還必須這樣要求,是因為onPreExecute()有可能會在UI線程中初始化一些基本信息,比如UI控件相關的,這個時候你必須要讓execute()執行在UI主線程中,
而如果onPreExecute()沒有實現,那麼execute()完全可以在子線程中使用execute方法,不過為了謹慎和安全以及方便,execute()最好不好再子線程中調用;

到這裡我們就把AsyncTask都分析完了。上面說到了由於AsyncTask存在缺陷,就是因為線程池的容量問題,導致任務數量不能同時運行超過138個任務,所以Android3.0以後就專門修復了這個BUG,下面我們再來看看Android5.0源碼分析


Android5.0源碼分析

這裡我們就不從頭開始分析了,我們只看線程池的處理部分,其他都是差不多,5.0中把任務按照順序來執行,而3.0之前是並發執行的,我們還是從源碼的角度分析吧5.0中AsyncTask部分含有2個線程池,一個用於順序控制,一個用於執行任務。看看源代碼,這個設計真的很精妙,就是執行效率比之前慢了

  private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
   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);
            }
        }
    }

第6行,當執行任務的時候,首先會創建一個任務,添加到mTask順序隊列裡面;
第16行,第一次是null,if條件為true,進入scheduleNext()
第22行,mTask.poll()去任務,是非空的,
第23行,THREAD_POOL_EXECUTOR是另外一個線程池,專門用於執行,不貼代碼了,開始執行任務了。
再看第10行,mFutrueTask調用自身的run()方法,最後還是會執行mWorker,這裡這個不是重點
5.0以後AsyncTask內部開始自己定義了一個線程池SERIAL_EXECUTOR,內部阻塞隊列換成了ArrayDeque,同時execute方法變成了synchorzied的了,這樣即使在不同的線程中,執行的時候就是在這個隊列裡面同步取任務讓另一個線程池執行,方法變成同步有序的了,而且隊列還是無容量限制的,只是執行任務是按照順序同步執行,效率趕不上3.0之前的異步執行。但是解決了之前的BUG。

到這裡消息機制就分析就到這裡了,有不對的地方希望多多指正,相互學習,共同進步~~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved