AsyncTask 面試解析

[Toc]java

基礎認識

AsyncTask 是基於 Handler 進行封裝的輕量級異步類,它是一個抽象類,咱們要使用的時候須要實現其子類的如下 4 個方法android

方法 描述
onPreExecute() 任務執行前被調用,執行在 UI 線程中,在這裏咱們作一些任務啓動前的準備
doInBackground() 執行在新的子線程中的,作異步任務的處理
onProgressUpdate() 這個方法是在調用 publishProgress 的時候被調用的,是運行在 UI 線程的
onPostExecute() 這個方法是任務執行完畢以後被調用的,是運行在 UI 線程中的

做用

  • 實現多線程: 在工做線程中執行任務,如 耗時任務
  • 異步通訊、消息傳遞: 實現工做線程 & 主線程(UI線程)之間的通訊,即:將工做線程的執行結果傳遞給主線程,從而在主線程中執行相關的UI操做

AsyncTask 的三種狀態

每一個狀態在一個任務的生命週期中只會被執行一次。bash

狀態 描述
PENDING 等待(尚未開始執行任務)
RUNNING 執行中
FINSHED 完成

AsyncTask 的內部執行過程

AsyncTask 的對象調用 execute 方法,execute 內部又調用了 executeOnExecutor ,onPreExecute 方法就是在這裏被回調,以後將 AsyncTask 的參數封裝成一個併發類,而後將其添加到排隊線程池(SerialExecutor)中進行排隊,若是當前有任務正在執行,則等待,不然 THREAD_POOL_EXECUTOR 執行該任務。在任務的執行過程當中,經過 InternalHandler 將進度 pos(MESSAGE_POST_GROGRESS)發送到主線程中,此時會調用 onProgressUpdate 方法,任務執行完畢以後,InternalHandler 將結果 post(MESSAGE_POST_RESULT) 發送到主線程中,此時 onPostExecute 或者 onCancle 會被調用,任務執行到這裏就結束了。markdown

基本使用

  • 舉個栗子:在異步任務中每隔 1s 打印 1 ~ 10 的數值
    1. 咱們不干預任務的執行過程,由任務執行完成,查看任務執行狀況;
    2. 任務執行完成後,咱們再點擊開始,查看任務執行狀況;
    3. 干預任務的執行過程,在任務執行期間點擊取消,查看任務執行狀況;
public class MainActivity extends AppCompatActivity {
    private TextView mText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mText = findViewById(R.id.text);
        progressAsycn1 = new ProgressAsycn();
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressAsycn1.execute(1);
            }
        });
         findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressAsycn1.cancel(true);
                mText.append(String.format("取消任務%s\n",new Date().toString()));
            }
        });

    }


    private ProgressAsycn progressAsycn1;
    private class ProgressAsycn extends AsyncTask<Integer,Integer,String> {

        // 這個方法是在啓動以前被調用的,執行在 UI 線程中,在這裏咱們作一些任務啓動前的準備
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            mText.append(String.format("準備執行%s\n",new Date().toString()));
        }

        // 這個方法是執行在新的線程的中的
        @Override
        protected String doInBackground(Integer... params) {
            for (int i = params[0]; i <= 10; i++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                publishProgress(i);
            }
            return "任務已經執行完畢";
        }

        // 這個方法是在調用 publishProgress 的時候被調用的,是運行在 UI 線程的
        @Override
        protected void onProgressUpdate(Integer... values) {
            mText.append(String.format("工做進度:%d\n",values[0]));
        }

        // 這個方法是任務執行完畢以後被調用的
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            mText.append(String.format("任務執行完畢%s\n",new Date().toString()));
        }

        /**
         * 異步任務被取消時回調,即 AsyncTask 的對象調用了 cancel 方法
         * 這個方法和 onPostExecute 互斥
         * doInBackground 方法中的任務執行完畢,纔會被回調
         */
        @Override
        protected void onCancelled() {
            mText.append(String.format("異步任務已取消%s\n",new Date().toString()));
        }
    }
}
複製代碼
  • 執行結果多線程

    1. 不干預任務的執行過程,最後的執行結果以下

    asyncTask1.png
    2. 在 1 執行完以後,再點擊開始,結果程序報以下錯誤

    java.lang.IllegalStateException: Cannot execute task: the task has already been executed (a task can be executed only once)
    複製代碼
    1. 在任務的執行過程當中,點擊取消任務,調用 cancel() 方法,以後咱們能夠看到 onProgressUpdate() 和 onPostExecute() 方法再也不被調用,可是咱們取消任務的時候,任務仍是沒有中止的,等到任務真正中止的時候,onCancelled() 方法被調用,執行效果圖以下:

    asyncTask2.png

  • 問題:從上面的執行結果中,咱們能夠看出兩個問題併發

      1. AsyncTask 的對象只能被調用一次,再次調用的時候,會出錯
      1. Asynctask 調用了 cancel() 方法取消任務,可是任務並無真正的中止

提出問題

