AsyncTask你真的會用嗎?實戰、原理、最佳實踐!(Android Q)

AsyncTask


AsyncTask可以非常方便、簡單的在UI線程中使用,它幫助我們在不使用Thread和Handler的情況下,在後臺執行一個後臺(異步)任務,並且把執行結果通知到UI線程中。也就是說,使用AsyncTask可以極大的簡化我們進行後臺任務的操作,使用它,我們不必關心工作線程是如何啓動的,也不必關心,工作線程和UI線程之間的通信問題,這些都被AsyncTask通過內部封裝實現了,我們只需要按接口規範使用它即可。

AsyncTask適用於短時的後臺(異步)任務(最多幾秒鐘),它並不適合時間較長的後臺任務,如果我們想在後臺線程執行長時間的異步任務,可以使用java.util.concurrent併發工具包中的Executor、ThreadPoolExecutor和FutureTask等。

AsyncTask的聲明和執行

AsyncTask執行後臺任務,是由後臺線程執行具體的任務,最後把結果發佈到UI線程。

一個後臺任務的聲明由3種泛型類型:Params、Progress和Result,以及4個過程:onPreExecute、doInBackground、onProgressUpdate和onPostExecute構成。

我們來看使用AsyncTask有哪些要求?

  • 必須創建AsyncTask的子類,以它的子類形式來使用。
  • 必須在子類中至少實現doInBackground()回調方法,通常我們還會實現onPostExecute()方法。
  • AsyncTask類必須在UI線程中被加載(Android 4.1之後自動執行)。
  • AsyncTask實例對象,必須在UI線程中創建。
  • 必須在UI線程中調用execute()來運行任務。

3種泛型

Params:doInBackground方法的參數類型,它會在任務執行之前發送給後臺任務。
Progress:onProgressUpdate方法的參數類型,它是在任務執行期間發佈的進度類型。
Result:onPostExecute方法的參數類型,它是後臺任務執行結束後的結果類型。

示例:

    private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {}

示例中的Params、Progress和Result分別是String、Integer和Long。

4個步驟

AsyncTask後臺任務執行過程,將會經歷4個步驟:

onPreExecute():UI線程中執行。它發生在後臺任務開始執行之前,我們可以通過它來進行任務的配置,比如開始展示進度條等。

doInBackground(Params…):後臺線程中執行。它在onPreExecute完成之後立即執行,它會執行較長時間的後臺運算任務。運算結果必須由該步驟return,發佈給onPostExecute方法。我們也可以在該過程中,調用一次或多次publishProgress(Progress…)方法來發布執行進度信息,進度信息最終會發布到UI線程中,成爲onProgressUpdate(Progress…)的參數。

onProgressUpdate(Progress…):UI線程中執行。它是在調用publishProgress(Progress…)之後執行的,它通常用來展示後臺任務的執行進度。

onPostExecute(Result):UI線程中執行。它是在後臺任務執行結束之後調用的,由doInBackground(Params…)方法return的結果作爲參數。

任務的取消

AsyncTask後臺任務執行中的任意時刻,都可以調用cancel(boolean)來進行取消操作。

cancel方法需要關注以下幾點:
  • 執行cancel方法之後,調用isCancelled()將返回true。
  • 執行cancel方法之後,onCancelled(java.lang.Object)方法將會在doInBackground(java.lang.Object[]) return之後執行(代替了onPostExecute方法)。
  • 我們應該儘可能及時的檢測到後臺任務的取消。可以在doInBackground方法中,使用isCancelled()來定時執行檢測工作,一旦發現任務取消,立即取消現有操作,執行return。

AsyncTask演示Demo

示例代碼,創建了一個DownloadFilesTask類,繼承於AsyncTask,用於執行後臺任務。

    private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {//3個泛型分別爲:String, Integer, Long
        protected Long doInBackground(String... urls) {//參數爲字符串列表
            int count = urls.length;
            long time = 0;
            for (int i = 0; i < count; i++) {
                try {
                    Thread.sleep(1000);//示例任務,執行線程休眠1秒,模擬耗時任務
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                time += 1000;
                publishProgress((int) (((i + 1) / (float) count) * 100));//更新進度
                if (isCancelled()) break;//檢測是否執行過程中取消了任務
            }
            return time;
        }

        protected void onProgressUpdate(Integer... progress) { //更新進度。這裏的參數是Integer對象
            mTextView.setText("進度:" + progress[0]);
        }

        protected void onPostExecute(Long result) { //執行結果回調
            mTextView.setText("time : " + result + " 毫秒");
        }
    }

異步任務準備好了,我們創建一個DownloadFilesTask類的實例,調用execute()方法即可執行:


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        new DownloadFilesTask().execute("url1", "url2", "url3", "url3");
    }

