Android 的系統異步工具類AsyncTask源碼解析

由于從wordpress將文章倒回,發現格式有點混亂,經努力調整后依然有部分的格式還未調教好,請多多包涵.

分析AsyncTask或者闡述AsyncTask的博文有很多,本著授人予魚不如授人予漁的想法我想通過自主學習的方式去探索這個api,如果有闡述不當的地方歡迎各位大神不吝斧正.本文將通過源碼學習分析以及demo實例驗證的方式徹底的了解AsyncTask運行的原理.

隨著開源社區的興旺,在目前android的開發中有著各種異步的工具以及框架,現在比較熱門的當屬Rxjava+RxAndroid這個React的開源庫,至于這個從Android1.5版本就開始提供的異步工具AsyncTask反而經常出現在Android各類異步實現文章中的反面例子.我寫這篇文章有以下的意圖:一是通過源碼和實例去探討為什么AsyncTask一直被吐槽.二是了解學習AsyncTask的實現方式(只有了解原理,在使

用中遇到各式問題才有對策).

關于AsyncTask的介紹以及使用例子可以移步官方開發者網站,這里就不多費口舌.AsyncTask的存在的原因是在Android應用運行過程中,耗時的任務不能放在UI線程也就是我們常說的主線程中執行,必須開啟線程在后臺執行,如果有執行結果要通知到頁面上的話需要通過handler進行子線程和UI線程的通信,而AsyncTask就是應運而生去簡化這一過程的api.

現在進入實現原理的正題,通過學習源碼的過程可以設想如果是要求自己實現一個這樣的異步工具應當怎么做,任何創造的前提是從模仿開始.筆者這邊使用的sdk version 23的源碼.首先是類的定義:

public abstract class AsyncTask {

? ? private static final String LOG_TAG = "AsyncTask";

? ? private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

? ? private static final int CORE_POOL_SIZE = CPU_COUNT + 1;

? ? private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

? ? private static final int KEEP_ALIVE = 1;

? ? 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());

? ? }

? ? };

? ? private static final BlockingQueue sPoolWorkQueue =new LinkedBlockingQueue(128);

/**

* An {@link Executor} that can be used to execute tasks in parallel.

*/

public static final Executor THREAD_POOL_EXECUTOR

= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,

TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

AsyncTask是抽象類,根據使用的需要定義了三個泛型分別是傳入的參數,執行的Progress,以及執行的結果.通過這一小段的源碼可以看到AsyncTask擁有一個靜態final的THREAD_POOL_EXECUTOR,這個線程池能夠運行的線程最大數是通過 cpu核心數*2+1 計算得出.并且在一個進程中無論開發者寫出多少個AsyncTask的實現子類最終管理線程的只是這個大小為128的線程池.如果連續添加超過128個任務,線程池就爆了(這也是被其他開發者詬病的地方,不過這樣的開發寫法是否符合規范也待商榷).現在接著往下看:

/**

* An {@link Executor} that executes tasks one at a time in serial

* order.? This serialization is global to a particular process.

*/

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static final int MESSAGE_POST_RESULT = 0x1;

private static final int MESSAGE_POST_PROGRESS = 0x2;

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

private static InternalHandler sHandler;

private final WorkerRunnable mWorker;

private final FutureTask mFuture;

private volatile Status mStatus = Status.PENDING;

private final AtomicBoolean mCancelled = new AtomicBoolean();

private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

private static class SerialExecutor implements Executor {

final ArrayDeque mTasks = new ArrayDeque();

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);

}}}

剛才執行的線程池構建好了,現在輪到Exector SERIAL_EXECUTOR,看這個對象的命名為serial,感覺像是單個按順序的執行器(非并發),我們接著往下看:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

