基礎(chǔ)知識(shí)點(diǎn)
1.線程池Executor
2.Future
3.Callable
4.中斷線程
5.Handler
6.枚舉
7.泛型
8.單例模式
9.可變參數(shù)
AsyncTask設(shè)計(jì)思想
1.回調(diào)函數(shù)
2.如何有序調(diào)度任務(wù)
3.串行或并行執(zhí)行任務(wù)
4.中斷任務(wù)
5.線程調(diào)度
回調(diào)函數(shù)
AsyncTask,是可異步執(zhí)行任務(wù)的工具類。開發(fā)者只需要至少重寫一個(gè)回調(diào)函數(shù)doInBackground(Params... params);就能完成線程調(diào)度
我們看看它的使用方法:
我們寫一個(gè)抽象類AsyncTask的具體子類MyAsyncTask,并覆寫其四個(gè)函數(shù)。
private class MyAsynckTask extends AsyncTask<Long, String, String> {
/**
* 任務(wù)被執(zhí)行前,會(huì)被調(diào)用,UI線程做一些提示操作,如顯示進(jìn)度條
*/
@Override protected void onPreExecute() {
super.onPreExecute();
Log.d(TAG, "onPreExecute: " + Thread.currentThread().getName());
}
/**
* 在后臺(tái)子線程執(zhí)行任務(wù),返回類型String
* @param params 模擬計(jì)算時(shí)間
* @return
*/
@Override protected String doInBackground(Long... params) {
try {
TimeUnit.SECONDS.sleep(params[0]);//模擬后臺(tái)耗時(shí)計(jì)算,參數(shù)大小作為計(jì)算時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "doInBackground: " + Thread.currentThread().getName());
return Thread.currentThread().getName();
}
/**
* 任務(wù)執(zhí)行完畢后,在主線程回調(diào)這個(gè)函數(shù)。
* @param s 后臺(tái)任務(wù)返回的結(jié)果
*/
@Override protected void onPostExecute(String s) {
super.onPostExecute(s);
Log.d(TAG, "onPostExecute: " + s + " ---" + Thread.currentThread().getName());
}
/**
* 任務(wù)中途被取消會(huì)回調(diào)它
*/
@Override protected void onCancelled() {
super.onCancelled();
Log.d(TAG, "onCancelled: ");
}
}
我們主要看doInBackground(Parms...parm)函數(shù),它是什么時(shí)候被回調(diào)的呢?在后臺(tái)FutrueTask類執(zhí)行call()函數(shù)時(shí)回調(diào)。 這里WorkerRunable是一個(gè)實(shí)現(xiàn)了Callable接口的實(shí)現(xiàn)類,它可以執(zhí)行任務(wù)后能攜帶一個(gè)返回值Result,并拓展了可以持有一個(gè)Parms參數(shù)。我們看看它的回調(diào)時(shí)刻:
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
/省略一些代碼
Result result = null;
result = doInBackground(mParams);
````省略一些代碼
return result;
}
};
我們?cè)赨I線程使用它即可:
new MyAsynckTask().execute(100l);
new MyAsynckTask().execute(5l);
new MyAsynckTask().execute(5l);
new MyAsynckTask().execute(5l);
輸出結(jié)果:
04-03 22:44:47.713 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:47.713 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:47.713 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:47.714 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPreExecute: main
04-03 22:44:52.746 4256-4295/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #1
04-03 22:44:52.747 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #1 ---main
04-03 22:44:57.788 4256-4376/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #2
04-03 22:44:57.788 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #2 ---main
04-03 22:45:02.832 4256-4450/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #3
04-03 22:45:02.833 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #3 ---main
04-03 22:45:07.875 4256-4450/com.fulaan.asynctaskdemo D/MainActivity: doInBackground: AsyncTask #3
04-03 22:45:07.875 4256-4256/com.fulaan.asynctaskdemo D/MainActivity: onPostExecute: AsyncTask #3 ---main
所以,我們?cè)诨惱锩鎸懸恍┖瘮?shù)鉤子,并調(diào)用,然后然使用者在具體實(shí)現(xiàn)類里面去實(shí)現(xiàn)。
如何結(jié)合線程池串行調(diào)度任務(wù)
從上面的輸出結(jié)果來看,AsyncTask是默認(rèn)串行執(zhí)行的(不考慮版本問題)。它用了兩個(gè)調(diào)度器Executor實(shí)現(xiàn)。
- SERIAL_EXECUTOR,用隊(duì)列保存等待被被執(zhí)行的任務(wù)
*THREAD_POOL_EXECUTOR。異步執(zhí)行任務(wù)
SerialExecutor串行調(diào)度。我們把任務(wù)裝入一個(gè)隊(duì)列數(shù)據(jù)結(jié)構(gòu),一個(gè)個(gè)排隊(duì)執(zhí)行, 代碼:
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();//一個(gè)環(huán)形隊(duì)列用來保存提交過來的任務(wù)。
Runnable mActive;//表示可立即執(zhí)行的任務(wù)。
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在同步方法execute(r)中的實(shí)現(xiàn),它有兩步:
第一步: 保存任務(wù)到隊(duì)列。新建一個(gè)Runable包裹提交進(jìn)來的任務(wù),在try作用域里面先執(zhí)行r.run(),必須等任務(wù)執(zhí)行完后,在finally作用域中會(huì)執(zhí)行 scheduleNext(),來調(diào)度下一個(gè)任務(wù)。因此任務(wù)是串行執(zhí)行的。最后我們會(huì)把這個(gè)新建的Runnable對(duì)象放入隊(duì)列mTasks中。
第二步:查看是否有可立即執(zhí)行的任務(wù)mActive。 如果有,則立即放入THREAD_POOL_EXECUTOR執(zhí)行。
我們?cè)倏纯磗cheduleNext()函數(shù),就是調(diào)用mTasks.poll())從隊(duì)列得到下一個(gè)任務(wù),執(zhí)行。
文字描述也許會(huì)迷糊,不如看下圖[采用OmniGraffle軟件制作]:
至此,為什么要兩個(gè)調(diào)度器呢?我認(rèn)為是為了遵守單一職責(zé)的設(shè)計(jì)原則。一個(gè)調(diào)度器只負(fù)責(zé)一件事,前面SerialExecutor我們已經(jīng)分析。那THREAD_POOL_EXECUTOR線程池調(diào)度器則是根據(jù)CPU數(shù)構(gòu)造線程Thread緩存池,節(jié)約資源,提高AsyncTask執(zhí)行性能。
3.為什么要串行執(zhí)行任務(wù)?不串行可以嗎?
為什么要串行執(zhí)行,我認(rèn)為是為了保證數(shù)據(jù)一致性。正如官方文檔說的:
tasks are executed on a single thread to avoid common application errors caused by parallel execution.</p>
- If you truly want parallel execution, you can invoke
executeOnExecutor(java.util.concurrent.Executor, Object[])} with - THREAD_POOL_EXECUTOR.
意思是說,串行模式能夠避免一些并發(fā)異常。如果你真的需要并發(fā)執(zhí)行,這可以調(diào)用executeOnExecutor(),傳入自定義一個(gè)Executor,這樣就不會(huì)去調(diào)用SerialExecutor了。而我認(rèn)為AsyncTask只適合執(zhí)行一些小任務(wù),因?yàn)樵诖袌?zhí)行時(shí),如果某個(gè)任務(wù)執(zhí)行時(shí)間過長,或者中途阻塞,占有CPU太久,則會(huì)導(dǎo)致后面排隊(duì)的任務(wù)等待過久。
4.如何取消任務(wù)
我們知道為了能得到后臺(tái)線程運(yùn)行后的結(jié)果,我們的任務(wù)實(shí)現(xiàn)了的Callable接口,它和Runnable區(qū)別是有一個(gè)返回值。現(xiàn)在我們想取消任務(wù),我們可以實(shí)現(xiàn)Future接口,它除了能通過get()得到返回值,還有cancle()方法用來取消任務(wù),done()回調(diào)函數(shù)監(jiān)聽是否任務(wù)結(jié)束
在AsyncTask中,我們用FutureTask這個(gè)系統(tǒng)SDK為我們實(shí)現(xiàn)了Ruanble和Future的實(shí)現(xiàn)類,它可以通過一個(gè)Callable類型的對(duì)象參數(shù)構(gòu)造出來,這里使用我們的是上文中的WorkRunnable作為參數(shù)。
代碼如下:
FutureTask 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);
}
}
};
那么如果我們想取消任務(wù),現(xiàn)在是不是直接調(diào)用mFuture.cancel(true)強(qiáng)制中斷線程就好了?答案是否定的。因?yàn)閙Future.cancel(true)方法內(nèi)部實(shí)現(xiàn)機(jī)制其實(shí)是:
//.....省略一些代碼
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
//.....省略一些代碼
意思是如果任務(wù)正在運(yùn)行,則interrupt()中斷線程。而在這里,如果任務(wù)正好還在排隊(duì)尚未執(zhí)行,而我們想取消未執(zhí)行的任務(wù),那么直接調(diào)用mFuture.cancel(true)就無效!因此,我們還需要考慮未執(zhí)行的任務(wù)如何中斷。
令人驚喜的是,在AsyncTask中還提供了一個(gè)對(duì)象:
private final AtomicBoolean mCancelled = new AtomicBoolean();
它是一個(gè)原子類,通過CAS機(jī)制保證了mCancelled可見性,它內(nèi)部保存了當(dāng)前任務(wù)是否被取消的信號(hào)。
那么正確完整的取消任務(wù)方案是分兩個(gè)步驟:
第一步:調(diào)用AsyncTask中的cancel方法:
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);//用來取消未執(zhí)行的任務(wù)
return mFuture.cancel(mayInterruptIfRunning);//用來取消正在執(zhí)行的任務(wù)可能會(huì)拋出異常
}
第二步:在doInBackground回調(diào)函數(shù)中循環(huán)去判斷mCancelled中的值確認(rèn)將要執(zhí)行的任務(wù)是否要求取消:
@Override protected String doInBackground(Long... params) {
while (!isCancelled()){
//todo something...
}
return null;
}
至此,我們成功實(shí)現(xiàn)了取消任務(wù)功能。
線程調(diào)度
在安卓系統(tǒng)中,我們要從子線程更新UI主線程,當(dāng)然是利用handler+looper+messageQueue機(jī)制,在AsyncTask中不例外也是如此。但是除此之外,我們可以學(xué)習(xí)如何通過單例模式構(gòu)造一個(gè)handler,如何強(qiáng)制構(gòu)造一個(gè)主線程執(zhí)行的handler。
1.單例模式:
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
看看源碼中的這個(gè)單例模式,比較簡陋,也不是什么double-check :)
2.強(qiáng)制在主線程執(zhí)行handler
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
}
如上在父類構(gòu)造函數(shù) [super(Looper.getMainLooper())] 初始化Looper。
執(zhí)行流程圖
總結(jié)
AsyncTask作為Activity內(nèi)部類使用的時(shí)候還會(huì)因?yàn)槌钟衋citivity對(duì)象引用,在后臺(tái)導(dǎo)致內(nèi)存泄漏問題。推薦閱讀其他執(zhí)行異步任務(wù)類框架, 如Job、Rxjava等。