前言
提到Android的多線程機制,常用的有如下幾種方式:
- AsyncTask: 封裝了線程池和Handler,爲 UI 線程與工作線程之間進行快速切換提供一種便捷機制。適用於當下立即需要啓動,但是異步執行的生命週期短暫的使用場景。
- HandlerThread: 一個已經擁有了Looper的線程類,內部可以直接使用Handler。爲某些回調方法或者等待某些任務的執行設置一個專屬的線程,並提供線程任務的調度機制。
- ThreadPool: 把任務分解成不同的單元,分發到各個不同的線程上,進行同時併發處理。
- IntentService: 適合於執行由 UI 觸發的後臺 Service 任務,並可以把後臺任務執行的情況通過一定的機制反饋給 UI。
儘管Android已經設計了基本的Handler異步消息機制提供給我們進行線程間通信,不過對於頻繁得UI更新操作Handler用起來確實有點細碎,爲了更加方便我們在子線程中更新UI元素,Android從1.5版本就引入了一個AsyncTask類,使用它我們可以非常靈活方便地從子線程切換到UI線程。
我們就從AsyncTask的基本用法開始,一起分析下AsyncTask源碼,看看它是如何實現的。
使用AsyncTask
由於AsyncTask是一個抽象類,所以如果我們想使用它,就必須要創建一個子類去繼承它。在繼承時我們可以爲AsyncTask類指定三個泛型參數,這三個參數的用途如下:
- Params:在執行AsyncTask時需要傳入的參數,可用於在後臺任務中使用。
- Progress:後臺任務執行時,如果需要在界面上顯示當前的進度,則使用這裏指定的泛型作爲進度單位。
- Result:當任務執行完畢後,如果需要對結果進行返回,則使用這裏指定的泛型作爲返回值類型。
一個最簡單的自定義AsyncTask就可以寫成如下方式:
private class MyTask extends AsyncTask<Void, Void, Void> { ... }
然後我們還需要去重寫AsyncTask中的幾個方法才能完成對任務的定製。經常需要去重寫的方法有以下四個:
onPreExecute():一般會在
UI Thread
中執行。用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。doInBackground(Params…):這個方法中的所有代碼都會在子線程
Worker Thread
中運行,我們應該在這裏去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)
方法來完成。onProgressUpdate(Progress…):在
UI Thread
中執行。當在後臺任務中調用了publishProgress(Progress...)
方法後,這個方法隨後就會被調用,方法中攜帶的參數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用參數中的數值就可以對界面元素進行相應的更新。onPostExecute(Result):在
UI Thread
中執行。當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作爲參數傳遞到此方法中,可以利用返回的數據來進行一些UI操作,比如彈出Toast提醒任務執行的結果,以及關閉掉進度條對話框等。
特別說明!
onPreExecute
並不保證一定在UI線程中執行!我們稍後源碼分析時說明
一個比較完整的自定義AsyncTask就可以寫成如下方式:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
//更新進度
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
@Override
protected void onProgressUpdate(Integer... progress) {
progressDialog.setMessage("當前下載進度:" + progress[0] + "%");
}
@Override
protected void onPostExecute(Long result) {
showDialog("下載已完成!Downloaded " + result + " bytes");
}
}
然後,調用execute()
執行任務就可以了:
new DownloadFilesTask().execute(url1, url2, url3);
以上就是AsyncTask的基本用法,我們並不需要去考慮什麼異步消息處理機制,也不需要專門使用一個Handler來發送和接收消息,只需要調用一下publishProgress()
方法就可以輕鬆地從子線程切換到UI線程了。
AsyncTask源碼
首先提醒一下大家,該版本分析的代碼是Android API 21(對應的Android 5.0)的源碼,由於AsyncTask在之前幾個版本改動比較大,不過不影響我們分析原理,所以最後我儘量介紹一下區別。
AsyncTask的源碼鏈接:https://github.com/android/platform_frameworks_base/blob/master/core/java/android/os/AsyncTask.java
可以看到AsyncTask開頭定義了一些字段,如下所示:
private static final String LOG_TAG = "AsyncTask";
//CPU_COUNT爲手機中的CPU核數
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//線程池的核心線程數
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//線程池的最大線程數
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//同一時刻只允許1個線程執行
private static final int KEEP_ALIVE = 1;
//sThreadFactory用於在後面創建線程池
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
//重寫newThread方法: 爲了將新增線程的名字以"AsyncTask #"標識
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//實例化阻塞式隊列BlockingQueue,隊列中存放Runnable,容量爲128
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
//根據上面定義的參數實例化線程池
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
通過以上代碼和註釋我們可以知道,AsyncTask初始化了一些參數,並用這些參數實例化了一個線程池THREAD_POOL_EXECUTOR
,需要注意的是該線程池被定義爲public static final
,由此我們可以看出AsyncTask內部維護了一個靜態的線程池,默認情況下,AsyncTask的實際工作就是通過該THREAD_POOL_EXECUTOR
完成的。
構造函數
我們來看一看AsyncTask的構造函數:
public abstract class AsyncTask<Params, Progress, Result> {
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
//實例化mWorker,實現了Callable接口的call方法
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//在線程池的工作線程中執行doInBackground方法,執行完的結果傳遞給postResult方法
return postResult(doInBackground(mParams));
}
};
//用mWorker實例化mFuture
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 occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
...省略其他代碼...
}
首先我們看到AsyncTask是一個抽象類,所以我們不能直接使用。在構造函數上有一句註釋說:AsyncTask的構造函數需要在UI線程上調用,言外之意也就是說我們必須在主線程中new創建AsyncTask對象。
然後構造函數中實際上並沒有任何具體的邏輯會得到執行,只是初始化了兩個變量,mWorker
和mFuture
,並在初始化mFuture
的時候將mWorker
作爲參數傳入。mWorker
是一個Callable對象,mFuture
是一個FutureTask對象,這兩個變量會暫時保存在內存中,稍後纔會用到它們。
mWorker是WorkerRunnable類型的對象,WorkerRunnable是AsyncTask中的一個內部類,代碼如下所示:
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
- mWorker :上面代碼我們可以看到,mWorker其實是一個Callable類型的對象。實例化mWorker,實現了Callable接口的call方法。call方法是在線程池的某個線程中執行的,而不是運行在主線程中。在線程池的工作線程中執行doInBackground方法,執行實際的任務,並返回結果。當doInBackground執行完畢後,將執行完的結果傳遞給postResult方法。postResult方法我們後面會再講解。
- mFuture :mFuture是一個FutureTask類型的對象,用mWorker作爲參數實例化了mFuture。在這裏,其實現了FutureTask的done方法,我們之前提到,當FutureTask的任務執行完成或任務取消的時候會執行FutureTask的done方法。done方法裏面的邏輯我們稍後再講。
這裏先詳細說一下FutureTask:由於AsyncTask能夠取消任務,所以AsyncTask使用了FutureTask以及與其相關的Callable,此處對二者簡單進行一下介紹。FutureTask、Callable在Java的併發編程中是比較常見的,可以用來獲取任務執行完之後的返回值,也可以取消線程池中的某個任務。Callable是一個接口,其內部定義了call方法,在call方法內需要編寫代碼執行具體的任務,在這一點上Callable接口與Runnable接口很類似,不過不同的是Runnable的run方法沒有返回值,Callable的call方法可以指定返回值。FutureTask類同時實現了Callable接口和Runnable接口,FutureTask的構造函數中需要傳入一個Callable對象以對其進行實例化。Executor的execute方法接收一個Runnable對象,由於FutureTask實現了Runnable接口,所以可以把一個FutureTask對象傳遞給Executor的execute方法去執行。當任務執行完畢的時候會執行FutureTask的done方法,我們可以在這個方法中寫一些邏輯處理。在任務執行的過程中,我們也可以隨時調用FutureTask的cancel方法取消執行任務,任務取消後也會執行FutureTask的done方法。我們也可以通過FutureTask的get方法阻塞式地等待任務的返回值(即Callable的call方法的返回值),如果任務執行完了就立即返回執行的結果,否則就阻塞式等待call方法的完成。
構造函數我們先分析到這裏,關於mWorker這個對象裏調用doInBackground()
函數的流程我們稍後講到然後把它們串起來。
execute()方法
如果我們想要啓動某一個任務,就需要調用該任務的execute()
方法,因此現在我們來看一看它的源碼:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
//如果當前AsyncTask已經處於運行狀態,那麼就拋出異常,不再執行新的任務
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
//如果當前AsyncTask已經把之前的任務運行完成,那麼也拋出異常,不再執行新的任務
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;
//Executor的execute方法接收Runnable參數,由於mFuture是FutureTask的實例,
//且FutureTask同時實現了Callable和Runnable接口,所以此處可以讓exec執行mFuture
exec.execute(mFuture);
return this;
}
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
可以看到execute()
方法調用了executeOnExecutor()
方法。
在executeOnExecutor()
方法中,我們終於看到它調用了onPreExecute()
方法,因此證明了onPreExecute()方法會第一個得到執行。
下面對以上代碼進行一下說明:
- 一個AsyncTask實例執行執行一次任務,當第二次執行任務時就會拋出異常。executeOnExecutor方法一開始就檢查AsyncTask的狀態是不是PENDING,只有PENDING狀態才往下執行,如果是其他狀態表明現在正在執行另一個已有的任務或者已經執行完成了一個任務,這種情況下都會拋出異常。
- 如果開始是PENDING狀態,那麼就說明該AsyncTask還沒執行過任何任務,代碼可以繼續執行,然後將狀態設置爲RUNNING,表示開始執行任務。
- 在真正執行任務前,先調用onPreExecute方法。由於executeOnExecutor方法應該運行在主線程上,所以此處的onPreExecute方法也會運行在主線程上,可以在該方法中做一些UI上的處理操作。
- Executor的execute方法接收Runnable參數,由於mFuture是FutureTask的實例,且FutureTask同時實現了Callable和Runnable接口,所以此處可以讓exec通過execute方法在執行mFuture。在執行了exec.execute(mFuture)之後,後面會在exec的工作線程中執行mWorker的call方法,我們之前在介紹mWorker的實例化的時候也介紹了call方法內部的執行過程,會首先在工作線程中執行doInBackground方法,並返回結果,然後將結果傳遞給postResult方法。
最後調用exec.execute(mFuture);
去執行真正的任務,此處exec對象就是sDefaultExecutor
,可以看到其實是個SerialExecutor
對象,源碼如下所示:
private static class SerialExecutor implements Executor {
//mTasks是一個維護Runnable的雙端隊列,ArrayDeque沒有容量限制,其容量可自增長
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
//execute方法會傳入一個Runnable類型的變量r
//然後我們會實例化一個Runnable類型的匿名內部類以對r進行封裝,
//通過隊列的offer方法將封裝後的Runnable添加到隊尾
mTasks.offer(new Runnable() {
public void run() {
try {
//此處r的run方法是在線程池中執行的
r.run();
} finally {
//當前任務執行完畢後,通過調用scheduleNext方法執行下一個Runnable任務
scheduleNext();
}
}
});
//只有當前沒有執行任何任務時,纔會立即執行scheduleNext方法
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//通過mTasks的poll方法進行出隊操作,刪除並返回隊頭的Runnable,
//將返回的Runnable賦值給mActive,並將其作爲參數傳遞給THREAD_POOL_EXECUTOR的execute方法進行執行
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
SerialExecutor實現了Executor接口中的execute方法,該類用於串行執行任務,即一個接一個地執行任務,而不是並行執行任務。
通過以上代碼和註釋我們可以知道:
SerialExecutor實現了Executor接口中的execute方法,該類用於串行執行任務,即一個接一個地執行任務,而不是並行執行任務。
SerialExecutor內部維護了一個存放Runnable的雙端隊列mTasks。當執行SerialExecutor的execute方法時,會傳入一個Runnable變量r,但是mTasks並不直接存儲r,而是又新new了一個匿名Runnable對象,其內部會調用r,這樣就對r進行了封裝,將該封裝後的Runnable對象通過隊列的offer方法入隊,添加到mTasks的隊尾。
SerialExecutor內部通過mActive存儲着當前正在執行的任務Runnable。當執行SerialExecutor的execute方法時,首先會向mTasks的隊尾添加進一個Runnable。然後判斷如果mActive爲null,即當前沒有任務Runnable正在運行,那麼就會執行scheduleNext()方法。當執行scheduleNext方法的時候,會首先從mTasks中通過poll方法出隊,刪除並返回隊頭的Runnable,將返回的Runnable賦值給mActive,如果不爲空,那麼就讓將其作爲參數傳遞給THREAD_POOL_EXECUTOR的execute方法進行執行。由此,我們可以看出SerialExecutor實際上是通過之前定義的線程池
THREAD_POOL_EXECUTOR
進行實際的處理的。當將mTasks中的Runnable作爲參數傳遞給THREAD_POOL_EXECUTOR執行execute方法時,會在線程池的工作線程中執行匿名內部類Runnable中的try-finally代碼段,即先在工作線程中執行r.run()方法去執行任務,無論任務r正常完成還是拋出異常,都會在finally中執行scheduleNext方法,用於執行mTasks中的下一個任務。從而在此處我們可以看出SerialExecutor是一個接一個執行任務,是串行執行任務,而不是並行執行。
我們看SerialExecutor最終執行的是r.run()
,那這裏的r是什麼呢?就是execute方法中的exec.execute(mFuture)
中的參數mFuture。也就是最終執行了mFuture這個FutureTask對象的run()方法,我們進入看看FutureTask類中的run()方法:
//FutureTask的構造函數
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
//此處的callable就是接收的mWorker對象
this.callable = callable;
this.state = NEW;
}
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//核心是調用了callable(也就是mWorker)的call方法
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
可以看到核心代碼是調用了callable對象(也就是mWorker)的call方法。所以我們回頭看看構造函數中的mWorker對象。
執行任務 - 調用doInBackground()
我們前面知道,Executor的execute方法接收Runnable參數,由於mFuture是FutureTask的實例,且FutureTask同時實現了Callable和Runnable接口,所以此處可以讓exec通過execute方法在執行mFuture。在執行了exec.execute(mFuture)
之後,後面會在exec的工作線程中執行mWorker的call方法,我們之前在構造函數中介紹mWorker的實例化的時候也介紹了call方法內部的執行過程,會首先在工作線程中執行doInBackground方法,並返回結果,然後將結果傳遞給postResult方法。
我們回過頭看看構造函數中mWorker這個任務對象,在構造函數中的mWorker定義如下:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
我們看最後這句postResult(doInBackground(mParams));
,它會調用我們的doInBackground()函數執行任務,並把結果發送給postResult()
方法,我們跟進去看:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private static final InternalHandler sHandler = new InternalHandler();
它使用sHandler
對象發出了一條消息,InternalHandler創建一個Message Code爲MESSAGE_POST_RESULT的Message,此處還將doInBackground
返回的result通過new AsyncTaskResult<Result>(this, result)
封裝成了AsyncTaskResult,將其作爲message的obj屬性。
AsyncTaskResult是AsyncTask的一個內部類,其代碼如下所示:
private static class AsyncTaskResult<Data> {
//mTask表示當前AsyncTaskResult是哪個AsyncTask的結果
final AsyncTask mTask;
//mData表示其存儲的數據
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
在構建了message對象後,通過message.sendToTarget()
將該message發送給sHandler
,之後sHandler
的handleMessage方法會接收並處理該message,這個sHandler
對象是InternalHandler類的一個實例,InternalHandler的源碼如下所示:
private static class InternalHandler extends Handler {
@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;
}
}
}
msg.obj是AsyncTaskResult類型,result.mTask表示當前AsyncTaskResult所綁定的AsyncTask。result.mData[0]表示的是doInBackground所返回的處理結果。將該結果傳遞給AsyncTask的finish方法,finish代碼如下所示:
private void finish(Result result) {
if (isCancelled()) {
//如果任務被取消了,那麼執行onCancelled方法
onCancelled(result);
} else {
//將結果發傳遞給onPostExecute方法
onPostExecute(result);
}
//最後將AsyncTask的狀態設置爲完成狀態
mStatus = Status.FINISHED;
}
finish方法內部會首先判斷AsyncTask是否被取消了,如果被取消了執行onCancelled(result),否則執行onPostExecute(result)方法。需要注意的是InternalHandler是指向主線程的,所以其handleMessage方法是在主線程中執行的,從而此處的finish方法也是在主線程中執行的,進而onPostExecute也是在主線程中執行的。
我們知道,在doInBackground方法中是在工作線程中執行比較耗時的操作,這個操作時間可能比較長,而我們的任務有可能分成多個部分,每當我完成其中的一部分任務時,我們可以在doInBackground中多次調用AsyncTask的publishProgress方法,將階段性數據發佈出去。
publishProgress方法代碼如下所示:
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
可以看到最後發送了一條MESSAGE_POST_PROGRESS
的Message給sHandler,到sHandler的代碼中,我們能看到它調用了onProgressUpdate()
這個方法,也就是我們使用示例當中的進度條更新函數。
最後,AsyncTask無論任務完成還是取消任務,FutureTask都會執行done方法,如下所示:
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
//任務執行完畢或取消任務都會執行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);
}
}
};
無論任務正常執行完成還是任務取消,都會執行postResultIfNotInvoked方法。postResultIfNotInvoked代碼如下所示:
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
//只有mWorker的call沒有被調用纔會執行postResult方法
postResult(result);
}
}
如果AsyncTask正常執行完成的時候,call方法都執行完了,mTaskInvoked設置爲true,並且在call方法中最後執行了postResult方法,然後進入mFuture的done方法,然後進入postResultIfNotInvoked方法,由於mTaskInvoked已經執行,所以不會執行再執行postResult方法。
如果在調用了AsyncTask的execute方法後立馬就執行了AsyncTask的cancel方法(實際執行mFuture的cancel方法),那麼會執行done方法,且捕獲到CancellationException異常,從而執行語句postResultIfNotInvoked(null)
,由於此時還沒有來得及執行mWorker的call方法,所以mTaskInvoked還未false,這樣就可以把null傳遞給postResult方法。
到這裏,AsyncTask中的細節基本上就分析完了。
注意事項
在Google官方文檔裏有這麼一段:
Threading rules
There are a few threading rules that must be followed for this class to work properly:
- The AsyncTask class must be loaded on the UI thread. This is done automatically as of
JELLY_BEAN
.- The task instance must be created on the UI thread.
execute(Params...)
must be invoked on the UI thread.- Do not call
onPreExecute()
,onPostExecute(Result)
,doInBackground(Params...)
,onProgressUpdate(Progress...)
manually.- The task can be executed only once (an exception will be thrown if a second execution is attempted.
翻譯過來就是:
- AsyncTask必須在UI主線程中創建(new);
execute(Params...)
函數必須在UI線程中調用;- 不要手動調用
onPreExecute()
,onPostExecute(Result)
,doInBackground(Params...)
,onProgressUpdate(Progress...)
這些方法。 - 每個AsyncTask任務只能被執行一次;
大家注意到了嗎,AsyncTask必須在UI主線程中創建(new),execute(Params...)
函數必須在UI線程中調用。也就是說這個要求Google並沒有在framework層實現強制約束,而是給了口頭上的一種編碼約定(結合源碼我們也能看到源碼中也沒有這樣的機制保證)。這也就可能會引發我們開頭那個問題:
onPreExecute
並不保證一定在UI線程中執行!而是由execute(Params...)
函數在哪個線程中調用決定的!
比如stackoverflow上的這個問題:Android: AsyncTask onPreExecute() method is NOT executed in UI thread - stackoverflow。就是在子線程中調用了execute(Params...)
函數。所以爲了避免這樣的問題,我們一定要遵守上面那幾條官方約定。
一些版本變化
在Android 3.0之前是並沒有SerialExecutor這個類的,那個時候是直接在AsyncTask中構建了一個sExecutor常量,並對線程池總大小,同一時刻能夠運行的線程數做了規定,比如Android Froyo 2.2.3版本的源碼如下所示:
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
//線程池大小
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 10;
private static final BlockingQueue<Runnable> sWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
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());
}
};
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;
private static final int MESSAGE_POST_CANCEL = 0x3;
private static final InternalHandler sHandler = new InternalHandler();
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
private volatile Status mStatus = Status.PENDING;
...省略其他代碼...
}
可以看到,這裏規定同一時刻能夠運行的線程數爲5個,線程池總大小爲128。也就是說當我們啓動了10個任務時,只有5個任務能夠立刻執行,另外的5個任務則需要等待,當有一個任務執行完畢後,第6個任務纔會啓動,以此類推。而線程池中最大能存放的線程數是128個,當我們嘗試去添加第129個任務時,程序就會崩潰。
而到了Android Gingerbread 2.3.6版本,把同一時刻的5個併發線程改成了同一時刻只有一個線程的串行執行,代碼如下:
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
//注意這裏
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> sWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
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());
}
};
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;
private static final int MESSAGE_POST_CANCEL = 0x3;
private static final InternalHandler sHandler = new InternalHandler();
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
private volatile Status mStatus = Status.PENDING;
...省略其他代碼...
}
總結
AsyncTask的底層其實是對Thread、Handler、Message的封裝,智能的應用了Handler。
因爲AsyncTask裏面的內部handler和Executor都是靜態變量,所以他們控制着所有的子類。
如果不想使用默認的線程池,可以使用
executeOnExecutor()
函數自由地進行配置而不是execute()
。因爲用系統默認的線程池因爲串行執行可能需要等待(SerialExecutor)。自己使用自定義線程池方式如下:
Executor exec = new ThreadPoolExecutor(15, 200, 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
new DownloadTask().executeOnExecutor(exec);
AsyncTask適合處理短時間的操作。長時間的操作,比如下載一個很大的視頻,這就需要你使用自己的線程來下載,不管是斷點下載還是其它的。從google官方文檔你也可以看到,AsyncTasks should ideally be used for short operations (a few seconds at the most.)
不要隨意使用AsyncTask,除非你必須要與UI線程交互。默認情況下使用Thread即可,要注意需要將線程優先級調低。
Android3.0之前,異步任務是併發執行的,即幾個任務同時切換執行,3.0之後,異步任務改成了順序執行,即任務隊列中的任務要一個個執行(並非按順序),一個執行不完,不能執行另一個,即順序執行,他是默認的執行方式execue()
方法,其默認執行的方法是:executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)
,如果要併發執行,需要執行AsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
,且爲了防止系統的任務繁重,只在線程池中維護了5個線程,也就是,每次最多跑5個任務(類似於迅雷下載)。如果需要併發更多的任務,需要自定義線程池了。所以異步任務只適合處理一些輕量級的並隨時修改UI的異步線程,如果遇到繁重的任務,最好自己新建一個Thread並用handler和looper機制處理。