看完這篇,再也不怕被問到 AsyncTask 的原理了
阿新 • • 發佈:2020-07-27
本文很多資料基於[Google Developer](https://developer.android.google.cn/reference/android/os/AsyncTask?hl=en)官方對AsyncTask的最新介紹。
### AsyncTask 是什麼
```
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,例如 **Executor**,**ThreadPoolExecutor**和 **FutureTask**。
```
!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的引數和重要方法
定義子類時需設定傳入的三大引數型別,如果某型別未用到的將它設為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方法就執行非同步任務了。
```java
//最簡單的AsyncTask實現方式
public class DownloadTask extends AsyncTask {
@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的程式碼入手:
```java
new DownloadTask().execute("");
```
進入execute方法檢視:
```java
@MainThread
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
```
先看下`sDefaultExecutor`這個屬性是什麼名堂:
```java
//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 mTasks = new ArrayDeque();
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(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
```
可以看到的是`sDefaultExecutor`,是一個**控制任務排隊的執行緒池**,被呼叫`execute`時會將新的Runnable壓入任務佇列,如果Runnable mActive == null的話會取出隊頭的Runnable執行,而每當一個任務結束後都會執行任務佇列中隊頭Runnable。
這樣做的目的是:保證在不同情況,只能有一個任務可以被執行,SerialExecutor做出了單一執行緒池的效果。每當一個任務執行完畢後,下一個任務才會得到執行,假如有這麼一種情況,主執行緒快速地啟動了很多AsyncTask任務,同一時刻只會有一個執行緒正在執行,其餘的均處於等待狀態。
再來看看`executeOnExecutor`這個方法又有什麼名堂:
```java
@MainThread
public final AsyncTask 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任務
exec.execute(mFuture);
return this;
}
//Callable的子類,泛型型別為
private final WorkerRunnable mWorker;
private final FutureTask mFuture;
```
`executeOnExecutor` 執行方法,會先檢查**是否運行了這個任務或者已結束**,由於AsyncTask任務規定每個任務只能執行一次,不符合就會丟擲異常。接著開始呼叫 `onPreExecute` 開始預執行,然後給mWorker賦值引數,執行
mFuture蘊含的任務。到這裡好像就沒了?非也非也,還有很關鍵的AsyncTask的建構函式