默認的executor為SERIAL_EXECUTOR 并帶上了volatile(這里帶上volatile類型修飾符的原因是在多線程編程中開發者可以通過調用AsyncTask.setDefaultExecutor(Executor executor)使用自定義的Executor替換SERIAL_EXECUTOR.此處先不急著看SERIAL_EXECUTOR.接著是

private static InternalHandler sHandler;

又是一個靜態的對象InternalHandler,貼出類聲明:

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;

}

}

}

根據對象名稱有internal說明是一個內部的事件handler,構造函數帶有super(Looper.getMainLooper())說明是這個Handler的handleMessage(Message msg)的方法是在主線程中調用,分別有兩個事件是處理result信息和progress信息這里也就滿足了之前類設計需求需要在主線程中反饋執行結果.剩下的

private final WorkerRunnable mWorker;

private final FutureTask mFuture;

private volatile Status mStatus = Status.PENDING;

private final AtomicBoolean mCancelled = new AtomicBoolean();

private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

將放在通過調用類的構造函數以及執行的流程中講訴.以上我們將AsyncTask的屬性分析完畢,接下來通過實例調用過程來進行源碼旅程.Demo的例子可以查看Github.

我們創建一個AsyncTask的子類TimeConsumingTask,模擬后臺耗時的操作以及反饋結果給UI線程.

public class TimeConsumingTask extends AsyncTask {

private static String TAG = "TimeConsumingTask";

private WeakReference mHandler;

public static final String TAG_RESULT = "result";

private static volatile int sExecutionCount ;

public TimeConsumingTask(Handler handler) {

mHandler = new WeakReference(handler);

}

@Override

protected void onPreExecute() {

Log.d(TAG, "onPreExecute");

}

@Override

protected Boolean doInBackground(Integer... params) {

Log.d(TAG,"execute count is :"+ ++sExecutionCount);

for (Integer num : params) {

try {

Thread.sleep(num * 1000);

//will call onProgressUpdate()

publishProgress(num);

} catch (InterruptedException e) {

e.printStackTrace();

return false;

}}


return true;

}

@Override

protected void onProgressUpdate(Integer... values) {

for (Integer value : values) {

Log.d(TAG, "onProgressUpdate result: " + value);

if (null != mHandler.get()) {

Message message = new Message();

message.what = value;

mHandler.get().sendMessage(message);

}

}

}

@Override

protected void onPostExecute(Boolean aBoolean) {

Log.d(TAG, "onPostExecute result: " + aBoolean);

if (null != mHandler.get()) {

Message message = new Message();

message.getData().putBoolean(TAG_RESULT, aBoolean);

mHandler.get().sendMessage(message);

}}}

這里構造方法我是通過傳入一個弱引用的主線程的handler進行運行結果的反饋,雖然只是demo還是希望寫的嚴謹些.子類會率先調用父類的構造方法,此時我們來關注AsyncTask的構造方法,里面包含了剛才未說明的兩個屬性WorkerRunnable和FutureTask,FutureTask是java1.5引入的api,源碼說明是A cancellable asynchronous computation.具體的可以建議讀者去好好學習下,當下android很多流行的開源庫中實現都離不開FutureTask,這里就不做介紹.我們來看AsyncTask的構造函數:

/**

* Creates a new asynchronous task. This constructor must be invoked on the UI thread.

*/

public AsyncTask() {

mWorker = new WorkerRunnable() {

public Result call() throws Exception {

mTaskInvoked.set(true);

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

//noinspection unchecked

Result result = doInBackground(mParams);

Binder.flushPendingCommands();

return postResult(result);

}

};

mFuture = new FutureTask(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 implements Callable {

Params[] mParams;

}

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(this, result));

message.sendToTarget();

return result;

}

我們可以看到WorkerRunnable是實現Callable在call方法中修改了運行標識|設置線程優先級|以及真正的運行doInBackground(Param)的方法,以及在運行結束后調用postResult(result).其中 Binder.flushPendingCommands();這句的調用可能很多朋友不甚熟悉,可以點擊方法跳轉至源碼查看說明,這里留個懸念.在FutureTask的主要是重寫了done()的回調進行如果是未調用后臺方法就結束了異步任務的判斷,并拋出異常,此處可以發現好的api是在方法命名中就清楚的告訴了閱讀者意圖.此處的getHandler()就是通過lazyInit的方式獲取剛才說到的變量InternalHandler進行通信.

