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的注意事項

  • AsynTask的實例需要在UI線程中創建
  • execute(Params... params)方法必須在UI線程中調用
  • AsynTask的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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容