Android進階知識:AsyncTask相關

1.前言

以前這篇Android基礎知識:多線程基礎總結裏提到了Android中幾種多線程任務使用方式,這篇就詳細看一下其中AsyncTask的使用和原理。android

2. 基礎使用

AsyncTask從名字就能夠看出看來是Android中提供的一個異步任務類,它的使用方法以下:
1.實現自定義AsyncTask,複寫對應方法bash

class MyAsyncTask extends AsyncTask<String, Integer, String> {
        int percent = 0;

        @Override
        protected void onPreExecute() {
            //doInBackground執行以前
            Log.d(TAG, "onPreExecute:" + Thread.currentThread().getName() + " 異步任務準備開始 ");
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            //doInBackground執行中
            Log.d(TAG, "onProgressUpdate:" + Thread.currentThread().getName() + " 任務進行中:" + values[0] + "%");
        }

        @Override
        protected void onPostExecute(String result) {
            //doInBackground執行以後
            Log.d(TAG, "onPostExecute:" + Thread.currentThread().getName() + " " + result);
        }

        @Override
        protected String doInBackground(String... strings) {
            String param = strings[0];
            Log.d(TAG, "doInBackground:" + Thread.currentThread().getName() + " 異步任務執行中,得到參數:" + param);
            while (percent < 100) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                percent += 10;
                publishProgress(percent);
            }
            Log.d(TAG, "doInBackground:" + Thread.currentThread().getName() + " 異步任務完成!");
            return "任務完成啦";
        }
    }
複製代碼

2.建立自定義的AsyncTask對象,調用execute方法開始任務多線程

MyAsyncTask myAsyncTask = new MyAsyncTask();
    myAsyncTask.execute("測試");
複製代碼

運行結果: 異步

關於實現 AsyncTask它有三個泛型,分別對應開始傳入的參數類型,中間進度類類型和最後任務結束返回的結果類型,實現時須要根據要求進行設置。另外還有四個主要複寫的方法分別是 doInBackgroundonPreExecuteonProgressUpdateonPostExecute

  • doInBackground:看名知意,是負責執行異步任務的方法,運行在後臺子線程,是必須複寫的方法。
  • onPreExecute:在doInBackground方法以前被調用,能夠在進行異步任務作一些準備工做,運行在主線程。
  • onPostExecute:在doInBackground方法以後被調用,得到一部人物大的返回結果,能夠進行一些處理結果的操做,運行在主線程。
  • onProgressUpdate:在doInBackground方法中調用publishProgress方法後被調用,用於異步任務時更新進度,運行在主線程。

示例代碼裏在doInBackground方法中模擬了耗時任務並輸出了日誌,經過日誌能夠確認複寫的幾個方法的執行順序而且發現onPreExecuteonProgressUpdateonPostExecute這三個方法是運行在主線程中的,而doInBackground方法是運行在子線程中的。這也和預期的同樣,因此使用時耗時任務都是寫在doInBackground方法裏的。另外AsyncTask能夠在調用execute方法時傳入所需的參數,在doInBackground方法裏能夠獲取到這些傳入參數,而且這裏的傳參是個可變參數對參數個數不作限制。關於AsyncTask的使用就是這些,接下來來看它的運行原理。ide

3.運行原理

瞭解運行原理就要查看他的源碼,AsyncTask源碼很少,一共不到800行就實現了須要的功能。咱們按照AsyncTask的使用步驟來看它的源碼流程。在實現本身定義的AsyncTask後,第一個步驟就是建立自定義的AsyncTask對象,因此就從構造函數開始看起。函數

