寫在前面
感覺最近自己需要多讀書,所以在以后的一段時間里可能都是筆記形式的文了,希望自己能厚積薄發(fā)吧。
AsyncTask簡介
AsyncTask是一個輕量級的異步任務(wù)類,允許你將一個耗時操作放在后臺進行,并且會返回操作的結(jié)果給你。那么AsyncTask和Thread-Handler或者線程池有什么異同呢?
在AsyncTask的源碼注釋里這樣描述:
/**
* <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
* perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers.</p>
*/
AsyncTask能讓更加簡便的使用UI線程。這個類允許執(zhí)行后臺操作和將結(jié)果發(fā)送到UI線程而不必操作線程和handlers。
讀了上面的注釋讓我們對AsyncTask有了一定的了解,這是個方便我們的類,讓我們在后臺執(zhí)行操作結(jié)果而不必自己手動的去切換線程,那么這個類是否有其他的限制呢?
/** <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link 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 <code>java.util.concurrent</code> package such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
*/
AsyncTask被設(shè)計作為Thread和Handler間的幫助類,并非構(gòu)建線程框架的通用類。AsyncTask理想情況下應(yīng)該被用來進行短時間的操作,如果你需要保證線程長期運行,那么強烈推薦你使用java.util.concurrentpackage提供的多種API,例如Executor、ThreadPoolExecutor和FutureTask。
簡單的演示
上面簡單的介紹了一下AsyncTask,下面看一下如何使用。AsyncTask提供了4個核心方法:
onPreExecute(),在主線程中執(zhí)行,在后臺任務(wù)執(zhí)行之前,此方法會被調(diào)用
doInBackground(Params... params),此方法用于執(zhí)行需要執(zhí)行的異步任務(wù)
onProgressUpdata(Progress... values),在主線程中執(zhí)行,當(dāng)后臺任務(wù)的執(zhí)行進度發(fā)生改變時此方法會被調(diào)用
onPostExecute(Result result),在主線程中執(zhí)行,返回操作結(jié)果,返回類型是doInBackground的返回值
new AsyncTask<String, Integer, Bean>() {
@Override
protected void onPreExecute() {//做一些準(zhǔn)備工作
super.onPreExecute();
}
@Override
protected Bean doInBackground(String... params) {//后臺任務(wù)
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {//后臺任務(wù)執(zhí)行進度發(fā)生變化
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Bean aLong) {//異步任務(wù)執(zhí)行完成返回結(jié)果
super.onPostExecute(aLong);
}
}.execute(url1,url2);
上面的代碼只是一個簡單的實例,并非真的演示如何使用。以上代碼中的三個參數(shù)可以理解為url、進度和自定義的數(shù)據(jù)類型。帶入我們平時的開發(fā)中就是根據(jù)url拿到數(shù)據(jù),然后在界面上更新進度。在方法中這樣String... params表示不定數(shù)量的參數(shù),是數(shù)組型的參數(shù)。上面的方法實際上運行的效果是串行執(zhí)行的,你可能會說AsyncTask內(nèi)部不是封裝了一個線程池嗎?為毛會是串行的?這個問題先留著,先把結(jié)論擺在這,而且我也在AsyncTask的源碼中看到了如下的注釋(原文不放了,感興趣的請自己去看):
調(diào)度任務(wù)是用隊列單獨的調(diào)度一個后臺線程還是用線程池取決于平臺版本。剛發(fā)布的時候,AsyncTask是以串行線程的方式執(zhí)行的。從Android DONUT(1.6)開始允許多任務(wù)并發(fā)執(zhí)行。在Android HONEYCOMB(3.0)之后又變成了單任務(wù)串行執(zhí)行,這是為了避免由于并發(fā)操作可能帶來的錯誤。如果你真的想要并發(fā)執(zhí)行,你可以使用excuteOnExecutor和THREAD_POOL_EXECUTOR。
好了,讀到這,終于對AsyncTask有了一些了解了,帶著一些問題去看看源碼吧。
源碼筆記
讀源碼先從AsyncTask的入口execute()開始看:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
調(diào)了一個方法,沒啥好說的,跟進去看就行了,這里返回值是AsyncTask,方便我們持有一個AsyncTask的引用。
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;
//調(diào)用這個方法,如果你有實現(xiàn),那么你的代碼將在這被執(zhí)行
onPreExecute();
//拿到參數(shù)
mWorker.mParams = params;
//開始運行
exec.execute(mFuture);
return this;
}
這里先看mStatus,這東西是個枚舉類型,里面仨值分別是:
- PENDING:任務(wù)還沒有被執(zhí)行過,表示可以被執(zhí)行
- RUNNING:任務(wù)正在執(zhí)行
- FINISHED:任務(wù)已經(jīng)完成
從代碼中可以看出來只有這個值是PENDING的時候才會被執(zhí)行,其他的值都會報異常,這就是AsyncTask對象只能運行一次的由來了,每次執(zhí)行任務(wù)都需要新建一個AsyncTask對象(準(zhǔn)確的來說是子類對象)。
在運行之后將mStatus的值改為RUNNING,之后這個對象就不能在其他的地方被執(zhí)行了。可以看到在任務(wù)真正被執(zhí)行之前調(diào)用了onPreExecute()方法,這就是這個方法可以做一些準(zhǔn)備工作的原因。
之后先獲取參數(shù),再執(zhí)行。這里先簡單的說說,要弄懂最后這句exec.execute(mFuture);代碼還需要結(jié)合前面的代碼來看。
之前在前面說了AsyncTask內(nèi)部有兩個線程池,那么他要干啥為毛要兩個線程池呢?因為一個線程池是串行的線程池,一個進程中的所有待執(zhí)行的任務(wù)都會在這個串行的線程池中排隊執(zhí)行。接下來看一下AsyncTask內(nèi)部的倆線程池在代碼里長啥樣:
/**
* 可以并發(fā)執(zhí)行任務(wù)
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
/**
* 在串行命令下一次執(zhí)行一個任務(wù)。
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
上面那個線程池是真正用來執(zhí)行任務(wù)的,下面的是用來排隊等待的??梢郧宄目吹竭@倆是靜態(tài)的,所以是全局共享這個就不做過多的解釋了。那么按順序來,從下面的線程池的execute()來看:
private static class SerialExecutor implements Executor {
//雙端隊列
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
//將任務(wù)插入到任務(wù)隊列中
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
//如果當(dāng)前沒有正在執(zhí)行的任務(wù),那么執(zhí)行下一個AsyncTask任務(wù)
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {//取出這個任務(wù)
//放入并發(fā)線程池中執(zhí)行
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
從上面代碼的流程我們可以認(rèn)識到在我們不指定線程池的情況下,的確,我們的代碼是以串行的方式被執(zhí)行的。關(guān)于mFuture其真實類型是FutureTask,對此我們不需要再做更多的了解(其實我了解的也不多...),當(dāng)然了如果你對Java的并發(fā)編程感興趣可以自己去做更多的了解。在這我們需要知道的就是mFuture的run方法會調(diào)用mWorker的call方法,因此mWorker的call方法最終會在線程池中執(zhí)行。
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
//表示當(dāng)前任務(wù)已經(jīng)被調(diào)用過了
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//執(zhí)行我們的代碼
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
//發(fā)送操作結(jié)果
return postResult(result);
}
};
以上可以看到我們希望在后臺執(zhí)行的代碼被調(diào)用了,并且結(jié)果被postResult這個方法發(fā)送了,跟進去看看:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
直接看這個消息在Handler里面是怎么處理的:
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@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;
}
}
}
在這可以看到這個Handler是一個靜態(tài)類,靜態(tài)類會在類被加載的時候就被初始化,而Handler的初始化時需要looper()的,所以這就需要你在主線程中使用AsyncTask。否則要么出錯,要么AsyncTask就被你廢了。好了,繼續(xù)看,在對應(yīng)的情況底下調(diào)用了AsyncTask的finish方法,看下是啥:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
如果被取消,就調(diào)取消的方法,不然的話就回調(diào)這個onPostExecute(result),由于是經(jīng)過handler發(fā)送的,所以線程已經(jīng)切換到了AsyncTask調(diào)用的線程中去了(關(guān)于這個如果你有不明白可以看我的Android消息機制淺析),我們就可以在主線程中開心的使用這個結(jié)果去更新UI了。
小結(jié)
- AsyncTask用起來比較方便,但是特別耗時的操作并不適合用它來執(zhí)行。
- AsyncTask默認(rèn)是串行執(zhí)行,但是你可以通過指定執(zhí)行的線程池來讓他并發(fā)執(zhí)行。
- AsyncTask對象只能被執(zhí)行一次。
- AsyncTask使用Handler來切換線程。
- AsyncTask一般情況下都是需要在主線程被實現(xiàn)和調(diào)用的。
- AsyncTask在不同版本的Android上可能會有不同的表現(xiàn),但是現(xiàn)在用戶Android版本普遍在4.0以上,這個就無需考慮了。
參考資料:
《Android開發(fā)藝術(shù)探索》
源碼版本:Android 7.0 api 24