看完這篇,再也不怕被問到 AsyncTask 的原理了

DMingO發表於2020-07-27

本文很多資料基於Google Developer官方對AsyncTask的最新介紹。

AsyncTask 是什麼

image-20200727110522173
AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

上文翻譯:AsyncTask 是一個被設計為圍繞Thread和Handler操作的工具幫助類,而不是作為通用的執行緒框架,理想情況下,應將AsyncTasks用於短操作(最多幾秒鐘)。如果需要長時間保持執行緒執行,Google建議使用java.util.concurrent這個併發包提供的各種API,例如 ExecutorThreadPoolExecutorFutureTask

!This class was deprecated in API level 30.
Use the standard java.util.concurrent or Kotlin concurrency utilities instead.

目前官方已經明確說明,AsyncTask 將會在API 30,也就是Android 11的版本中,將這個類廢棄掉。使用java.util.concurrent和Kotlin的協程元件代替AsyncTask 。

谷歌要將AsyncTask廢棄掉的原因,我猜測是:AsyncTask 是一個很古老的類了,在API Level 3的時候就有了,還有著許多致命的缺點,終於連Google都忍不了,加上目前已經有許多替代的工具了,如Kotlin協程等。

AsyncTask的缺陷

However, the most common use case was for integrating into UI, and that would cause Context leaks, missed callbacks, or crashes on configuration changes. It also has inconsistent behavior on different versions of the platform, swallows exceptions from doInBackground, and does not provide much utility over using Executors directly.

譯:

  • AsyncTask 最常見是子類繼承然後直接用在 UI層的程式碼裡,這樣容易導致Context的記憶體洩漏問題
  • Callback回撥的失效
  • 配置改變時的崩潰
  • 不同版本行為不一致:最初的AsyncTasks在單個後臺執行緒上序列執行;Android1.6時它更改為執行緒池,允許多個任務並行執行;Android 3.2時又改為只會有單個執行緒在執行請求,以避免並行執行引起的常見應用程式錯誤。
  • 在它的重要方法doInBackground中會將出現的異常直接吞掉
  • 多個例項呼叫execute後,不能保證非同步任務的執行修改順序合理
  • 在直接使用Executors方面沒有提供太多實用性

缺點真的蠻多的,簡單用用可能還行,但是要考慮清楚

AsyncTask的引數和重要方法

定義子類時需設定傳入的三大引數型別<Params , Progress , Result>,如果某型別未用到的將它設為Void

  1. Params(在執行AsyncTask時需要傳入的引數,可用於在後臺任務中使用)

  2. Progress(後臺任務執行時,如果需要在介面上顯示當前的進度,則使用這裡指定的泛型作為進度單位)

  3. Result(當任務執行完畢後,如果需要對結果進行返回,則使用這裡指定的泛型作為返回值型別)

定義子類時可重寫四大方法:onPreExecute,onProgressUpdate,doInBackground,onPostExecute

  • onPreExecute()

    這個方法會在後臺任務開始執行之間呼叫,用於進行一些介面上的初始化操作,比如顯示一個進度條對話方塊等。

  • doInBackground(Params...)

    子執行緒中執行此方法,可以將幾秒的耗時任務在這裡執行。任務一旦完成就可以通過return語句來將任務的執行結果進行返回。若Result型別,指定了Void,就不會返回任務執行結果。如果想要更新當前的任務進度想在UI中顯示出來,可以通過 publishProgress(Progress...)。

  • onProgressUpdate(Progress...)

    doInBackground(params)呼叫了publishProgress(Progress...)方法後,此方法中會被呼叫,傳遞的引數值就是在後臺任務中傳遞過來的,型別是上面說到的Progress的型別。這個方法是在UI執行緒操作,對UI進行操作,利用引數中的數值就可以對介面元素進行相應的更新。

  • onPostExecute(Result)

    當後臺任務執行完畢並通過return語句進行返回時,返回的資料會作為引數傳遞到此方法中,可以利用返回的Result來進行一些UI操作。

AsyncTask開始簡單的非同步任務

簡單來說:AsyncTask是一個抽象類,必須被子類化才能使用,這個子類一般都會覆蓋這兩個方法doInBackground(Params...)onPostExecute(Result),建立AsyncTask子類的例項執行execute方法就執行非同步任務了。

//最簡單的AsyncTask實現方式
public class DownloadTask extends AsyncTask<String, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
    @Override
    protected Boolean doInBackground(String... strings) {
        return null;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
    }
}

//在UI執行緒中啟用 AsyncTask
new DownloadTask().execute();

使用AsyncTask要遵守的規矩

  • 必須在UI執行緒上載入AsyncTask類。

  • 必須在UI執行緒上建立 AsyncTask子類的例項。

  • 必須在UI執行緒上呼叫 execute(Params ...)。

  • 不要手動呼叫onPreExecute,onPostExecute,doInBackground,onProgressUpdate這幾個方法

  • 該任務只能執行一次(如果嘗試第二次執行,則將引發異常。)