public AsyncTask() {
        this((Looper) null);
    }
    public AsyncTask(@Nullable Handler handler) {
        this(handler != null ? handler.getLooper() : null);
    }
  
    public AsyncTask(@Nullable Looper callbackLooper) {
        // 給mHandler初始化賦值
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
        // 建立WorkerRunnable
        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 {
                    // 將返回結果post傳遞
                    postResult(result);
                }
                return result;
            }
        };
        // 建立FutureTask傳入WorkerRunnable
        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有三個構造函數,最常使用的是無參的構造函數,固然也能夠調用有參的傳入一個Handler或者Looper,無論調用那個最終都會走到參數是Looper的這個構造中來,這個構造函數中首先對成員變量中的mHandler進行賦值,賦值先判斷了傳入的Looper是否爲空,若是爲空或者傳入的Looper是主線程的Looper就調用getMainHandler直接將主線程的Handler賦值給mHandler,不然就用傳入的Looper建立一個Handler給成員變量賦值。oop

接着程建立了一個WorkerRunnable,看名字就知道應該是個工做線程,看到它複寫了call方法再結合下面又把這個WorkerRunnable傳入了一個FutureTask能夠猜想,它是由一個Callable實現的,點擊跟蹤找一下這個類,他是一個內部類。post

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }
複製代碼

能夠看到就是一個實現了Callable接口的抽象類,其中封裝了咱們的傳入參數。回到構造函數中WorkerRunnablecall方法中顯示設置了當前線程的優先級爲後臺進程,接着執行了doInBackground方法得到異步任務的返回結果,最後在finally代碼塊中調用postResult方法將結果傳遞。測試

構造函數中的最後一步是建立了一個異步任務對象FutureTaskWorkRunnable傳入,在異步任務完成時會調用done方法,複寫的done方法中一樣先經過get方法得到異步任務結果,再經過postResultIfNotInvoked方法將結果傳遞。關於CallableFutureTask不清楚的能夠去看前一篇多線程基礎中有簡單的介紹。ui

在進行下一步調用execute方法以前,先看一下AsyncTask類還有一個靜態代碼塊,咱們知道靜態代碼塊會隨類的加載而加載,因此先來看下這個靜態代碼塊。

// CPU核心數
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    //線程池核心線程數
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    // 線程池最大線程數
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    // 線程池中線程空閒存活實現
    private static final int KEEP_ALIVE_SECONDS = 30;
    // 線程工廠
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        // 原子型指令Integer類型
        private final AtomicInteger mCount = new AtomicInteger(1);
        // 返回一個名爲AsyncTask #加上編號的線程
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
    // 線程池的阻塞隊列長度128
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }
複製代碼

靜態代碼塊中初始化了一個線程池,並將其賦值給成員變量中的THREAD_POOL_EXECUTOR,從註釋看是一個用於執行並行任務的線程池。下面就進入execute方法來看看AsyncTask究竟是怎麼運行的。

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

execute方法中進而調用了executeOnExecutor方法,除了傳入了params還傳入了一個executeOnExecutor,先來看看executeOnExecutor是什麼?

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    /**
     * 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();
複製代碼

它引用了成員變量中的SERIAL_EXECUTOR,而SERIAL_EXECUTOR又是一個SerialExecutor對象,根據註釋來看它是一個用來串行執行任務的線程池。來進一步看這個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 {
                        // 調用傳入的Runnable的run方法
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            // 第一次進入或對列爲空mActive爲空直接調用scheduleNext
            if (mActive == null) {
                scheduleNext();
            }
        }
        // 從隊列中取出任務交給THREAD_POOL_EXECUTOR線程池處理
        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
複製代碼

SerialExecutor中有一個任務隊列mTasks,它的execute方法中將傳入的Runnable從新封裝入一個新的Runnable,在新的Runnablerun方法中調用原來Runnablerun方法,而且在finally代碼塊中調用了scheduleNext方法,最終將這個新的Runnable任務加入mTasks任務隊列。以後判斷了mActive是否爲空,爲空的話也調用scheduleNext方法。這個mActive看到是在scheduleNext方法中從任務隊列中取出的Runnable對象,scheduleNext方法中取出任務後不爲空將這個任務交給THREAD_POOL_EXECUTOR這個線程池去處理,THREAD_POOL_EXECUTOR就是靜態代碼塊中初始化的線程池,也是最終處理異步任務的線程池。

接着再回到executeOnExecutor方法中,AsyncTaskexecute方法中調用的executeOnExecutor方法。

@MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
            // 判斷AsyncTask的狀態不爲PENDING拋出對應異常
        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)");
            }
        }
        // 狀態爲PENDING就將狀態修改成RUNNING
        mStatus = Status.RUNNING;
        // 調用onPr方法eExecute
        onPreExecute();
        // 將execute方法中傳入的參數傳遞到構造函數中建立的WorkerRunnable中
        mWorker.mParams = params;
        // 將構造函數中建立的FutureTask交給exec線程池即sDefaultExecutor線程池處理
        exec.execute(mFuture);
        return this;
    }
複製代碼

方法中先判斷AsyncTask的狀態,AsyncTask的狀態有如下幾種。

public enum Status {
        /**
         * 表示任務還沒有執行
         */
        PENDING,
        /**
         * 表示任務正在執行
         */
        RUNNING,
        /**
         * 表示任務已經完成
         */
        FINISHED,
    }