我們看完了構造方法,現在來看AsyncTask的調用方法,AsyncTask的api設計十分簡單,可調用方法execute(Params...)/cancel(boolean mayInterruptIfRunning)/...,接下來看下一般繼承AsyncTask一般要重寫的四個方法:onPreExecute(),doInBackground(Param...), onProgressUpdate(Progress... values) ,onPostExecute(Result result),接下來會講解這四個方法在源碼中被調用的時機.我們先看下核心方法execute(Params...).進入源碼:

@MainThread

public final AsyncTask execute(Params... params) {

return executeOnExecutor(sDefaultExecutor, params);

}

@MainThread

public final AsyncTask 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;

}

在方法頭加入了google annotation@MainThread,如果還不了解google annotation的朋友也可以上官方開發者網站上搜索進行了解.說明execute方法需要運行在主線程中.execute的方法調用了executeOnExecutor并傳入剛才看到的sDefaultExecutor也就是SERIAL_EXECUTOR以及AsyncTask中三個泛型中的第一個Param作為參數.這里在executeOnExecutor運行中對Status進行了檢查,如果非PENDING態則拋出異常.Status只有PENDING/RUNNING/FINISHED,三種狀態,初始化的時候是PENDING態,這也是我們不能對同一個task反復調用execute的原因.更改狀態之后就調用了 onPreExecute();所以開發者繼承AsyncTask重寫的四大方法中第一個onPreExecute()是運行在主線程中.

exec.execute(mFuture);

AsyncTask的sDefaultExecutor開始執行我們在構造函數中初始化的futureTask了.此處讓我們將目光轉向sDefaultExecutor的默認賦值SERIAL_EXECUTOR:

private static class SerialExecutor implements Executor {

final ArrayDeque mTasks = new ArrayDeque();

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);}}}

好的代碼總是簡潔明快,通過ArrayDeque 存儲傳入的任務,并在scheduleNext通過THREAD_POOL_EXECUTOR.execute(task)的方式進行任務調用,這里就不多做闡述了,這個Executor做了和它類名一樣的實行就是序列化執行列表中的任務.SERIAL_EXECUTOR是在3.0版本后加入的,說明3.0版本后AsyncTask默認不是并行化執行任務而是順序執行.也就是說在3.0之前的AsyncTask可以同時有5個任務在執行,而3.0之后的AsyncTask同時只能有1個任務在執行.

之前在文章中已經講述在WorkerRunnable的call()方法中調用了耗時操作方法doInBackground(Params...),那在后臺執行的過程中更新UI的方法onProgressUpdate(Progress... values)又是怎么被調用的,根據官方文檔在doInBackground方法中需要post結果到主線程的時候會調用publishProgress(Progress...)方法.而這個方法和主線程有關系我相信讀者已經有一個概念該方法中是通過往IntervalHandler發出消息來實現:

@WorkerThread

protected final void publishProgress(Progress... values) {

if (!isCancelled()) {

getHandler().obtainMessage(MESSAGE_POST_PROGRESS,

new AsyncTaskResult(this, values)).sendToTarget();

}

}

這邊提一下方法頭的googleAnnotation @WorkerThread說明該方法只能在工作線程中被調用.

Demo地址:在使用Demo時候可以通過DDMS的Thread監控功能進行AsyncTask的線程池監控驗證.

這里只是拋磚引玉寫一篇AsyncTask的源碼分析,希望各位大神能熱心參與android異步編程的討論.

Android異步一些輕量實現的討論文章:https://medium.com/@ali.muzaffar/handlerthreads-and-why-you-should-be-using-them-in-your-android-apps-dc8bf1540341#.6de7zdbii

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容