咱們在閱讀源碼以前,先給本身提一些問題,而後咱們在閱讀源碼的時候,帶着問題來去找答案,這樣咱們的目標纔會更加明確。app

  1. 爲何AsyncTask 的對象只能被調用一次,不然會出錯?(每一個狀態只能執行一次)
  2. AsyncTask 的類爲何必須在主線程加載
  3. AsyncTask 的對象爲何必須在主線程中建立
  4. AsyncTask 是串行執行任務仍是並行執行任務?
  5. AsyncTask 調用 cancel() 任務是否當即中止執行?onPostExecute() 還會被調用嗎?onCancelled() 何時被調用?

內部源碼分析

構造函數中作了什麼

首先,咱們查看在 AsyncTask 的構造函數裏面到底作了些什麼異步

/**
     * 該構造方法必須在 UI 線程中被調用
     */
    public AsyncTask() {
        this((Looper) null);
    }
    
    
    /**
     * 該構造方法必須在 UI 線程中被調用
     *
     * @hide
     */
    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);
                    // doInBackground 的調用時機
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                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);
                }
            }
        };
    }
複製代碼

從以上的代碼中,咱們能夠看出 AsyncTask 作了如下幾個工做:async

  • 初始化 handleride

    • 咱們能夠從默認調用的構造函數中傳入的 callbackLooper 是爲 null,那麼 handler 初始化的時候是調用 getMainHandler() 方法的,getMainHandler() 方法的的具體代碼以下:
    private static InternalHandler sHandler;
    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }    
    複製代碼
    • 咱們從上面的代碼中能夠知道,getMainHandler() 最後返回的值是 sHandler,而 sHandler 是個靜態 InternalHandler 實例,而關於 InternalHandler 的實現,咱們在下面再作分析。 咱們在這裏須要注意的點有兩個
      • 爲了可以將執行環境切換到主線程中,咱們要求 sHandler 必須在主線程中建立,因此 AsyncTask 的構造函數必須在 UI 線程中調用
      • 咱們知道靜態成員會在加載類的時候進行初始化,因爲 sHandler 是個靜態變量,那麼咱們要求 AsyncTask 類必須在 UI 線程中進行加載,不然 AsyncTask 沒法正常工做
  • 初始化 mWorker

    • mWorker 是一個 Callable 對象,並在以後初始化 mFutrue 的時候做爲參數傳入,咱們在 mWorker 的代碼實現這裏能夠看到 doInBackground() 在這裏被調用了(可是真正的調用時機不不是在這裏進行的,而是在 SerialExecutor 的 execute 的方法中),並將結果返回給 result,並在方法結束以前調用 postResult(result)。 方法
  • 初始化 mFuture

    • MFuture 是一個 FutureTask 對象

execute() 中作了什麼

接着若是想要啓動某一個任務,就須要調用該任務的 execute() 方法,所以如今咱們來看一看 execute() 方法的源碼

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

execute() 方法的比較簡單,只是調用了 executeOnExecutor 方法,那麼具體的邏輯是在 executeOnExecutor 裏面,咱們再接着看下去:

@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;
        exec.execute(mFuture);

        return this;
    }

複製代碼
  • 首先,executeOnExecutor 先對當前的狀態進行判斷,因爲 AsyncTask 中有三個狀態(PENDING、RUNNING、FINISHED),而且每一個狀態在 AsyncTask 的生命週期中有且只能被執行一次,若是當前的狀態不是 Status.PENDING(未執行),那麼就拋出異常。在這裏也解釋了,咱們 execute() 方法只能被執行一次,再次調用會報錯的緣由。
  • 接着,咱們能夠看到 onPreExecute() 方法被調用了
  • 緊接着,咱們能夠看到 exec.execute(mFuture) 這行代碼,而 exec 又是什麼,經過查找上面的 execute 方法,咱們能夠看到這個 exec 是一個 sDefaultExecutor 變量,sDefaultExecutor 變量的定義以下:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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

複製代碼

從以上代碼能夠看到,在 executeOnExecutor 中的 exce 最終調用的是 SerialExecutor 中 execute() 方法。而 execute() 這個方法的邏輯是在子線程中執行的,而 execute 這個方法傳入的 Runnable 正是 mFuture,在 run 方法中調用了, mFuture 對象的 run。咱們再找到 FutureTask 中實現的 run 方法的代碼,代碼以下(有省略):

public void run() {
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                try {
                    result = c.call();
                } catch (Throwable ex) {
                   
                }
            }
        } finally {
            // .....
        }
    }

複製代碼

從上面的代碼中咱們能夠看出,最終調用了 Callable 中的 call (),而這個 callball 就是咱們在 executeOnExecutor 中傳入 mFuture 的 mWorker 對象。如今咱們又從新拿出 MWorker 的代碼來看一下:

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);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };
複製代碼

咱們從上面的代碼能夠看到兩個主要的點,首先是 doInBackground() 方法的調用,並將結果給到 result,最後在方法結束以前調用了 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 中使用了 sHandler 對象發送了一條消息,消息中攜帶了 MESSAGE_POST_RESULT 常量和一個表示任務執行結果的 AsyncTaskResult 對象。這個 sHandler 對象是 InternalHandler 類的一個實例,那麼稍後這條消息確定會在 InternalHandler 的 handleMessage() 方法中被處理。InternalHandler 的源碼以下所示:

private static class InternalHandler extends Handler {
       
        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;
            }
        }
    }
複製代碼

在 handleMessage 這裏對消息的類型進行了判斷,若是這是一條 MESSAGE_POST_RESULT 消息,就會去執行 finish() 方法,若是這是一條 MESSAGE_POST_PROGRESS 消息,就會去執行 onProgressUpdate() 方法。那麼 finish() 方法的源碼以下所示:

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

複製代碼

能夠看到,若是當前任務被取消掉了,就會調用 onCancelled() 方法,若是沒有被取消,則調用 onPostExecute() 方法,這樣當前任務的執行就所有結束了。

咱們注意到,在剛纔 InternalHandler 的handleMessage() 方法裏,還有一種 MESSAGE_POST_PROGRESS 的消息類型,這種消息是用於當前進度的,調用的正是 onProgressUpdate() 方法,那麼何時纔會發出這樣一條消息呢?相信你已經猜到了,查看publishProgress()方法的源碼,以下所示:

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

最後咱們來理一下 executeOnExecutor 中的任務啓動到結束,關鍵方法的調用順序:

executeOnExecutor() -> sDefaultExecutor.execute() -> mFuture.run() -> mWorker.call() -> doInBackground() -> postResult() -> sHandler.sendMessage() -> sHandler.handleMessage() -> onPostExecute()

複製代碼

問題解決

  1. 爲何 AsyncTask 的對象只能被調用一次,不然會出錯?(每一個狀態只能執行一次)

    從上面咱們知道,AsyncTask 有 3 個狀態,分別爲 PENDING、RUNNING、FINSHED,並且每一個狀態在 AsyncTask 的生命週期中有且只執行一次。因爲在執行完 execute 方法的時候會先對 AsyncTask 的狀態進行判斷,若是是 PENDING(等待中)的狀態,就會往下執行並將 AsyncTask 的狀態設置爲 RUNNING(運行中)的狀態;不然會拋出錯誤。AsyncTask finish 的時候,AsyncTask 的狀態會被設置爲 FINSHED 狀態。
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)");
            }
        }
複製代碼
  1. AsyncTask 的類爲何必須在主線程加載

    因爲 sHandler 是一個靜態的 Handler 對象,爲了可以將執行環境切換到主線程中,這就要求 sHandler 必須在主線程中建立,也便是 AsyncTask 必須在主線程中建立。因爲靜態成員會在加載類的時候進行初始化,所以也要求 AsyncTask 的類必須在主線程中加載,不然 AsyncTask 沒法正常工做。

  2. AsyncTask 的對象爲何必須在主線程中建立

    由於在 AsyncTask 的構造函數中對 handler 進行了初始化的操做,因此 AsyncTask 必須在主線程中進行建立,不然 AsyncTask 沒法進行線程切換的工做

  3. AsyncTask 是串行執行任務仍是並行執行任務?

    在 Android 1.6 以前,AsyncTask 是串行執行任務的,Android 1.6的時候 AsyncTask 是並行執行任務的,Android 3.0 以後,爲了不併行錯誤,AsyncTask 又採用一個線程來串行執行任務。

  4. AsyncTask 調用 cancel() 任務是否當即中止執行?onPostExecute() 還會被調用嗎?onCancelled() 何時被調用?

    任務不會當即中止的,咱們調用 cancel 的時候,只是將 AsyncTask 設置爲 canceled(可取消)狀態,咱們從如下代碼能夠看出,AsyncTask 設置爲已取消的狀態,那麼以後 onProgressUpdate 和 onPostExecute 都不會被調用,而是調用了 onCancelled() 方法。onCancelled() 方法是在異步任務結束的時候才調用的。時機是和 onPostExecute 方法同樣的,只是這兩個方法是互斥的,不能同時出現。

@WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
    
// 線程執行完以後纔會被調用
private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
複製代碼

AsyncTask 在使用的過程當中的一些限制總結

  • (1)AsyncTask 的類必須在主線程加載
  • (2)AsyncTask 的對象必須在主線程中建立
  • (3)execute 方法必須在 UI 線程中調用
  • (4)不要在主線程中調用 onPreExecute 、doInbackground、onPressUpdate、onPostExecute
  • (5)AsyncTask 的對象只能被調用一次,不然會出錯
  • (6)AsyncTask 不太適合作太耗時的操做
  • (7)在 Android 1.6 以前,AsyncTask 是串行執行任務的,Android 1.6的時候 AsyncTask 是並行執行任務的,Android 3.0 以後,爲了不併行錯誤,AsyncTask 又採用一個線程來串行執行任務。
  • (8)若是在 AsyncTask 中的 doInBackGround 中開啓了新的線程,咱們執行了 cancle() 方法來中止異步任務,線程是不會被中止的,直到任務執行完成爲止,這個過程當中,onProgressUpdate 和 onPostExecute 是不會被調用的
相關文章
相關標籤/搜索