基於Android10.0源碼深入瞭解終將老去的AsyncTask

一、前言

相信AsyncTask對每一個開發者來說都非常熟悉,它是一個輕量級的異步任務類。同時,它也經歷了很多個版本的調整,如實現上從開始串行執行,再到並行執行,再後來又改回串行執行。AsyncTask從Android API3已經出現,它之所以能夠屹立不倒這麼多年,想必有很多值得我們學習的地方,本文將基於Android Q(10.0)的源碼對AsyncTask進行分析。

二、關於Deprecated

當我準備開始閱讀AsyncTask源碼的時候,我在AsyncTask的官方文檔發現了它在Android R(11.0)上已經被標記過時,官方更推薦開發者使用Kotlin的協程進行異步操作。

image

Google官方列舉了以下把它標記爲過時的原因,其實這也是AsyncTask一直以來都被詬病的地方:

  1. 容易導致內存泄漏
  2. 忘記回調
  3. 橫豎屏切換導致崩潰
  4. 不同版本的AsyncTask的兼容問題

三、使用

也許你很久已經沒有使用AsyncTask,所以我覺得有必要先幫大家重溫一下AsyncTask的使用方法,如果你已經非常熟悉它的使用,可以跳過本部分。

AsyncTask使用也是非常簡單,只需要進行兩步即可完成異步任務的執行,這裏就一筆帶過。

繼承AsyncTask

class MyAsyncTask : AsyncTask<Void, Void, Void>() {
    /**
    *  任務執行前的操作,可以不重寫,執行在UI線程
    **/
    override fun onPreExecute() {
        super.onPreExecute()
    }
    
    /**
    * 必須重寫的方法,需要在異步線程執行的任務都在這裏面執行,執行在子線程
    **/
    override fun doInBackground(vararg params: Void?): Void? {
        Log.i("MyAsyncTask", "doInBackground")
        return null
    }
    
    /**
    * doInBackground執行完會把result返回給這個方法,一般是做UI的更新,執行在UI線程
    **/
    override fun onPostExecute(result: Void?) {
        super.onPostExecute(result)
    }
}

執行AsyncTask

// 在需要的時候執行AsyncTask
val mTask = MyAsyncTask()
mTask.execute()

四、串行還是並行?

相信熟讀面經的你肯定知道AsyncTas的執行順序,當然太過久遠的版本源碼我也沒有看,我覺得知道一個大概就行了,具體如下:

  • Android1.6以前,是串行執行,實現原理是一個用一個子線程進行任務的串行執行。
  • Android1.6到2.3,是並行執行,實現原理是用一個線程數爲5的線程池進行並行執行,但是如果前5個任務執行時間過長,就會阻塞後面任務的執行,所以不適合大量任務併發執行。
  • Android3.0之後,是串行執行,使用一個全局的線程池進行串行處理任務。

當然,你可能記不住哪些版本怎麼執行,我覺得這個不重要,你只需要記住目前Android版本使用的就是串行執行就可以了,因爲Android Q(10.0)的源碼就確實是這麼做的。

1.如何驗證串行執行?

舉個例子,同時執行三個AsyncTask,每個AsyncTask的doInBackground方法裏面延時2s鍾輸出一個Log,按道理來說,如果並行執行,大概2s左右(最多誤差幾十毫秒)就能把三個Log都打印出來;反之,如果是串行執行,整個執行流程就需要大概6s左右。下面,我們來驗證下,代碼也是非常簡單。

class MyAsyncTask(index: Int) : AsyncTask<Void, Void, Void>() {
    private var mIndex = index
    override fun onPreExecute() {
        super.onPreExecute()

        Log.i("MyAsyncTask", "AsyncTask$mIndex onPreExecute.")
    }

    override fun doInBackground(vararg params: Void?): Void? {
        Thread.sleep(2000)
        Log.i("MyAsyncTask", "AsyncTask$mIndex doInBackground. thread=${Thread.currentThread().name}")
        return null
    }

    override fun onPostExecute(result: Void?) {
        super.onPostExecute(result)
        Log.i("MyAsyncTask", "AsyncTask$mIndex onPostExecute.")
    }
}

class MainActivity : AppCompatActivity() {
    private var mTask: MyAsyncTask? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 循環三次執行
        for (i in 1..3) {
            mTask = MyAsyncTask(i)
            mTask?.execute()
        }
    }
}

輸出結果:

2020-03-18 11:51:15.286 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask1 onPreExecute.
2020-03-18 11:51:15.287 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask2 onPreExecute.
2020-03-18 11:51:15.287 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask3 onPreExecute.
2020-03-18 11:51:17.288 18164-18259/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask1 doInBackground. thread=AsyncTask #1
2020-03-18 11:51:17.290 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask1 onPostExecute.
2020-03-18 11:51:19.295 18164-18351/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask2 doInBackground. thread=AsyncTask #2
2020-03-18 11:51:19.296 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask2 onPostExecute.
2020-03-18 11:51:21.299 18164-18352/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask3 doInBackground. thread=AsyncTask #3
2020-03-18 11:51:21.301 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask3 onPostExecute.

通過結果可以知道,整個過程耗時6s左右,而且,每個Task的doInBackground都是順序執行的,由此可以證明,在Android Q上,AsyncTask默認是串行執行異步任務的。

2.可以改成並行執行嗎?

Android官方知道使用默認實現肯定滿足不了各種各樣的使用場景,所以它暴露了executeOnExecutor接口,讓開發者自己實現執行異步任務的線程池,所以我們可以指定AsyncTask的線程池,從而使它變成並行執行。

  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)

線程池我們可以手動實現一個,當然AsyncTask裏面已經實現了一個核心線程數爲1,最大線程數爲20,3s線程存活的線程池:

private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_SECONDS = 3;

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

我們可以直接使用這個線程池執行AsyncTask,即可實現並行執行:

for (i in 1..3) {
    mTask = MyAsyncTask(i)
    mTask?.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}

五、實現原理

通過前面的拋磚後,我給大家拋出幾個問題,爲什麼使用了線程池就可以讓AsyncTask默認是串行執行?爲什麼手動設置了一個線程池,AsyncTask就變成了異步執行?它的內部實現到底是怎麼樣的?如果你還是不太瞭解,帶着這些疑問閱讀會比較好理解。

1.串行執行原理/執行過程原理

在AsyncTask裏面定義了一個靜態內部類SerialExecutor,SerialExecutor實現了Executor接口,對外暴露一個execute接口(注意這個接口是用synchronized修飾的),同時有一個它的實例靜態變量sDefaultExecutor,也就是說sDefaultExecutor在一個進程裏面是隻有一個對象,即每一個AsyncTask都是共用這個sDefaultExecutor。

那麼當多個AsyncTask調用execute時,就會觸發執行sDefaultExecutor的execute方法,由於這個方法是同步的,那麼調用的就會持有SerialExecutor的鎖,其他AsyncTask再調用execute的時候,就需要等到前一個AsyncTask釋放鎖之後才能調用,這樣就實現了多個AsyncTask的串行執行。具體流程圖如下所示。

image

結合源碼一起來看它的實現:

(1).執行任務的線程池

在AsyncTask內部創建了一個線程池,核心線程數是1,最大線程數是20,這個線程池最終是進行異步任務執行的,以下代碼就是它的一個初始化,並沒有執行邏輯:

// 核心線程數是1
private static final int CORE_POOL_SIZE = 1;
// 最大線程數20
private static final int MAXIMUM_POOL_SIZE = 20;
// 空閒線程存活時間3s
private static final int KEEP_ALIVE_SECONDS = 3;

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

(2).保持串行執行的Executor

在AsyncTask裏面還創建了一個保證串行執行的Executor,即上面流程圖所說的靜態實例,所有的AsyncTask都是共用同一個Executor,它還用volatile來修飾,保證了多線程對它的可見性,

// 串行執行的Executor
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
// 靜態實例
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// SerialExecutor實現了Executor接口
private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    // execute方法用synchronized保證了多任務執行時是串行執行
    public synchronized void execute(final Runnable r) {
        // 競爭到鎖後,把它放到ArrayDeque裏面
        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進行任務處理
            // SERIAL_EXECUTOR只是作爲一個保證串行執行的執行器
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

(3).觸發執行SerialExecutor的execute方法

當我們在業務層調用了asyncTask.execute()的時候,就調用了AsyncTask的execute方法,然後把excute的參數賦值給mWorker(它是一個Callable),mFuture再把mWorker進行一次包裝,至於用FutureTask而不是用一個Runnable的原因,就是FutureTask能夠獲取結果,而Runnable並不能。最終觸發執行到SerialExecutor的execute,然後再把FutureTask塞到線程池THREAD_POOL_EXECUTOR中執行。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    // 由於是沒有指定AsyncTask的執行器
    // 所以是使用了上面說的sDefaultExecutor,觸發串行執行
    return executeOnExecutor(sDefaultExecutor, params);
}

