(轉)AsyncTask完全解析篇

AsyncTask的基本用法

AsyncTask本身是一個抽象類,若想要使用它,需要創建一個子類去繼承它,且必須複寫它的抽象方法doInBackground()。
在繼承AsyncTask類時需要指定三個泛型參數:

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

這三個參數的用途:

  • Params
    在執行AsyncTask的execute(Params)時傳入的參數,可用於在doInBackground()任務中使用。
  • Progress
    後臺執行任務進度值的類型。
  • Result
    後臺任務執行完畢後,如果需要結果返回,可指定爲Result類型的泛型數據作爲返回值類型。
    舉個具體的例子:
/**
 * Created by Kathy on 17-2-17.
 */

public class MyTask extends AsyncTask<Object, Integer, Boolean> {


    @Override
    protected Boolean doInBackground(Object... params) {
        return null;
    }
}

第一個泛型參數指定爲Object,表示在執行AsyncTask任務時,execute(Object)方法需要傳入Object類型的參數給後臺;
第二個泛型參數指定爲Integer,表示將使用整型數據作爲進度顯示單位;
第三個泛型參數指定爲Boolean,標識用布爾型數據來反饋執行結果。

AsyncTask的基本方法及用途

  1. onPreExecute()
    這個方法在後臺任務執行前調用,用於進行界面的初始化,比如顯示一個進度條對話框等。

  2. doInBackground(Params… params)
    必須重寫的抽象方法!這個方法中的所有代碼都會在子線程中運行,我們應該在這裏去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress…)方法來完成。

  3. publishProgress(Progress… values)
    反饋當前任務的執行進度,在doInBackground()中調用。

  4. onProgressUpdate(Progress… values)
    當在後臺任務中調用了publishProgress(Progress…)方法後,這個方法就很快會被調用,方法中攜帶的參數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應更新。

  5. onPostExecute(Result result)
    當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作爲參數傳遞到此方法中,可以利用返回的數據來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。

  6. boolean cancel(boolean mayInterruptIfRunning)
    試圖取消任務的執行,但是如果任務已經被完成會取消失敗。執行這個方法會導致doInBackground()執行完後不會去執行onPostExecute()方法,而是執行onCancelled()方法。

  7. boolean isCancelled()
    如果在任務執行完成前被取消,該方法返回true。

  8. onCancelled(Result result)
    任務被取消後回調的方法。

簡單實例

    class DownloadImageTask extends AsyncTask<Void, Integer, Boolean> {  
      
        @Override  
        protected void onPreExecute() {  
            // 顯示進度條
            progressBar.show();  
        }  
      
        @Override  
        protected Boolean doInBackground(Void... params) { 
                   int downloadprogress = doDownload();  
                   // 通知進度條更新UI
                   publishProgress(progress);
            return true;  
        }  
      
        @Override  
        protected void onProgressUpdate(Integer... values) {  、
            // 更新進度條
            progressBar.setProgress(values[0] );  
        }  
      
        @Override  
        protected void onPostExecute(Boolean result) {  
            // 進度條消失
            progressBar.setVisibility(View.INVISIBLE);
            progressBar.setProgress(0);
        }  
    }  

在UI線程裏執行上述任務,只需要調用如下execute()方法即可:

new DownloadImageTask().execute();

源碼角度分析AsynTask

啓動AsynTask任務,需要構建它的實例,首先來看AsyncTask的構造函數:

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };
e
        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);
                }
            }
        };
    }

AsyncTask的構造函數只是初始化了兩個變量mWorker和mFuture,並在初始化mFuture時傳入了mWorker作爲參數。

如果需要啓動某個任務,需要調用AsyncTask的execute()方法:

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, 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()方法
        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);
        return this;
    }