複製代碼

AsyncTask的不是還沒有執行狀態就拋出對應的異常,不然就調用onPreExecute這個異步任務執行前的方法,而後將接收到的參數傳入構造函數中創WorkerRunnable中,最後調用exec.execute方法將異步任務交給exec線程池即sDefaultExecutor線程池處理。接着就會按照以前看過的同樣,異步任務會先進入SerialExecutor線程池,在SerialExecutor中放入取出串行任務隊列,最終交給THREAD_POOL_EXECUTOR線程池執行任務。

以前看過WorkRunnable任務執行完成後會調用postResultpostResultIfNotInvoked方法。

private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }
複製代碼

postResultIfNotInvoked方法中仍是調用了postResult方法,因此進入這個postResult方法跟蹤查看。

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

postResult方法中調用了getHandler方法得到成員變量中的mHandler發送了個消息,消息中將異步任務的返回結果封裝成了一個AsyncTaskResult對象放入消息的obj發送。AsyncTaskResult對象將是將當前的AsyncTask和異步任務結果作了個封裝。

private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }
複製代碼

還記得mHandler是在哪兒建立的嗎?是在AsyncTask的構造函數中經過getMainHandler方法建立的。

private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }
複製代碼

getMainHandler方法中用主線程Looper建立一個InternalHandler。剛纔的postResult方法中發送的消息,就交給的是這個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) {
                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中的handleMessage方法中處理了兩種消息,一種是異步任務完成結果的消息,另外一種是更新進度的消息。仍是先來看任務結果的消息,其對應MESSAGE_POST_RESULT。調用result.mTask.finish方法,即AsyncTaskfinish方法。

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

finish方法中先判斷了此時任務有沒有被取消,取消了就會走onCancelled方法,沒取消就會調用onPostExecute這個異步任務完成後的方法,而後將AsyncTask的狀態修改成已完成。至此AsyncTask的一次執行流程結束。

最後來看看異步任務更新進度的方法。要進行進度更新須要咱們在doInBackground方法中調用publishProgress(percent)方法將進度傳入。因而來看這個publishProgress(percent)方法。

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

方法中先判斷任務沒被取消就獲取Handler發送更新進度消息,進度數據一樣封裝到AsyncTaskResult類型裏。而後再看怎麼處理消息。

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;
            }
        }
複製代碼

處理消息很簡單就是調用了AsyncTaskonProgressUpdate方法將進度數據傳入便可。至此AsyncTask的運行原理就所有講完了,最後經過兩張圖梳理下整個過程。

方法流程

4.AsyncTask的問題

4.1 AsyncTask必須在主線程建立調用

AsyncTask必定要在主線程建立調用嗎?實踐是檢驗真理的惟一標準。

new Thread(new Runnable() {
    @Override
    public void run() {
        MyAsyncTask myAsyncTask = new MyAsyncTask();
        myAsyncTask.execute("測試");
    }
}).start();
複製代碼