例子很簡單,但是也完整的展示了AsyncTask的基本用法。

接下來我們來分析AsyncTask的實現原理。

AsyncTask源碼解析


我們接下來分析AsyncTask的源碼,看下它的實現原理。

在Demo中,我們調用AsyncTask的實例對象的execute方法來實現調用的,那麼我們就從execute方法作爲入口點來分析。

AsyncTask的execute方法:

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

這裏調用了executeOnExecutor方法,參數sDefaultExecutor是一個線程池,作爲默認線程池。

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;
    }
邏輯解析
  • 該方法在UI線程中執行。
  • 屬性mStatus用來判斷當前任務的狀態,當任務正在運行或已經結束了,會拋出Error。
  • 設置mStatus的狀態爲Status.RUNNING。
  • 調用onPreExecute()方法,該方法通常在使用AsyncTask時,需要實現。
  • 把參數params保存在mWorker.mParams中。
  • 調用線程池,執行mFuture。

線程池執行的部分我們後面來分析,先來看這裏有2個重要的屬性mWorker和mFuture,它們起到了非常重要的作用,我們來看。

mWorker、mFuture

屬性mWorker和mFuture是在AsyncTask的構造方法中初始化的,它們是後臺線程控制邏輯的核心所在。

我們來看AsyncTask的構造方法:

    public AsyncTask() {//這裏是在UI線程中執行的
        this((Looper) null);
    }
    public AsyncTask(@Nullable Handler handler) {
        this(handler != null ? handler.getLooper() : null);
    }
    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);//這裏設置了線程的優先級爲THREAD_PRIORITY_BACKGROUND
                    //noinspection unchecked
                    result = doInBackground(mParams);//這裏調用了doInBackground方法
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result); //post結果給UI線程
                }
                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);
                }
            }
        };
    }
    
    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }
邏輯解析:
  1. 首先初始化Handler對象,因爲AsyncTask涉及到後臺線程和UI線程之間的通信,它內部實現使用了Handler通信機制。
  2. mWorker屬性賦值,這裏的mWorker是一個WorkerRunnable對象,它是一個抽象類,繼承自Callable接口,用於執行異步任務並獲取線程執行結果。WorkerRunnable對象的call()方法是在後臺線程中執行的。這裏可以看到在call()方法中,調用了doInBackground方法。
  3. call()方法中設置了線程的優先級爲THREAD_PRIORITY_BACKGROUND(後臺線程優先級)。
  4. mFuture屬性賦值,這裏的mFuture是一個FutureTask對象,FutureTask可以用於線程任務的控制等邏輯,異步任務執行完成後,會回調done()方法。
  5. done()方法中,調用postResultIfNotInvoked方法,把任務結果返回給UI線程,這裏執行的是非正常情況下的返回,正常執行結束會在mWorker的call方法中通過調用postResult(result)將結果返回給UI線程。

Handler處理

我們來看,後臺任務處理完成後,會在WorkerRunnable的call方法中,調用postResult(result)將結果返回給UI線程,但如果後臺任務執行之前被終止,則會把結果傳遞給postResultIfNotInvoked方法。