// 最終在executeOnExecutor裏面處理執行的邏輯
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,mWorker是一個Callable
    mWorker.mParams = params;
    // mFuture是一個FutrueTask,它是執行mWorker的載體
    // 最終觸發2中SerialExecutor的execute方法
    exec.execute(mFuture);

    return this;
}

2.實現並行執行的原理

在串行還是執行那一部分我們知道,只要修改AsyncTask內部的執行器,就可以實現把串行執行改爲並行執行:

mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)

查看源碼可知,executeOnExecutor實際上是把默認的sDefaultExecutor替換成了THREAD_POOL_EXECUTOR,而sDefaultExecutor就是通過synchronized實現了任務的串行執行,而THREAD_POOL_EXECUTOR並沒有把execute方法改成同步,所以自然就變成了異步執行。

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

    onPreExecute();

    mWorker.mParams = params;
    // 使用了傳入的Executor執行,而不是sDefaultExecutor
    exec.execute(mFuture);

    return this;
}

3.onProgressUpdate和onPostExecute實現原理

我們都知道onProgressUpdate是進行進度更新,onPostExecute是在子線程任務執行完後回調到UI線程更新UI的方法。所以這兩個方法都是在UI線程執行的,那麼子線程和UI線程是如何進行通信的呢?

相信聰明的你一定猜到了,那就是Handler,Handler是Android裏面實現線程通信的工具,AsyncTask子線程就是通過獲取UI線程的Handler,然後把消息發送到UI線程的消息列表,從而實現onProgressUpdate和onPostExecute的回調。

(1)初始化Handler

初始化AsyncTask的時候,會生成UI線程的Handler。

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
                ? getMainHandler()
                : new Handler(callbackLooper);
}

(2)更新進度

當使用時主動調用publishProgress時,就會通過UI線程的Handler,把消息推到UI線程的消息隊列。

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

private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

(3)執行結果

在執行原理中,我有提到,一個任務就是一個FutrueTask,它的實現如下,當任務執行完了之後,就會回調到done方法,然後調用postResultIfNotInvoked(get())方法,這個方法就是把消息通過Handler分發回UI線程,然後收到消息後觸發finish方法,在finish裏面進行回調到onPostExecute。

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 void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}

/**
* 最終也是通過Handler發送事件
**/
private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

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:
                // 調用AsyncTask的finish方法
                result.mTask.finish(result.mData[0]);
                break;
       
        }
    }
}

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        // 最後回調到onPostExecute
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

4.異常處理

我們已經知道,AsyncTask執行的線程池是THREAD_POOL_EXECUTOR,它在創建時,設置了拒絕執行的策略sRunOnSerialPolicy,即當THREAD_POOL_EXECUTOR拒絕執行任務的時候,具體的處理邏輯就會分發到sRunOnSerialPolicy裏面的rejectedExecution方法,這時候AsyncTask就會獲取一個備用的線程池(如果不存在則創建出來),這個線程池核心線程數是5個,然後讓備用線程池執行這個被拒絕的任務。

private static final int BACKUP_POOL_SIZE = 5;

private static ThreadPoolExecutor sBackupExecutor;
private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue;

private static final RejectedExecutionHandler sRunOnSerialPolicy = new RejectedExecutionHandler() {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // 如果任務被拒絕執行,就會回調到這個方法裏面
        synchronized (this) {
            // 這裏會觸發一個備用的線程池sBackupExecutor進行任務處理
            if (sBackupExecutor == null) {
                sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
                sBackupExecutor = new ThreadPoolExecutor(
                        BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
                        TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
                sBackupExecutor.allowCoreThreadTimeOut(true);
            }
        }
        sBackupExecutor.execute(r);
    }
};

六、總結

通過翻閱Android Q中AsyncTask的源碼,我們知道了AsyncTask默認是串行執行任務,它裏面使用了兩個“線程”池,其中一個是僞線程池,它只是實現了Executor接口,並把execute方法用synchronized,從而實現多任務時串行執行,而另一個線程池則是真實執行任務的線程池。

此外,AsyncTask在設計上也是支持我們把任務執行順序改爲並行的,只要通過修改它的執行任務的線程池即可。

在線程切換方面,AsyncTask通過Handler來實現子線程和UI線程的通信,從而實現進度變化和UI更新。

再者,AsyncTask設置了一個備用的線程池,以便常規執行任務的線程池拒絕執行任務時,還能保證任務隊列裏面的任務正常執行。

最後,AsyncTask在Android R(11.0)中已經被標記Deprecated,在如今異步操作框架百花齊放的時代,它多多少少顯得有點落伍,但是我們也不要忘記它曾經的輝煌,一代人終將老去,但總有人正年輕,它已經很好地完成了它的使命感。

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