運行結果日誌:

根據運行結果日誌觀察,發現子線程中建立和運行 AsyncTask並無發生錯誤,只是 onPreExecute方法調用的線程變了,變成建立的子線程了,這也很好理解,根據以前看過的源碼能夠知道 onPreExecute方法是在 executeOnExecutor方法中直接調用的,因此就是運行在 AsyncTask建立線程裏。而其餘方法要麼是經過線程池中線程調用,要麼是經過 InternalHandler到主線程中調用,因此運行線程與以前無異。

那麼子線程中建立使用AsyncTask單單只是onPreExecute方法運行線程不一樣嗎?爲何會有必須在主線程建立調用這種說法呢?

其實這種說法是創建在老版本的AsyncTask上的,在上面的運行原理中全部的源碼相關都是截取的API28版本的AsyncTask的源碼。其中Handler是得到的主線程的Looper建立的。

sHandler = new InternalHandler(Looper.getMainLooper());
複製代碼

而在早期版本中,這裏看API10版本的AsyncTask中的代碼。

private static final InternalHandler sHandler = new InternalHandler();
複製代碼

早期版本中建立Handler時沒有使用主線程的Looper因此建立AsyncTask的線程就是Handler所在線程,這樣會致使onPostExecuteonProgressUpdate方法都會運行在這個線程,若是建立AsyncTask不在主線程就會致使onPostExecuteonProgressUpdate方法也不運行在主線程,進而在這兩個方法中沒法直接對UI進行更新。

4.2 Android3.0以前AsyncTask是並行執行任務,3.0以後爲串行執行。

關於這點一樣仍是源碼版本差別,新版本源碼從以前的運行原理描述中能夠看到AsyncTask在調用execute方法開啓任務後,是先將任務放入一個SerialExecutor線程池,其中維護了一個隊列,每次都是從這個隊列中一個個取任務再交給真正處理任務的線程池。而在早期版本中是沒有這個SerialExecutor這個線程池的,任務都是直接放入任務線程池執行的。

public final AsyncTask<Params, Progress, Result> execute(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;
        sExecutor.execute(mFuture);
        return this;
    }

    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
複製代碼

上面這段是API10對應Android2.3.7版本中AsyncTaskexecute方法,能夠看到任務是直接加入到sExecutor這個任務線程池中的。

4.3 AsyncTask內存泄漏

在使用AsyncTask若爲非靜態內部類,在Activity銷燬時,AsyncTask中的耗時任務尚未完成,這時AsyncTask會還持有Activity的引用,形成其沒法被會正常回收,形成內存泄漏。

解決方法也很簡單隻要使用靜態類static加上弱引用WeakReference就能夠了。另外從源碼中看出AsyncTask有提供一個cancel方法,那麼在Activitydestory方法裏調用這個方法取消任務可不能夠解決內存泄漏呢?答案是不能夠的,具體仍是看源碼。

private final AtomicBoolean mCancelled = new AtomicBoolean();

    public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }
複製代碼

看到AsyncTaskcancel方法只是將mCancelled這個狀態修改成true,另外調用了mFuture.cancel方法。

public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              U.compareAndSwapInt(this, STATE, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
複製代碼

mFuture.cancel方法中最終仍是調用了線程的t.interrupt方法,而Threadinterrupt方法是不會當即中斷線程的,它一樣是僅僅修改了一箇中斷線程的標誌狀態,須要本身在代碼中判斷處理或者等該線程進入阻塞纔會拋出一個InterruptedException異常退出線程。因此AsyncTaskcancel方法是不能阻止內存泄漏的。

5.總結

關於AsyncTask的內容就是以上這些了,AsyncTask做爲官方提供的一種處理異步任務的封裝類,其實也並無那麼好用,稍不注意就會發生各類問題,不過做爲了解,理解它的運行原理仍是頗有必要的。

相關文章
相關標籤/搜索