AsyncTask 是Android特有的一個輕量級異步抽象類,在類中通過doInBackground()
在子線程執行耗時操作,執行完畢在主線程調用onPostExecute()
。
前言
衆所周知,Android視圖的繪製、監聽、事件等都UI線程(主線程,Main Thread)執行,如果執行訪問網絡請求、數據庫等耗時操作,可能會阻塞主線程,若阻塞時間超過5秒,可能會引起系統的ANR異常(Application Not Responding)。所以耗時操作需要放在子線程(也稱爲Worker Thread)執行,這樣就避免了主線程的阻塞,然而在線程是不能有更新UI的操作的,比如在子線程調用TextView.setText()
就會引發以下錯誤:
Only the original thread that created a view hierarchy can touch its views.
故而可以用 “Handle + Thread”的方式,子線程執行耗時操作,通過Handler通知主線程更新UI。但是這個方式略微麻煩,於是便引入了AsyncTask。
AsyncTask特點
AsyncTask
AsyncTask簡單使用
public void request() {
AsyncTask<String, Integer, Integer> task = new AsyncTask<String, Integer, Integer>() {
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.i("AsyncTask", "準備執行後臺任務");
}
@Override
protected Integer doInBackground(String... params) {
String url_1 = params[0];
doRequest(url_1);
publishProgress(50);
String url_2 = params[1];
doRequest(url_2);
publishProgress(100);
return 1;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.i("AsyncTask", "當前進度" + values[0] + "%");
}
@Override
protected void onPostExecute(Integer ret) {
super.onPostExecute(ret);
Log.i("AsyncTask", "執行完畢,執行結果" + ret);
}
};
String url_1 = "https://api.github.com/users/smuyyh";
String url_2 = "https://api.github.com/users/smuyyh/followers";
task.execute(url_1, url_2); // 開啓後臺任務
}
代碼較爲簡單,就不做過多的解釋了。後面着重介紹AsyncTask的內部實現機制。
原理分析
這一節將從源碼的角度來分析一下AsyncTask。以下源碼基於Android-23.
- 開啓後臺任務之前,首先需要創建AsyncTask的實例,所以還得從構造函數說起。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(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);
}
}
};
}
在構造函數中,初始化了兩個變量,分別是mWorker與mFuture,mFuture創建的時候傳入了mWorker參數,而mWorker本身是一個Callable對象。那麼,mFutrue是個什麼東西呢?
mFuture是一個FutureTask對象,FutureTask實際上是一個任務的操作類,它並不啓動新線程,並且只負責任務調度。任務的具體實現是構造FutureTask時提供的,實現自Callable接口,也就是剛纔的mWorker。
- AsyncTask對象穿件完畢之後調用
execute(Params...)
執行,跟進看看
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
只有一句話,可知是調用executeOnExecutor進行執行。這裏就有個疑問了,sDefaultExecutor是個什麼東西?在說這個之前,需要明確一下一下三個事:
1、Android3.0之前部分代碼
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 ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
2、在Android3.0之前,AsyncTask執行中最終觸發的是把任務交給線池THREAD_POOL_EXECUTOR來執行,提交的任務並行的在線程池中運行,但這些規則在3.0之後發生了變化,3.0之後提交的任務是默認串行運行的,執行完一個任務才執行下一個!
3、在Android3.0以前線程池裏核心線程有5個,任務隊列的任務數最大不能超過128個,線程池裏的線程都是並行運行的,在3.0以後,直接調用execute(params)觸發的是sDefaultExecutor的execute(runnable)方法,而不是原來的THREAD_POOL_EXECUTOR。在Android4.4以後,線程池大小等於 cpu核心數 + 1,最大值爲cpu核心數 * 2 + 1。這些變化大家可以自行對比一下。
跟進源碼不難發現,sDefaultExecutor實際上是指向SerialExecutor的一個實例,從名字上看是一個順序執行的executor,並且它在AsyncTask中是以常量的形式存在的,因此在整個應用程序中的所有AsyncTask實例都會共用同一個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);
}
}
}
SerialExecutor是使用ArrayDeque這個隊列來管理Runnable對象的。當Executor提交一個任務,執行一次execute()
,在這裏向mTasks隊列添加一個Runnable對象。初次添加任務時mActive爲null,故接下來會執行scheduleNext()
,將mActive指向剛剛添加的runbale,並提交到THREAD_POOL_EXECUTOR
中執行。
當AsyncTask不斷提交任務時,那麼此時mActive不爲空了,所以後續添加的任務能得到執行的唯一條件,就是前一個任務執行完畢,也就是r.run()
。所以這就保證了SerialExecutor的順序執行。這個地方其實也是一個坑,初學者很容易在這裏踩坑,同時提交多個任務,卻無法同步執行。
如果想讓其並行執行怎麼辦?AsyncTask提供了一下兩種方式:
task.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
task.executeOnExecutor(executor, params); //可以自己指定線程池
- 繼續跟進
executeOnExecutor(Executor exec, Params... params)
代碼
@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;
}
不難看出,最後是調用Executor的execute(Runnable command)
方法啓動mFuture。默認情況下,sDefaultExecutor就是SerialExecutor類,所以爲串行執行。當然用戶也可以提供自己的Executor來改變AsyncTask的運行方式。最後在THREAD_POOL_EXECUTOR
真正啓動任務執行的Executor。
上面已經提到,Execute執行是調用Runnable的run()方法,也就是mFuture的run方法,繼續跟進代碼
public void run() {
if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
從第10行代碼可發現,最後是調用callable的call()方法。那麼這個callable是什麼呢?就是初始化mFuture傳入的mWorker對象。在前面的構造函數那邊可以發現call()方法,我們單獨分析一下這個方法
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
看了這麼久,終於發現了doInBackground()
,深深鬆了一口氣。執行完之後得到的結果,傳給postResult(result)
。繼續跟進
private Result postResult(Result result) {
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
可以發現,最後是通過Handler的方式,把消息發送出去,消息中攜帶了MESSAGE_POST_RESULT
常量和一個表示任務執行結果的AsyncTaskResult對象。而getHandler()
返回的sHandler是一個InternalHandler
對象,InternalHandler源碼如下所示:
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@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;
}
}
}
這裏對消息的類型進行了判斷,如果是MESSAGE_POST_RESULT,就執行finish()
,如果是MESSAGE_POST_PROGRESS,就onProgressUpdate()
方法。那麼什麼時候觸發如果是MESSAGE_POST_PROGRESS消息呢?就是在publishProgress()方法調用的時候,publishProgress()方法用finial標記,說明子類不能重寫他,不過可以手動調用,通知進度更新,這就表明了publishProgress可在子線程執行。
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
然後看一下finish()的代碼。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
不難發現,如果當前任務被取消掉了,就會調用onCancelled()方法,如果沒有被取消,則調用onPostExecute()方法,這樣當前任務的執行就全部結束了。並且,當你再次調用execute的時候,這個時候mStatus的狀態爲Status.FINISHED,表示已經執行過了,那麼此時就會拋異常,這也就是爲什麼一個AsyncTask對象只能執行一次的原因。
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)");
}
到這裏,就非常清晰了吧。徹底的瞭解了AsyncTask內部實現的邏輯。
總結
可以看出,在使用AsyncTask的過程中,有許多需要注意的地方。
- 由於Handler需要和主線程交互,而Handler又是內置於AsnycTask中的,所以,AsyncTask的創建必須在主線程,execute的執行也應該在主線程。
- AsyncTask的doInBackground(Params… Params)方法運行在子線程中,其他方法運行在主線程中,可以操作UI組件。
- 儘量不要手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute 這些方法,避免發生不可預知的問題。
- 一個任務AsyncTask任務只能被執行一次。否則會拋IllegalStateException異常
- 在doInBackground()中要檢查isCancelled()的返回值,如果你的異步任務是可以取消的話。cancel()僅僅是給AsyncTask對象設置了一個標識位,雖然運行中可以隨時調用cancel(boolean mayInterruptIfRunning)方法取消任務,如果成功調用isCancelled()會返回true,並且不會執行 onPostExecute() 方法了,取而代之的是調用 onCancelled() 方法。但是!!!值得注意的一點是,如果這個任務在執行之後調用cancel()方法是不會真正的把任務結束的,而是繼續執行,只不過改變的是執行之後的回調方法是 onPostExecute還是onCancelled。可以在doInBackground裏面去判斷isCancle,如果取消了,那就直接return result; 當然,這種方式也並非非常完美。
- Asynctask的生命週期和它所在的activity的生命週期並非一致的,當Activity終止時,它會以它自有的方式繼續運行,即使你退出了整個應用程序。另一方面,要注意Android屏幕切換問題,因爲這時候Activity會重新創建。