一、前言
相信AsyncTask對每一個開發者來說都非常熟悉,它是一個輕量級的異步任務類。同時,它也經歷了很多個版本的調整,如實現上從開始串行執行,再到並行執行,再後來又改回串行執行。AsyncTask從Android API3已經出現,它之所以能夠屹立不倒這麼多年,想必有很多值得我們學習的地方,本文將基於Android Q(10.0)的源碼對AsyncTask進行分析。
二、關於Deprecated
當我準備開始閱讀AsyncTask源碼的時候,我在AsyncTask的官方文檔發現了它在Android R(11.0)上已經被標記過時,官方更推薦開發者使用Kotlin的協程進行異步操作。
Google官方列舉了以下把它標記爲過時的原因,其實這也是AsyncTask一直以來都被詬病的地方:
- 容易導致內存泄漏
- 忘記回調
- 橫豎屏切換導致崩潰
- 不同版本的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的串行執行。具體流程圖如下所示。
結合源碼一起來看它的實現:
(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,在如今異步操作框架百花齊放的時代,它多多少少顯得有點落伍,但是我們也不要忘記它曾經的輝煌,一代人終將老去,但總有人正年輕,它已經很好地完成了它的使命感。