好雞肋的設定啊,不知道當初為什麼要這樣設計

AsyncTask原始碼分析

先由一行最簡單的啟動AsyncTask的程式碼入手:

new DownloadTask().execute("");

進入execute方法檢視:

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

先看下sDefaultExecutor這個屬性是什麼名堂:

	//sDefaultExecutor 被 volatile修飾
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

	//序列執行任務執行緒池例項
	public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    private static class SerialExecutor implements Executor {
        //維護一個Runnable的佇列
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;
		
        //往隊尾中壓入一個Runnable的同時執行隊頭的Runnable,維護佇列的大小
        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }
		//彈出,執行隊頭的Runnable
        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                //執行 被賦值的mActive 任務 
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
	
	//建立核心執行緒數為 1,最大執行緒容量為20的執行緒池,實際的任務執行執行緒池
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(), sThreadFactory);
        threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

可以看到的是sDefaultExecutor,是一個控制任務排隊的執行緒池,被呼叫execute時會將新的Runnable壓入任務佇列,如果Runnable mActive == null的話會取出隊頭的Runnable執行,而每當一個任務結束後都會執行任務佇列中隊頭Runnable。

這樣做的目的是:保證在不同情況,只能有一個任務可以被執行,SerialExecutor做出了單一執行緒池的效果。每當一個任務執行完畢後,下一個任務才會得到執行,假如有這麼一種情況,主執行緒快速地啟動了很多AsyncTask任務,同一時刻只會有一個執行緒正在執行,其餘的均處於等待狀態。

再來看看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;
        //由上文提到的 sDefaultExecutor 執行 FutureTask<Result>任務
        exec.execute(mFuture);

        return this;
    }
	//Callable的子類,泛型型別為<Params, Result>
    private final WorkerRunnable<Params, Result> mWorker;
	
    private final FutureTask<Result> mFuture;

executeOnExecutor 執行方法,會先檢查是否執行了這個任務或者已結束,由於AsyncTask任務規定每個任務只能執行一次,不符合就會丟擲異常。接著開始呼叫 onPreExecute 開始預執行,然後給mWorker賦值引數,執行

mFuture蘊含的任務。到這裡好像就沒了?非也非也,還有很關鍵的AsyncTask的建構函式?

/**
 * 建立一個新的非同步任務。 必須在UI執行緒上呼叫此建構函式。
*/
public AsyncTask(@Nullable Looper callbackLooper) {
        //獲取主執行緒Looper的Handler物件
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
		//Worker執行緒被執行後會執行非同步的任務
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //執行doInBackground(mParams)返回 Result型別
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };
		//FutureTask--泛型引數為 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);
                }
            }
        };
    }
	//postResultIfNotInvoked 如果未被呼叫,傳遞結果
    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }
	//利用Handler將帶有result的訊息壓入訊息佇列,等待主執行緒的Handler執行
    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        //getHandler()獲取到的就是AsyncTask構建物件時建立的‘mHandler’,持有主執行緒Looper
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

AsyncTask的構造方法,對三個重要的東西(mHandlermWorkermFuture)進行了初始化,確保他們在被執行時能夠執行相應的任務。

再來看下 mHandlerInternalHandler的例項,這個持有主執行緒Looper的Handler子類的特別之處:

    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) {
                //result.mTask屬性是AsyncTask的引用
                //當接收到不同型別訊息,會分別執行 在UI執行緒更新進度 和 結束任務的方法
                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的例項被建立時,獲取的是主執行緒的Looper,所以它的 handleMessage(msg) 方法自然也是在主執行緒中執行

再看下這個AsyncTask類內的 finish 方法

    private void finish(Result result) {
        if (isCancelled()) {
            //任務已經被取消了,執行onCancelled
            onCancelled(result);
        } else {
            //任務正常結束了,執行onPostExecute(result)
            onPostExecute(result);
        }
        //將任務狀態變更為 已結束
        mStatus = Status.FINISHED;
    }

判斷任務是由於被取消掉而結束還是正常結束,執行onCancelled(result) or 執行onPostExecute(result)

原始碼分析總結

AsyncTask類維護著一個序列執行任務的執行緒池sDefaultExecutor,一個程式中的所有的 AsyncTask 子類全部在這個執行緒池中執行。

AysncTask 中的兩個執行緒池(SerialExecutorTHREAD_POOL_EXECUTOR)和維護著一個持有主執行緒Looper的 Handler(InternalHandler),其中執行緒池 SerialExecutor 保證在不同情況,只能有一個任務可以被執行,THREAD_POOL_EXECUTOR 是核心執行緒數為1的執行緒池,負責執行耗時任務,InternalHandler用於將執行環境從後臺工作執行緒切換到主執行緒,完成UI更新進度,取消任務和任務結束後的UI操作。

相關文章