AsyncTask:一只命途多舛的小麻雀
麻雀雖小
AsyncTask是一只命途多舛的小麻雀,為什么說它命途多舛,因為它一直被改,從Android 1.6之前,然后1.6到2.3,再從3.0到現在(其實5.1開始后也有細微的改動),反反復復,從串行到并行,再恢復至串行,可見其內心之糾結,盡管如此,它還是不斷被開發人員各種吐槽,內存泄露,不靠譜的cancel等等,真可謂命途多舛。
可是天將降大任于斯人也,它畢竟是一只小麻雀啊,不到三百行代碼量的一只小麻雀,其特點便是麻雀雖小五臟俱全,因此好好解讀一下源碼是很有必要的。如何來解讀呢,不妨我們自己來創建一只簡化版的小麻雀——SimpleAsyncTask,通過這種方式來加深理解。
作為一個標題黨,小麻雀已經完成它的使命了,其實這篇文章真正的標題應該是《從FutureTask到AsyncTask》或者《自己寫個AsyncTask》,以下,開始正題。
一 需求和api選型
想想看我們對于AsyncTask的需求是什么,大概會有以下幾點:
- 能定義以下執行過程的操作:預執行、執行后臺任務、執行進度反饋、執行完畢、終止線程執行
- 能停止線程任務
- 子線程能與UI線程交互(反饋線程執行進度和執行結果)
第1點,我們定義這些方法目的是讓調用者重寫而實現各種交互,因此這5種操作實際上是5個抽象方法(或者空方法),我們需要在SimpleAsyncTask類中合適的時機去調用這5個抽象方法,顯然,這是模板方法模式(Template Method)的應用。
對于第2點,如何停止線程?這方面的最佳實踐是中斷+條件變量,縱觀java.util.concurrent,有個api很適合這個場景,那便是Future和Callback,而作為Future的唯一實現——FutureTask便是我們寫AsyncTask的重點。
第3點,看到子線程和UI線程,so easy呀,有了Handler,麻麻再也不用擔心線程之間的通訊了,關于Handler,如果你閑的蛋疼的話不妨看看筆者寫的《Handler和他的小伙伴們》上和中。 有一點要注意的是,AsyncTask會在各Activity中實例化,有可能在主線程或子線程中實例化,這么多的AsyncTask實例中,我們只需要一個持有mainLooper的Handler的實例,因此它將是一個單例對象,單個進程內共享。
確定好需求和api選型之后,接下來我們來寫代碼。
二 使用Template Method
首先定義五個空方法:預執行、執行后臺任務、執行進度反饋、執行完畢、終止線程執行。
public abstract class SimpleAsyncTask<Params, Progress, Result> {
/**
* 模板方法1-預執行,應在線程啟動之前執行
*/
protected void onPreExecute() {
}
/**
* 模板方法2-執行后臺任務,應該在線程體中調用,即在FutureTask中調用
*/
protected abstract Result doInBackground(Params params);
/**
* 模板方法3-執行進度反饋,應該在Handler中調用
*/
protected void onProgressUpdate(Progress progress) {
}
/**
* 模板方法4-執行完畢,應該在Handler中調用
*/
protected void onPostExecute(Result result) {
}
/**
* 模板方法5-終止線程執行,應該在Handler中調用
*/
protected void onCancelled() {
}
}
這幾個方法見名知意,同時從各自攜帶的參數類型可以清晰的理解幾個泛型的作用:
- Params:執行后臺任務的時候使用
- Progress:反饋進度的時候使用
- Result:反饋結果的時候使用
(注:這里為了代碼簡潔,去掉了Params和Progress的可變參數)
當然,模板方法模式最重要的是我們在何時去調用這些抽象的模板方法,很顯然,doInBackground作為后臺執行的邏輯,應該在線程中調用,也就是下文會講到的FutureTask,而其他幾個方法跟UI有關系,會在主線程中執行,因此它們會在Handler中調用到。
三 FutureTask開發
上文提到,Future和Callable是java.util.concurrent中提供了取消任務的一組api,要理解AsyncTask的話,需要先理解下這對組合。以下做個簡單的介紹:
-
Callable與Runable
public interface Callable<V> { V call() throws Exception; }
簡單來講,Callable接口等價于Runable,call()等價于run(),區別在于它是有返回值的。
我們可以通過ExecutorService調用Callable,執行后將返回Future對象,比如:
Future<String> future = Executors.newSingleThreadExecutor().submit(mCallable);
-
Future
public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
Future接口兩個方法著重理解下,一是
cancel(boolean mayInterruptIfRunning)
,顧名思義就是終止線程,二是get()
,它會阻塞線程,直到Callable的call()返回對象,并以此作為返回值。至于mayInterruptIfRunning
這個boolean值的含義,大家看看FutureTask中相應的源碼就直到了,其實只是多了thread.interrupt()
的邏輯而已。結合Callable的代碼,Future的使用如下:Future<String> future = Executors.newSingleThreadExecutor().submit(mCallable); //阻塞線程,等待Callable.call()的返回值 String result = future.get();
-
FutureTask
FutureTask的繼承關系
從FutureTask的繼承關系來看,它既是Runable也是Future,所以我們可以把當做Runable來使用,同時它也具備Future的能力,可以終止線程,可以阻塞線程,等待Callable的執行,并獲取返回值。另外要注意的是,它的構造函數是public FutureTask(Callable<V> callable)
,因此實例化FutureTask時需要Callable對象作為參數。關于這部分基礎知識的demo代碼在此處,有需要的可以跑起來看看。
-
SimpleAsyncTask中的FutureTask
介紹完Future和Callable的基礎知識后,我們回歸正題。FutureTask在AsyncTask里充當了線程的角色,因此耗時的后臺任務doInBackground應該在FutureTask中調用,同時我們還要提供線程池對象來執行FutureTask,代碼如下:public abstract class SimpleAsyncTask<Params, Progress, Result> { //...省略部分代碼 private static final Executor EXECUTOR = Executors.newCachedThreadPool(); private WorkerRunnable<Params, Result> mWorker; private FutureTask<Result> mFuture; public SimpleAsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { @Override public Result call() throws Exception { //調用模板方法2-執行后臺任務 Result result = doInBackground(mParams); //提交結果給Handler return postResult(result); } }; //此為線程對象 mFuture = new FutureTask<>(mWorker); } public void execute(Params params) { mWorker.mParams = params; //在線程啟動前調用預執行的模板方法,意味著它在調用AsyncTask.execute()的所在線程里執行,如果是在子線程中,則無法處理UI //調用模板方法1-預執行 onPreExecute(); //執行FutureTask啟動線程 EXECUTOR.execute(mFuture); } private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params mParams; } //...省略部分代碼 }
四 Handler開發
到目前為止,我們已經消費掉了兩個模板方法,分別是onPreExecute和doInBackground,此時還剩下3個模板方法,他們都需要有UI交互的,因此他們將在Handler中被調用。
首先,對于終止線程和線程執行完畢這兩個方法,我們都稱之為線程finish了,所以我們先定義個finish(Result result)
方法,如下:
private void finish(Result result) {
if (isCancelled()) {
//調用模板方法:終止線程執行
onCancelled();
} else {
//調用模板方法:線程執行完畢
onPostExecute(result);
}
}
接下來,開始寫關鍵的Handler類對象,因為該Handler位于SimpleAsyncTask內部,因此把它命名為InternalHandler,這個靜態內部類有幾點要注意:
- 它是靜態的單實例,所有AsyncTask對象共享
- 它必須持有mainLooper對象,才能與主線程進行交互
-
注意它調用幾個模板方法的時機
代碼如下:public abstract class SimpleAsyncTask<Params, Progress, Result> { //省略部分代碼 /** * 一個靜態的單例對象,所有AsyncTask對象共享的 */ private static InternalHandler sHandler = new InternalHandler(); private static class InternalHandler extends Handler { /** * 注:此為android 22之后的寫法,構造函數里默認指定mainLooper對象 * 它的好處是無論Handler在什么時機被實例化,都可以與主線程進行交互 * 相比之下,之前版本的AsyncTask必須在ActivityThread中執行AsyncTask.init() */ public InternalHandler() { super(Looper.getMainLooper()); } @Override public void handleMessage(Message msg) { AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj; switch (msg.what) { case MESSAGE_POST_RESULT: //調用模板方法4和5,取消或者完成 result.mTask.finish(result.mData); break; case MESSAGE_POST_PROGRESS: //調用模板方法3-執行進度反饋 result.mTask.onProgressUpdate(result.mData); break; } } } /** * 由于InternalHandler是靜態內部類,無法引用外部類SimpleAsyncTask的實例對象, * 因此需要將外部類對象作為屬性傳遞進來,所以封裝此類 */ private static class AsyncTaskResult<Data> { final SimpleAsyncTask mTask; final Data mData; AsyncTaskResult(SimpleAsyncTask task, Data data) { mTask = task; mData = data; } } //省略部分代碼 }
到目前為止5個模板方法都已經調用到了,其中四個方法均有觸發的時機和調用的時機,除了onProgressUpdate,它只有調用,但并沒有觸發的時機,因此,我們還要提供一個方法,供調用者主動觸發:
protected final void publishProgress(Progress progress) { if (!isCancelled()) { AsyncTaskResult<Progress> taskResult = new AsyncTaskResult<>(this, progress); sHandler.obtainMessage(MESSAGE_POST_PROGRESS, taskResult).sendToTarget(); } }
寫到這里,我們這只簡化版的小麻雀基本上已經完成,當然對比源碼還是有一點點細微的不同,大家可以自行對比一下。通過自己寫個SimpleAsyncTask的方式可以幫助我們更好的理解源碼,以上所寫的完整代碼在此。
五 關于其他細節
-
串行or并行?
在SimpleAsyncTask中,我們使用private static final Executor EXECUTOR = Executors.newCachedThreadPool()
作為線程池,而實際上,源碼中的默認線程池是自定義的,這個類是SerialExecutor
,從類的命名上看,Serial是串行的意思,所以很明顯,AsyncTask默認是串行的。除此之外,AsyncTask里還有個線程池THREAD_POOL_EXECUTOR
,實在需要并行的話我們就用這個線程池。如果都些都不滿足要求,我們也可以自定義符合自己業務要求的線程池,并通過
setDefaultExecutor(Executor exec)
改變默認的線程池。 -
不能執行多次
AsyncTask只能執行一次,類似這樣的代碼是會拋異常的:MyAsyncTask asyncTask = new MyAsyncTask(); asyncTask.execute(); asyncTask.execute();
原理很簡單,AsyncTask會判斷當前狀態,如果是RUNNING或者FINISHED狀態,則直接拋異常:
//execute最終會執行到executeOnExecutor 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)"); } } //省略部分代碼 }
-
AsyncTask是否只能在主線程創建和運行?
比如,這樣的代碼能否正常使用:new Thread(new Runnable() { @Override public void run() { MyAsyncTask myAsyncTask = new MyAsyncTask(); mSimpleAsyncTask.execute("task1"); } }).start();
從Android4.1(API 16)之后其實已經沒什么問題了,通過源碼來理解的話非常簡單,比如在我們自己寫的SimpleAsyncTask中,重寫了Handler的構造器,如下:
public InternalHandler() { super(Looper.getMainLooper()); }
這樣一來,無論AsyncTask在什么時候創建,實例化出來的靜態InternalHandler對象都持有mainLooper,都能與主線程進行通訊。有一點小區別,如果在子線程中調用execute(),則onPreExecute不能執行UI的操作,否則會拋異常。
要注意的是,這是Android 5.1(API 22)以及之后的寫法,API 16~API 21是另外一種寫法,參考下一點。
-
AsyncTask.init()
AsyncTask中有個隱藏方法init()(API 22之后已經移除)/** @hide Used to force static handler to be created. */ public static void init() { sHandler.getLooper(); }
與此對應的,在ActivityThread的main方法中會調用AsyncTask.init(),目的是什么?
由于AsyncTask中的Handler是靜態的單實例對象,他會在類加載期間進行初始化,萬一調用者在子線程中加載AsyncTask,將會導致同一進程的所有AsyncTask無法使用。因此,系統先下手為強,一開始就直接加載,保證該Handler持有主線程的mainLooper,能正常進行UI交互。
-
postResultIfNotInvoked的作用是什么?
AsyncTask有很多邏輯干擾了我們解讀源碼,postResultIfNotInvoked便是其中一個。它實際上是Google解決的一個bug,確保如果cancel()方法過早調用的場景下,onCancelled()仍然能順利的執行,參考stackoverflow這篇文章。比如,我們自定義的SimpleAsyncTask,如果我們執行以下代碼,onCancelled()是不會執行的,而AsyncTask則可以正常執行:
mSimpleAsyncTask = new MySimpleAsyncTask(); mSimpleAsyncTask.execute("task1"); //馬上終止線程 mSimpleAsyncTask.cancel(true);
六 最后
AsyncTask作為一只命途多舛的小麻雀,他是一只純粹的麻雀,脫離了低級趣味的麻雀,值得好好品嘗的小麻雀。
參考文章:
http://blog.csdn.net/guolin_blog/article/details/11711405
http://droidyue.com/blog/2015/12/20/worker-thread-in-android/?droid_refer=ninki_posts
http://droidyue.com/blog/2014/11/08/bad-smell-of-asynctask-in-android/index.html
http://stackoverflow.com/questions/25322651/asynctask-source-code-questions