Android AsyncTask 源碼解析

標簽:Android AsyncTask 源碼解析


1.關于AsyncTask

1.1 什么是AsyncTask?

根據Google的官方文檔

This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask允許我們執行后臺操作并在UI線程中更新結果,而不用去操作threads或handlers。這句解釋很明顯的告訴了我們:AsyncTask是一個thread和handler的封裝。

1.2 為什么使用AsyncTask?

既然AsyncTask只是一個封裝,那它到底幫我們解決了哪些細節上的問題呢?除了提供更簡明的接口之外,當我們單純的使用thread+handler的組合時,其實我們忽略了線程所帶來的一系列問題,比如創建線程的開銷、如何管理線程等等,特別是當多任務情況下,情況會更加復雜。

2.源碼解析

*注:源碼基于api-24
源碼點進去一大堆,我們這里直接從任務被執行開始看起。同時為了避免深入細節無法自拔的情況,我先大體看一下整個的調用順序,而不去太糾結具體實現。

@MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

可以看到子類直接調用了父類的execute()方法。該方法又調用了executeOnExecutor() 方法。同時將sDefaultExecutor這個靜態變量傳遞給了executeOnExecutor方法。那這個sDefaultExecutor又是什么鬼呢?

   public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
   //省略...
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

原來sDefaultExecutor只是一個Executor接口,而它的實現則是SerialExecutor這樣一個內部類。我們先把這個類的具體實現放下,不過從類的名字就可以猜出個大概來,這是一個用來順序執行任務的線程池類。OK這個坑我們先邁過去,回頭再來繼續填它。再看看executeOnExecutor()方法:

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

5-17行先判斷當mStatus的值,即前任務的狀態,只有沒有運的任務才能夠被運行,否則將會拋出異常。再往下看,在將mStatus的值置為RUNNING之后,我看到了一個十分熟悉的方法onPreExecute(),而這個方法是個空實現,具體在執行任務之前要做些什么,是由我們自己去實現的。23-24行突然冒出兩個新變量,mWorker和mFuture,回到變量聲明和構造函數處看看他們的實質。

 //變量聲明
 private final WorkerRunnable<Params, Result> mWorker;
 private final FutureTask<Result> mFuture;
 
 ...
 
 //構造函數,變量初始化
 public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                //...省略部分代碼
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
              //...省略部分代碼
        };
    }

可以看到mWorker為一個WorkRunnale類,該類為一個靜態抽象內部類,包含了一個初始化參數Params[],并繼承了Callable接口(待實現)。

  private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }

而mFuture為FutureTask的一個實例,FutureTask是Java并發編程中一個十分常見的類,主要用來異步執行任務,并返回結果。很顯然,這些特點都是我們需要,但單純的Thread所不具備的。FutureTask需要一個Callable接口來進行初始化,而這里我們使用了mWorker這個變量。
介紹了這么多,我們再捋一下思路:我們自己實現多XXXTask執行了execute()方法,再調用了我們重寫多onPreExecute()方法后,該方法最終把一個任務(mFuture)交給了一個串行執行任務的線程池(sDefaultExecutor)去執行。那么剩下的問題就只有以下幾個了:
1.任務(mFuture)具體做了些什么?
2.任務(mFuture)執行完后,如何通知UI線程的?
再回頭看看mWork中Callable的實現:

mWorker = new WorkerRunnable<Params, Result>() {
            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);
            }
        };

在這里我們發現一個十分熟悉的方法——doInBackground(),而該方法的簽名是protected abstract Result doInBackground(Params... params)。是我們在子類中必須去實現的,因此mFuture具體做的事情就是我們在doInBackground()中的具體實現。再看看最后一行postResult()(此時任務有可能并未完成)

  private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

再看看任務執行結束后,mFuture中done()方法的具體實現:

 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);
                }
            }
        };
    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }

可見,任務結束后同樣也調用了postResult()方法。而該方法又是如何將結果傳遞給UI線程的呢?答案也很簡單——通過Handler,這里的getHandler()方法返回一個sHandler靜態成員變量,其實現如下:

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

可以看到,構造器通過Looper.getMainLooper()來初始化了Hanlder,這也就是為什么hanlderMessage的具體執行是在UI線程(主線程)中的原因。最后判斷消息的種類,如果為MESSAGE_POST_PROGRESS就調用我們自己實現的onProgress方法;如果為MESSAGE_POST_RESULT就調用finish方法,而finish()方法又會判斷當前任務是否執行完成。若執行完成就交給我們實現的onPostExecute()方法,若還未執行完成就就給我們實現的onCancelled()方法。這樣一來整個AsyncTask的大致流程我們就走完了~
再次整理一下流程:
1.我們自己實現的xxxTask(繼承AsyncTask)執行了execute()方法;
2.execute方法調用了executeOnExecutor()方法,為我們的異步任務指定了一個串行執行任務的線程池;
3.該線程池調用了mFuture的call()方法,在子線程執行了我們重寫的doInBackground()方法,最終發送一個消息給handler(用主線程的looper初始化);
4.該handler根據消息類型的不同,執行結束任務onPostExecute()或更新進度的操作onProgressUpdate();

到此,我們常用的AsyncTask的方法,是不是不用再去翻文檔我們也能寫出先后順序了呢,成就感滿滿噠~

3.細節問題

---待更新---

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

推薦閱讀更多精彩內容