這裏判斷了任務的狀態,任務有三種狀態:PENDING / RUNNING / FINISHED

    /**
     * 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,
    }

可以發現execute()執行方法中,最先被調用的是onPreExecute()方法。接下來發現調用了exec.execute(mFuture)這個語句並將mFuture這個FutureTask對象傳遞進來,那麼exec是Executor類型的,且向上追溯,是執行execute()方法時由sDefaultExecutor傳遞進來的,且看sDefaultExecutor定義的地方:

    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

可以看到sDefaultExecutor的賦值是一個常量,且爲SerialExecutor類的實例,則exec.execute(mFuture)的執行方法可以追溯到SerialExecutor的execute()方法中。

    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類中也有一個execute()方法,這個方法裏的所有邏輯就是在子線程中執行的。且執行execute()方法時傳入的Runnable對象爲FutureTask對象,所以會調用到FutureTask類的run()方法。最終的最終,會執行到mWorker對象的call()方法:

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

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

可以看到以上方法中調用到了doInBackground(mParams)去做耗時處理,所以doInBackground()實在onPreExcute()方法之後執行,且在子線程中執行。
接下來,將返回值類型爲Result的doInBackground()的執行結果傳遞到postResult(result)中:

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

postResult()方法的作用是什麼呢?它使用Handler發送一條消息MESSAGE_POST_RESULT,並攜帶上AsyncTaskResult對象,這個對象中攜帶任務執行的結果。那麼接收這條消息的回調方法在哪兒呢?這個取決於發送消息的Handler:

    private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

InternalHandler的回調方法如下,且發現InternalHandler的Looper是主線程的,所以InternalHandler的回調方法是執行在主線程中!

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

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

從以上InternalHandler可以看到:

1.如果收到MESSAGE_POST_RESULT消息,會執行finish()方法;
2.如果收到MESSAGE_POST_PROGRESS消息,會執行onProgressUpdate()方法。

我們來看finish()方法:

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

邏輯很清晰了,執行完doInbackground()方法後,返回的結果通過IntenalHandler的實例發送消息傳遞給IntenalHandler的回調方法,最後,在主線程中執行finish()方法;如果任務執行被取消,則執行onCancelled()方法;如果沒有被取消,則繼續向下執行onPostExecute()方法,且將狀態設置爲Status.FINISHED。

可以看到,在IntenalHandler的回調方法中接收另外一個消息MESSAGE_POST_PROGRESS,這個消息是在哪兒發送的呢?

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

以上可知,在doInBackground()方法中通過調用publishProgress()實現子線程 和UI線程通信,publishProgress()通過handler發送消息,在主線程中接收消息的地方會執行到onProgressUpdate()方法去更新UI。

綜上可知,Asynctask背後使用的仍然是Handler異步消息處理機制,只是在源碼層做了封裝,我們在使用時,不必考慮而已。

使用AsyncTask的注意事項

  • AsyncTask的實例需要在UI線程中創建
  • execute(Params… params)方法必須在UI線程中調用
  • AsyncTask的doInBackground(Prams)方法執行異步任務運行在子線程中,其他方法運行在主線程中,可以操作UI組件。
  • 運行後,可以隨時調用AsynTask對象的cancel(boolean)方法取消任務,如果成功,調用isCancelled()將返回true,並且不再執行onPostExcute()方法,而是執行onCancelled() 方法。

你必須知道的AsyncTask的缺陷

1.生命週期

AsyncTask不隨着Activity的生命週期的銷燬而銷燬,這使得AsyncTask執行完成後,Activity可能已經不在了。AsyncTask會一直執行doInBackground()方法直到執行結束。一旦上述方法結束,會依據情況進行不同的操作。
但是,AsyncTask的cancel(mayInterruptIfRunning)可以通過傳入一個布爾值來打斷執行的任務,mayInterruptIfRunning設置爲true,表示打斷任務,正在執行的任務會繼續執行直到完成。但是可以在doInBackground()方法中有一個循環操作,通過調用isCancelled()來判斷任務是否被打斷,如果isCancelled() == true,則應避免再執行後續的一些操作。
總之,使用AsyncTask需要確保AsyncTask正確地取消。

2.內存泄露風險

在Activity中使用非靜態匿名內部AsyncTask類,由於Java內部類的特點,AsyncTask內部類會持有外部類的隱式引用。由於AsyncTask的生命週期可能比Activity的長,當Activity進行銷燬AsyncTask還在執行時,由於AsyncTask持有Activity的引用,導致Activity對象無法回收,進而產生內存泄露。

3.結果丟失

另一個問題就是在屏幕旋轉等造成Activity重新創建時AsyncTask數據丟失的問題。當Activity銷燬並重新創建後,還在運行的AsyncTask會持有一個Activity的非法引用即之前的Activity實例。

4.串行還是並行?

    new AsyncTask1.execute();  
    new AsyncTask2.execute();  

上面兩個任務是同時執行呢,還是AsyncTask1執行結束之後,AsyncTask2才能執行呢?實際上是結果依據API不同而不同。

關於AsyncTask時串行還是並行有很多疑問,這很正常,因爲它經過多次的修改。

在1.6(Donut)之前:

在第一版的AsyncTask,任務是串行調度。一個任務執行完成另一個才能執行。由於串行執行任務,使用多個AsyncTask可能會帶來有些問題。所以這並不是一個很好的處理異步(尤其是需要將結果作用於UI試圖)操作的方法。

從1.6到2.3(Gingerbread)

後來Android團隊決定讓AsyncTask並行來解決1.6之前引起的問題,這個問題是解決了,新的問題又出現了。很多開發者實際上依賴於順序執行的行爲。於是很多併發的問題蜂擁而至。

3.0(Honeycomb)到現在

好吧,開發者可能並不喜歡讓AsyncTask並行,於是Android團隊又把AsyncTask改成了串行。當然這一次的修改並沒有完全禁止AsyncTask並行。你可以通過設置executeOnExecutor(Executor)來實現多個AsyncTask並行。關於API文檔的描述如下
If we want to make sure we have control over the execution, whether it will run serially or parallel, we can check at runtime with this code to make sure it runs parallel

本文轉自作者Big不吃魚——>原文鏈接

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