Android 筆記之 AsyncTask

AsyncTask

        AsyncTask 是一個輕量級的異步任務類,它可以在線程池中執行後臺任務,然後把執行的進入和最終結果傳遞給主線程並在主線程中更新UI。它封裝了Thread 和Handler,但是AsyncTask 不適合執行特別耗時的後臺任務,對於特別耗時的任務建議採取使用線程池。

AsyncTask是一個抽象類,所以如果我們想使用它,就必須要創建一個子類去繼承它。在繼承時我們可以爲AsyncTask類指定三個泛型參數,這三個參數的用途如下,如果不需要某個參數,可以設置爲 Void

1. Params(參數類型)

在執行AsyncTask時需要傳入的參數,可用於在後臺任務中使用,是excute()任務執行方法和 doInBackground(Params...)的輸入參數,通常爲String

2. Progress(後臺任務執行的進度類型)

後臺任務執行時,如果需要在界面上顯示當前的進度,則使用這裏指定的泛型作爲進度單位,是 onProgressUpdate(Progress...)和 publishProgress(Progress...)方法的參數,是一個數組類型的參數,一般爲Integer

3. Result(返回的結果類型)

當任務執行完畢後,如果需要對結果進行返回,則使用這裏指定的泛型作爲返回值類型,是onPostExecute(Result)方法的輸入參數

如果AsyncTask 確實不需要傳遞具體的參數,這三個泛型參數可以使用Void 替代。

AsyncTask 的聲明如下所示:

public abstract class AsyncTask<Params, Progress, Result> {

因此,一個最簡單的自定義AsyncTask就可以寫成如下方式:

 

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}

 

這裏我們把AsyncTask的第一個泛型參數指定爲Void,表示在執行AsyncTask的時候不需要傳入參數給後臺任務。第二個泛型參數指定爲Integer,表示使用整型數據來作爲進度顯示單位。第三個泛型參數指定爲Boolean,則表示使用布爾型數據來反饋執行結果。

一個異步任務的執行一般包括以下幾個步驟。

1.execute(Parmas...parmas)

    執行一個異步任務,需要我們在代碼中調用此方法,觸發異步任務的執行

2. onPreExecute()

執行在 UI 線程,在execute(Parmas...parmas)被調用後立即執行,這個方法會在後臺任務開始執行之前調用,調用後立即執行,用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。

3. doInBackground(Params...)

在線程池中執行,在onPreExecute() 完成後立即執行,這個方法中的所有代碼都會在子線程中運行,我們應該在這裏去處理所有的耗時任務。此方法將接收輸入參數和返回計算結果,並將計算結果給onPostExecute 方法。在執行過程中可以調用 publishProgress(Progress...) 方法來更新任務進度信息。publishProgress 會調用 onProgressUpdate 方法。

4. onProgressUpdate(Progress...)

執行在 UI 線程,當在後臺任務中調用了 publishProgress(Progress...) 方法後,這個方法就會很快被調用,直接將進度信息更新到 UI 組件上。

5. onPostExecute(Result)

執行在 UI 線程,當後臺操作結束時,該方法被調用,doInBackground(Params...) 函數返回的計算結果將作爲參數傳遞到此方法中,直接將結果顯示到 UI 組件上比如說提醒任務執行的結果,以及關閉掉進度條對話框等。

上面這幾個方法,onPreExecute 先執行,接着是doInBackground,最後纔是onPostExecute。除了上述四個方法以外,AsyncTask 還提供了 onCancelled 方法,它同樣執行在UI 線程,當異步任務被取消時,onCancelled 方法會被調用,此時onPostExecute 方法則不會被調用。如下所示:

public class DownloadFilesTask
        extends AsyncTask<URL, Integer, Long>
{
    //執行在線程池中
    @Override
    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);//更新下載任務進度
            if (isCancelled()) {//判斷下載任務是否取消
                break;
            }
        }
        return totalSize;
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    @Override
    protected void onPostExecute(Long result) {
        showDialog("Downloaded" + result + "bttes");
    }
}

        注意doInBackground 和 onProgressUpdate 方法他們的參數中均包含 ... 的字樣,在Java 中 ... 表示參數的數量不定,它是一種數組類型的參數。當要執行上述下載任務時,可以通過如下方式來完成:

   new DownloadFilesTask().execute(url1,url2.url3);

 

在使用的時候,有以下幾點需要格外注意(AsyncTask 的侷限):

1、AsyncTask 對象必須在 UI 線程中創建,因爲在AsyncTask 中會構建一個 Handler 

2、execute(Parmas...parmas)方法必須在 UI 線程中調用,因爲在AsyncTask 中會構建一個 Handler 
3、不能在 doInBackground(Params...) 中更改 UI 組件信息,因爲在子線程中執行
4、不要在程序中直接調用 onPreExecute()、onPostExecute()、doInBackground() 和 onProgressUpdate() 方法
5、一個任務實例只能執行一次,如果執行第二次將會拋出異常
6、在Android1.6 之前,AsyncTask 是串行執行任務的,在Android 1.6 AsyncTask 採用線程池裏處理並行任務,但從Android 3.0 開始,爲避免AsyncTask 所帶來的併發錯誤,AsyncTask 又採用一個線程來串行執行任務。在Android 3.0 之後的版本中,我們仍然可以通過AsyncTask 的 executeOnExecutor 方法來並行的執行任務。

 