我們來看這兩個方法:

    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) { //同步屬性的布爾值,表示後臺任務是否已經開始執行
            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方法,創建一個message對象,把執行結果封裝成一個AsyncTaskResult對象,通過Handler機制,將後臺執行結果傳遞給UI線程(這裏只考慮UI線程一種情況)。

串行執行的線程池實現

到了這裏,Asynctask的執行部分、結果處理以及跨線程通信部分,都已經分析完成了,接下來我們來看後臺線程是如何執行的,任務隊列是如何實現的。

我們從上文知道,AsyncTask的execute方法中,直接使用了一個默認線程池sDefaultExecutor來負責後臺線程的創建及執行。

回顧一下:

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

sDefaultExecutor的單線程隊列的實現:

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

我們可以看到,sDefaultExecutor是SerialExecutor的一個實例對象。

SerialExecutor的屬性:

mTasks:是一個先進先出的隊列,這裏可以理解爲容量無限的一個先進先出的隊列(當然不可能無限,不過正常情況下我們是達不到它的最大容量的)。
mActive:表示當前正在執行的任務,它是一個Runnable對象。

後臺任務隊列的執行過程:

  1. 當第一次有任務進來時,先把任務添加到mTasks隊列中,這時mActive == null,執行scheduleNext方法。
  2. scheduleNext方法中,從隊列中取出隊列頭部的Runnale對象,並賦給mActive,最後把它交由THREAD_POOL_EXECUTOR線程池執行。
  3. 當第一個任務執行結束後,緊接着會調用scheduleNext方法執行下一個任務,以此類推……

串行執行的祕密:這裏其實就實現了一個單線程的任務隊列,所有AnsyncTask的任務,都會順序的,單線程執行。它的實現原理其實就是,準備了一個可以擴容的先進先出的任務隊列mTasks,所有的後臺任務執行時,先進入隊列中,然後調用scheduleNext執行,當前任務結束後,繼續執行下一個任務,這樣也就實現了串行的任務處理。

線程池THREAD_POOL_EXECUTOR

這裏的線程池其實沒有發揮線程池的作用,因爲默認情況下,AsyncTask只會執行單個任務調用,因爲在SerialExecutor中已經實現了任務的隊列管理了,SerialExecutor會單個順序的執行線程任務的調用。

THREAD_POOL_EXECUTOR的定義:

    public static final Executor THREAD_POOL_EXECUTOR;
    private static final int CORE_POOL_SIZE = 1;
    private static final int MAXIMUM_POOL_SIZE = 20;
    private static final int BACKUP_POOL_SIZE = 5;
    private static final int KEEP_ALIVE_SECONDS = 3;
    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;
    }
    

THREAD_POOL_EXECUTOR其實是一個可併發的線程池,它的核心線程數是1,最大線程數是20。(注意,不同Android版本中的實現會有不同)

線程的創建:

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

線程池是通過ThreadFactory對象來進行線程創建的,並且會爲線程命名爲"AsyncTask #" + mCount.getAndIncrement()。

AsyncTask併發的實現

在Android 3.0時,AsyncTask的任務執行改回了單一線程中順序執行,那麼我們如果想用AsyncTask實現併發任務,可以做到嗎?答案是肯定的,我們來看如何實現。

我們來看AsyncTask的executeOnExecutor方法:

    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        ……
        exec.execute(mFuture);

        return this;
    }
源碼解析:

executeOnExecutor方法,可以接受一個線程池的參數,來讓使用者實現自定義線程池的目的。
我們可以自定義一個併發的線程池,通過調用executeOnExecutor方法,替換掉AsyncTask的默認線程池,即可實現併發處理。

注意:雖然這裏可以實現線程池的併發處理,但是並不建議這麼做,如果想要實現併發的後臺任務,推薦使用java.util.concurrent併發工具包中的Executor、ThreadPoolExecutor和FutureTask等。

AsyncTask的最佳實踐


到了這裏,我們已經深入瞭解了AsyncTask的使用及實現原理了,那麼我們在開發時,有哪些是需要注意的呢?

AsyncTask使用時的一些要求:

  • AsyncTask類必須在UI線程中被加載(Android 4.1之後自動執行)。
  • AsyncTask實例對象,必須在UI線程中創建。
  • 必須在UI線程中調用execute()來運行任務。
  • 必須創建AsyncTask的子類,以它的子類形式來使用。
  • 必須在子類中至少實現doInBackground()回調方法,通常我們還會實現onPostExecute()方法。
  • 不要手動調用onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…)方法。
  • AsyncTask後臺任務,只能執行一次,否則會拋出異常。
  • 整個進程中的AsyncTask線程池及任務隊列都是同一個(自定義的除外),大量的調用AsyncTask執行後臺任務會造成任務隊列整體執行時間的變長,導致任務執行的時間不可控。
  • AsyncTask的默認線程優先級是Process.THREAD_PRIORITY_BACKGROUND(後臺線程級別),優先級較低,分配的CPU資源會較少,不適合執行優先級較高的任務。

關於執行順序及Android歷史版本中的變更

AsyncTask後臺任務的執行順序是不可靠的,因爲它在Android歷史版本中經歷了多次變更。在剛剛引入AsyncTask時,AsyncTask後臺任務的執行,是在單一線程中順序執行的;但是在Android 1.6中,AsyncTask的任務執行改成了可多線程併發執行;在Android 3.0時,AsyncTask的任務執行又改回了單一線程中順序執行,以避免多線程造成的併發等問題。

如果我們想通過AsyncTask來併發執行後臺任務,可以使用executeOnExecutor(java.util.concurrent.Executor, java.lang.Object[])方法,傳遞一個線程池來執行。

注意:AsyncTask在Android R(Android 11)中被標記爲棄用狀態,建議使用java.util.concurrent併發工具包來代替。

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