AsyncTask
AsyncTask可以非常方便、簡單的在UI線程中使用,它幫助我們在不使用Thread和Handler的情況下,在後臺執行一個後臺(異步)任務,並且把執行結果通知到UI線程中。也就是說,使用AsyncTask可以極大的簡化我們進行後臺任務的操作,使用它,我們不必關心工作線程是如何啓動的,也不必關心,工作線程和UI線程之間的通信問題,這些都被AsyncTask通過內部封裝實現了,我們只需要按接口規範使用它即可。
AsyncTask適用於短時的後臺(異步)任務(最多幾秒鐘),它並不適合時間較長的後臺任務,如果我們想在後臺線程執行長時間的異步任務,可以使用java.util.concurrent併發工具包中的Executor、ThreadPoolExecutor和FutureTask等。
AsyncTask的聲明和執行
AsyncTask執行後臺任務,是由後臺線程執行具體的任務,最後把結果發佈到UI線程。
一個後臺任務的聲明由3種泛型類型:Params、Progress和Result,以及4個過程:onPreExecute、doInBackground、onProgressUpdate和onPostExecute構成。
我們來看使用AsyncTask有哪些要求?
- 必須創建AsyncTask的子類,以它的子類形式來使用。
- 必須在子類中至少實現doInBackground()回調方法,通常我們還會實現onPostExecute()方法。
- AsyncTask類必須在UI線程中被加載(Android 4.1之後自動執行)。
- AsyncTask實例對象,必須在UI線程中創建。
- 必須在UI線程中調用execute()來運行任務。
3種泛型
Params:doInBackground方法的參數類型,它會在任務執行之前發送給後臺任務。
Progress:onProgressUpdate方法的參數類型,它是在任務執行期間發佈的進度類型。
Result:onPostExecute方法的參數類型,它是後臺任務執行結束後的結果類型。
示例:
private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {}
示例中的Params、Progress和Result分別是String、Integer和Long。
4個步驟
AsyncTask後臺任務執行過程,將會經歷4個步驟:
onPreExecute():UI線程中執行。它發生在後臺任務開始執行之前,我們可以通過它來進行任務的配置,比如開始展示進度條等。
doInBackground(Params…):後臺線程中執行。它在onPreExecute完成之後立即執行,它會執行較長時間的後臺運算任務。運算結果必須由該步驟return,發佈給onPostExecute方法。我們也可以在該過程中,調用一次或多次publishProgress(Progress…)方法來發布執行進度信息,進度信息最終會發布到UI線程中,成爲onProgressUpdate(Progress…)的參數。
onProgressUpdate(Progress…):UI線程中執行。它是在調用publishProgress(Progress…)之後執行的,它通常用來展示後臺任務的執行進度。
onPostExecute(Result):UI線程中執行。它是在後臺任務執行結束之後調用的,由doInBackground(Params…)方法return的結果作爲參數。
任務的取消
AsyncTask後臺任務執行中的任意時刻,都可以調用cancel(boolean)來進行取消操作。
cancel方法需要關注以下幾點:
- 執行cancel方法之後,調用isCancelled()將返回true。
- 執行cancel方法之後,onCancelled(java.lang.Object)方法將會在doInBackground(java.lang.Object[]) return之後執行(代替了onPostExecute方法)。
- 我們應該儘可能及時的檢測到後臺任務的取消。可以在doInBackground方法中,使用isCancelled()來定時執行檢測工作,一旦發現任務取消,立即取消現有操作,執行return。
AsyncTask演示Demo
示例代碼,創建了一個DownloadFilesTask類,繼承於AsyncTask,用於執行後臺任務。
private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {//3個泛型分別爲:String, Integer, Long
protected Long doInBackground(String... urls) {//參數爲字符串列表
int count = urls.length;
long time = 0;
for (int i = 0; i < count; i++) {
try {
Thread.sleep(1000);//示例任務,執行線程休眠1秒,模擬耗時任務
} catch (InterruptedException e) {
e.printStackTrace();
}
time += 1000;
publishProgress((int) (((i + 1) / (float) count) * 100));//更新進度
if (isCancelled()) break;//檢測是否執行過程中取消了任務
}
return time;
}
protected void onProgressUpdate(Integer... progress) { //更新進度。這裏的參數是Integer對象
mTextView.setText("進度:" + progress[0]);
}
protected void onPostExecute(Long result) { //執行結果回調
mTextView.setText("time : " + result + " 毫秒");
}
}
異步任務準備好了,我們創建一個DownloadFilesTask類的實例,調用execute()方法即可執行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new DownloadFilesTask().execute("url1", "url2", "url3", "url3");
}
例子很簡單,但是也完整的展示了AsyncTask的基本用法。
接下來我們來分析AsyncTask的實現原理。
AsyncTask源碼解析
我們接下來分析AsyncTask的源碼,看下它的實現原理。
在Demo中,我們調用AsyncTask的實例對象的execute方法來實現調用的,那麼我們就從execute方法作爲入口點來分析。
AsyncTask的execute方法:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
這裏調用了executeOnExecutor方法,參數sDefaultExecutor是一個線程池,作爲默認線程池。
executeOnExecutor方法:
@MainThread
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();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
邏輯解析
- 該方法在UI線程中執行。
- 屬性mStatus用來判斷當前任務的狀態,當任務正在運行或已經結束了,會拋出Error。
- 設置mStatus的狀態爲Status.RUNNING。
- 調用onPreExecute()方法,該方法通常在使用AsyncTask時,需要實現。
- 把參數params保存在mWorker.mParams中。
- 調用線程池,執行mFuture。
線程池執行的部分我們後面來分析,先來看這裏有2個重要的屬性mWorker和mFuture,它們起到了非常重要的作用,我們來看。
mWorker、mFuture
屬性mWorker和mFuture是在AsyncTask的構造方法中初始化的,它們是後臺線程控制邏輯的核心所在。
我們來看AsyncTask的構造方法:
public AsyncTask() {//這裏是在UI線程中執行的
this((Looper) null);
}
public AsyncTask(@Nullable Handler handler) {
this(handler != null ? handler.getLooper() : null);
}
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {//這裏是在後臺線程中執行的
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//這裏設置了線程的優先級爲THREAD_PRIORITY_BACKGROUND
//noinspection unchecked
result = doInBackground(mParams);//這裏調用了doInBackground方法
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result); //post結果給UI線程
}
return result;//把後臺線程處理結果返回
}
};
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 static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
邏輯解析:
- 首先初始化Handler對象,因爲AsyncTask涉及到後臺線程和UI線程之間的通信,它內部實現使用了Handler通信機制。
- mWorker屬性賦值,這裏的mWorker是一個WorkerRunnable對象,它是一個抽象類,繼承自Callable接口,用於執行異步任務並獲取線程執行結果。WorkerRunnable對象的call()方法是在後臺線程中執行的。這裏可以看到在call()方法中,調用了doInBackground方法。
- call()方法中設置了線程的優先級爲THREAD_PRIORITY_BACKGROUND(後臺線程優先級)。
- mFuture屬性賦值,這裏的mFuture是一個FutureTask對象,FutureTask可以用於線程任務的控制等邏輯,異步任務執行完成後,會回調done()方法。
- done()方法中,調用postResultIfNotInvoked方法,把任務結果返回給UI線程,這裏執行的是非正常情況下的返回,正常執行結束會在mWorker的call方法中通過調用postResult(result)將結果返回給UI線程。
Handler處理
我們來看,後臺任務處理完成後,會在WorkerRunnable的call方法中,調用postResult(result)將結果返回給UI線程,但如果後臺任務執行之前被終止,則會把結果傳遞給postResultIfNotInvoked方法。
我們來看這兩個方法:
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) { //同步屬性的布爾值,表示後臺任務是否已經開始執行
postResult(result);//這裏,後臺任務並沒有執行
}
}
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
postResult方法,創建一個message對象,把執行結果封裝成一個AsyncTaskResult對象,通過Handler機制,將後臺執行結果傳遞給UI線程(這裏只考慮UI線程一種情況)。
串行執行的線程池實現
到了這裏,Asynctask的執行部分、結果處理以及跨線程通信部分,都已經分析完成了,接下來我們來看後臺線程是如何執行的,任務隊列是如何實現的。
我們從上文知道,AsyncTask的execute方法中,直接使用了一個默認線程池sDefaultExecutor來負責後臺線程的創建及執行。
回顧一下:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
sDefaultExecutor的單線程隊列的實現:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new 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 {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
我們可以看到,sDefaultExecutor是SerialExecutor的一個實例對象。
SerialExecutor的屬性:
mTasks:是一個先進先出的隊列,這裏可以理解爲容量無限的一個先進先出的隊列(當然不可能無限,不過正常情況下我們是達不到它的最大容量的)。
mActive:表示當前正在執行的任務,它是一個Runnable對象。
後臺任務隊列的執行過程:
- 當第一次有任務進來時,先把任務添加到mTasks隊列中,這時mActive == null,執行scheduleNext方法。
- scheduleNext方法中,從隊列中取出隊列頭部的Runnale對象,並賦給mActive,最後把它交由THREAD_POOL_EXECUTOR線程池執行。
- 當第一個任務執行結束後,緊接着會調用scheduleNext方法執行下一個任務,以此類推……
串行執行的祕密:這裏其實就實現了一個單線程的任務隊列,所有AnsyncTask的任務,都會順序的,單線程執行。它的實現原理其實就是,準備了一個可以擴容的先進先出的任務隊列mTasks,所有的後臺任務執行時,先進入隊列中,然後調用scheduleNext執行,當前任務結束後,繼續執行下一個任務,這樣也就實現了串行的任務處理。
線程池THREAD_POOL_EXECUTOR
這裏的線程池其實沒有發揮線程池的作用,因爲默認情況下,AsyncTask只會執行單個任務調用,因爲在SerialExecutor中已經實現了任務的隊列管理了,SerialExecutor會單個順序的執行線程任務的調用。
THREAD_POOL_EXECUTOR的定義:
public static final Executor THREAD_POOL_EXECUTOR;
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int BACKUP_POOL_SIZE = 5;
private static final int KEEP_ALIVE_SECONDS = 3;
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;
}
THREAD_POOL_EXECUTOR其實是一個可併發的線程池,它的核心線程數是1,最大線程數是20。(注意,不同Android版本中的實現會有不同)
線程的創建:
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());
}
};
線程池是通過ThreadFactory對象來進行線程創建的,並且會爲線程命名爲"AsyncTask #" + mCount.getAndIncrement()。
AsyncTask併發的實現
在Android 3.0時,AsyncTask的任務執行改回了單一線程中順序執行,那麼我們如果想用AsyncTask實現併發任務,可以做到嗎?答案是肯定的,我們來看如何實現。
我們來看AsyncTask的executeOnExecutor方法:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
……
exec.execute(mFuture);
return this;
}
源碼解析:
executeOnExecutor方法,可以接受一個線程池的參數,來讓使用者實現自定義線程池的目的。
我們可以自定義一個併發的線程池,通過調用executeOnExecutor方法,替換掉AsyncTask的默認線程池,即可實現併發處理。
注意:雖然這裏可以實現線程池的併發處理,但是並不建議這麼做,如果想要實現併發的後臺任務,推薦使用java.util.concurrent併發工具包中的Executor、ThreadPoolExecutor和FutureTask等。
AsyncTask的最佳實踐
到了這裏,我們已經深入瞭解了AsyncTask的使用及實現原理了,那麼我們在開發時,有哪些是需要注意的呢?
AsyncTask使用時的一些要求:
- AsyncTask類必須在UI線程中被加載(Android 4.1之後自動執行)。
- AsyncTask實例對象,必須在UI線程中創建。
- 必須在UI線程中調用execute()來運行任務。
- 必須創建AsyncTask的子類,以它的子類形式來使用。
- 必須在子類中至少實現doInBackground()回調方法,通常我們還會實現onPostExecute()方法。
- 不要手動調用onPreExecute(), onPostExecute(Result), doInBackground(Params…), onProgressUpdate(Progress…)方法。
- AsyncTask後臺任務,只能執行一次,否則會拋出異常。
- 整個進程中的AsyncTask線程池及任務隊列都是同一個(自定義的除外),大量的調用AsyncTask執行後臺任務會造成任務隊列整體執行時間的變長,導致任務執行的時間不可控。
- AsyncTask的默認線程優先級是Process.THREAD_PRIORITY_BACKGROUND(後臺線程級別),優先級較低,分配的CPU資源會較少,不適合執行優先級較高的任務。
關於執行順序及Android歷史版本中的變更
AsyncTask後臺任務的執行順序是不可靠的,因爲它在Android歷史版本中經歷了多次變更。在剛剛引入AsyncTask時,AsyncTask後臺任務的執行,是在單一線程中順序執行的;但是在Android 1.6中,AsyncTask的任務執行改成了可多線程併發執行;在Android 3.0時,AsyncTask的任務執行又改回了單一線程中順序執行,以避免多線程造成的併發等問題。
如果我們想通過AsyncTask來併發執行後臺任務,可以使用executeOnExecutor(java.util.concurrent.Executor, java.lang.Object[])方法,傳遞一個線程池來執行。
注意:AsyncTask在Android R(Android 11)中被標記爲棄用狀態,建議使用java.util.concurrent併發工具包來代替。