AsyncTask 的工作原理:

        分析AsyncTask 的工作原理,從它的excute 方法開始,如下所示:

  private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
      public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

 

        execute 方法會調用 executeOnExecutor 方法,源碼如下所示:

    
    private final WorkerRunnable<Params, Result> mWorker;
    @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;//將參數賦值給mWorker
        exec.execute(mFuture);//mFuture 會交給 SerialExecutor 的 execute 方法處理

        return this;
    }

        在上述代碼中,sDefaultExecutor 實際上是一個串行的線程池,一個進程中所有的AsyncTask 全部在這個串行的線程池中排隊執行,在 executeOnExecutor 方法中,AsyncTask 的onPreExecute 方法最先執行,然後線程池開始執行,其執行過程如下所示:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            //將FutureTask 對象插入到任務隊列 mTasks 中,
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            //如果當前沒有正在活動的AsyncTask,則調用scheduleNext 
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {//執行下一個AsyncTask 任務
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

        從上面代碼可知,首先系統會把AsyncTask 的Params 參數封裝爲 FutureTask 對象,FutureTask 是一個併發類,在這裏充當了Runnable 的作用。接着這個 FutureTask 會交給 SerialExecutor 的 execute 方法處理,SerialExecutor 的 execute 方法首先會把FutureTask 對象插入到任務隊列 mTasks 中,如果這個時候沒有正在活動的AsyncTask,則調用scheduleNext方法來執行下一個AsyncTask 任務。 同時當一個AsyncTask 任務執行完後,AsyncTask 會繼續執行其他任務知道所有的任務都被執行爲止,從這點可以看出,默認情況下,AsyncTask 是串行執行的。

 

        AsyncTask 中有兩個線程池(SerialExecutor 和 THREAD_POOL_EXECUTOR )和一個 Handler(InternalHandler),其中線程池SerialExecutor 用於任務的排隊,線程池 THREAD_POOL_EXECUTOR  用於正真的執行任務,InternalHandler 用於將執行環境榮線程池切換到主線程。THREAD_POOL_EXECUTOR  的定義如下所示:

public static final Executor THREAD_POOL_EXECUTOR;
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//CPU核心數
    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;//最大線程數
    private static final int KEEP_ALIVE_SECONDS = 30;//超時時間
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);//任務隊列容量

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

        AsyncTask 對 THREAD_POOL_EXECUTOR 的配置規格如上所示。

 

      AsyncTask 的構造方法如下所示:

    public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        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);//調用doInBackground 並獲取返回值
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);//調用postResult 方法,並將返回值作爲參數傳遞過去
                }
                return 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);
                }
            }
        };
    }

 

        由於FutureTask 的run 方法會調用 mWorke 的call 方法,因此 mWorke 的call 方法最終會在線程池中執行。FutureTask 的run 方法如下所示:

 

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    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 must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }


        在mWorke 的call 方法中,首先將  mTaskInvoked 設未 true,表明當前任務已經被調用過了,然後執行AsyncTask的 doInBackground 方法,並將返回值傳遞給 postResult 方法,postResult 方法如下所示:

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

        postResult 方法會通過 mHandler 發送一個 MESSAGE_POST_RESULT的消息,mHandler 的定義如下所示:

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

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

 

        mHandler 是一個靜態的Handler 對象,爲了能夠將執行環境切換到主線程,這就要求 mHandler 這個對象必須在主線程中創建。由於靜態成員會在加載類的時候進行初始化,因此這就變相的要求 AsyncTask 的類必須在主線程中加載,否則同一個進程中的 AsyncTask 都將無法正常工作。

        mHandler 接收到 MESSAGE_POST_RESULT 消息後會調用 AsyncTask 的finish 方法,如下所示:

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

        從上述代碼可知,如果AsyncTask 被取消了,就調用 onCancelled 方法,否則就會調用 onPostExecute 方法,而onPostExecute 方法的參數就是 doInBackground 方法的返回值,以上就是AsyncTask 的整個工作過程。

總結:
    AsyncTask 構建的時候會做以下幾件事情:
    1、檢查當前任務是否已經執行,如果已經執行,則拋出異常,如果沒執行,則將當前任務的狀態置爲執行狀態,接着調用onPreExecute() 方法,用於執行後臺任務前的初始化操作。
    2、構建一個IntentHandler 用於切換線程,構建兩個線程池,一個用於執行任務,一個用於給任務排隊,同時還會構建一個workRunnable 並將執行的參數封裝成一個FuterTask 任務交給線程池排隊和執行,在workRunnable 中,會調用 doInBackground 執行後臺任務,並將執行結果通過postResult 方法發送給Handler,用於更新任務執行的進度及最終的執行結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章