AsyncTask 學(xué)習(xí)

AsyncTask 類常用于處理 Android 的異步任務(wù)。

本文概括AsyncTask 的使用和簡單分析內(nèi)部實(shí)現(xiàn)原理棍郎。

本文記錄基于Android API 27顾稀。

要點(diǎn)

  • AsyncTask 的創(chuàng)建可以傳入 Handler對象或 Looper對象,也可以不傳任何對象坝撑;
  • AsyncTask 需要重寫其 doInBackground()方法静秆,此方法會在子線程中執(zhí)行粮揉;
  • AsyncTask 的onPreExecute()會在任務(wù)正式啟動前被調(diào)用,此方法會在主線程中執(zhí)行抚笔;
  • AsyncTask 的 onPostExecute(Result result) 方法用于執(zhí)行結(jié)果的發(fā)送扶认,此方法會在主線程中執(zhí)行。返回的result是doInBackground()方法的結(jié)果殊橙,如果任務(wù)被取消辐宾,此方法不會被調(diào)用;
  • AsyncTask 內(nèi)部使用線程池執(zhí)行后臺任務(wù)膨蛮,使用Handler機(jī)制傳遞消息叠纹;
  • AsyncTask 的實(shí)例對象只能被執(zhí)行一次;
  • AsyncTask 對象可以通過調(diào)用executeOnExecutor()方法指定其執(zhí)行的Executor實(shí)現(xiàn)類敞葛,方便線程池的控制誉察;

分析

構(gòu)造方法

先看一下AsyncTask的構(gòu)造方法,構(gòu)造方法有三個惹谐,分別是:

public AsyncTask() {
    this((Looper) null);
}
public AsyncTask(@Nullable Handler handler) {
    this(handler != null ? handler.getLooper() : null);
}
public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
        ? getMainHandler()
        : new Handler(callbackLooper);

    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Result result = null;
            try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                result = doInBackground(mParams);
                Binder.flushPendingCommands();
            } catch (Throwable tr) {
                mCancelled.set(true);
                throw tr;
            } finally {
                postResult(result);
            }
            return result;
        }
    };

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

可以看到持偏,前面兩個構(gòu)造方法都是調(diào)用第三個構(gòu)造方法的。

構(gòu)造方法中氨肌,初始化了三個成員變量mHandler鸿秆、mWorker和mFuture。

初始化mHandler時怎囚,會判斷構(gòu)造方法是否傳入null卿叽、Handler對象或Looper對象,若有恳守,則當(dāng)前AsyncTask對象中的mHandler成員是主線程中的Handler對象(參考Handler的分析)附帽。mHandler對象只有在getHandler()方法中被調(diào)用:

private Handler getHandler() {
    return mHandler;
}

而getHandler()方法只在postResult(Result result)方法中被調(diào)用:

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

在postResult(Result result)中,獲得了一個Message對象并用當(dāng)前的mHandler發(fā)送出去井誉。

mWorker和mFuture是線程池相關(guān)的兩個類的實(shí)例。

AsyncTask 的構(gòu)造方法中的mWorker對象的call()方法整胃,這個方法顯示設(shè)置了當(dāng)前任務(wù)棧狀態(tài)颗圣,然后設(shè)置當(dāng)前線程優(yōu)先使用后臺線程,調(diào)用doInBackground(mParams)方法屁使,這個doInBackground(mParams)方法就是需要自行實(shí)現(xiàn)的方法在岂。把doInBackground(mParams)得到的結(jié)果賦值給result,最后調(diào)用postResult(result)發(fā)送出去蛮寂。這個postResult(result)就是構(gòu)造方法中的mHandler的發(fā)送消息方法蔽午。

mFuture對象的done()方法,嘗試把FutureTask實(shí)例的執(zhí)行結(jié)果拿到酬蹋,然后調(diào)用postResult(result)方法發(fā)送出去及老。在拿result的過程會判斷當(dāng)前任務(wù)是否被中斷抽莱、拋出異常執(zhí)行異常或被任務(wù)被取消骄恶,若有食铐,則執(zhí)行記錄、拋出異成常或發(fā)送空的result虐呻。

啟動異步任務(wù)

構(gòu)造好一個AsyncTask對象后,一般會調(diào)用其execute()方法來啟動任務(wù):

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

從源碼可以看到寞秃,這個方法會執(zhí)行在主線程中斟叼。如果在子線程中調(diào)用此方法,則子線程也會變成主線程春寿。例如我這么寫:

mTvFirst.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {

                AsyncTask asyncTask = new AsyncTask() {
                    @Override
                    protected Object doInBackground(Object[] objects) {
                        Log.d(LOG_TAG, "doInBackground當(dāng)前線程是" + Thread.currentThread().getName());
                        return null;
                    }

                    @Override
                    protected void onPreExecute() {
                        Log.d(LOG_TAG, "onPreExecute當(dāng)前線程是" + Thread.currentThread().getName());
                    }
                };
                asyncTask.execute();
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(LOG_TAG, "run當(dāng)前線程是" + Thread.currentThread().getName());
            }
        }).run();
    }
});

點(diǎn)擊該TextView后朗涩,打印出來的是:

04-17 04:25:32.478 5805-5805/com.erkang.gradlestudy D/EKwong: onPreExecute當(dāng)前線程是main
04-17 04:25:32.481 5805-5885/com.erkang.gradlestudy D/EKwong: doInBackground當(dāng)前線程是AsyncTask #1
04-17 04:25:37.479 5805-5805/com.erkang.gradlestudy D/EKwong: run當(dāng)前線程是main

可以看到,啟動AsyncTask的線程也是主線程堂淡,盡管是在子線程中調(diào)用的馋缅。

接下來我們看一下executeOnExecutor(sDefaultExecutor, params)方法:

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

這個方法用了final關(guān)鍵字,目的是不讓使用者進(jìn)行更改绢淀。

方法中萤悴,先判斷AsyncTask當(dāng)前狀態(tài)是否準(zhǔn)備狀態(tài),如果在運(yùn)行或已結(jié)束皆的,則拋出異常覆履。這說明同一個AsyncTask對象不能被啟動兩次,在運(yùn)行中狀態(tài)或已結(jié)束狀態(tài)都不可再次調(diào)用费薄。

然后把當(dāng)前狀態(tài)設(shè)置為運(yùn)行中硝全,回調(diào)onPreExecute()方法。onPreExecute()方法本身為空方法楞抡,在主線程中調(diào)用伟众,使用者可以重寫來實(shí)現(xiàn)自己想要的功能。

然后把參數(shù)賦值給mWorker召廷,調(diào)用Executor的實(shí)現(xiàn)類對象sDefaultExecutor的execute(Runnable command)方法凳厢。最后把AsyncTask本身返回。

SerialExecutor

這時候竞慢,我們看一下這個sDefaultExecutor對象是什么來的先紫,發(fā)現(xiàn)在代碼里是一個SerialExecutor對象:

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    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);
        }
    }
}

在這個SerialExecutor類中,有一個Runnable的隊(duì)列mTasks和一個具體的Runnable對象mActive筹煮。

在它的execute(final Runnable r)方法中遮精,先把本次execute()方法傳進(jìn)來的Runnable對象和一個scheduleNext()對象組裝成一個 新的Runnable對象,放到Runnable的隊(duì)列mTasks尾部败潦。判斷當(dāng)前mActive是否為空本冲,若為空准脂,則調(diào)用scheduleNext()方法。

接下來看scheduleNext()方法眼俊,先從mTasks頭部取出一個Runnable對象賦值給mActive意狠,若取到的值不為空,則讓THREAD_POOL_EXECUTOR執(zhí)行這個mActive對象疮胖。

mActive對象的run方法分兩步环戈,第一步是調(diào)用傳進(jìn)來的Runnable實(shí)現(xiàn)類的run()方法,第二步是繼續(xù)調(diào)用scheduleNext()方法澎灸。這是一個把mTasks隊(duì)列遍歷完的意思院塞。剛才我們傳進(jìn)來的Runnable實(shí)現(xiàn)類是mFuture對象,那么就是在這里調(diào)用mFuture對象的run()方法了性昭。

FutureTask

這時候我們打開mFuture的類FutureTask看一下里面的run()方法的實(shí)現(xiàn):

public void run() {
    if (state != NEW ||
        !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

開頭這個是當(dāng)前狀態(tài)是否為NEW和系統(tǒng)的內(nèi)存判斷拦止,finally后面也是一些系統(tǒng)的判斷,我們著重看中間try里面的內(nèi)容好了糜颠。

這里會調(diào)起FutureTask對象內(nèi)部的callable成員的call()方法汹族,如果調(diào)用成功,會把布爾值ran設(shè)置為true其兴。從構(gòu)造方法我們可以看到顶瞒,這個callable成員就是AsyncTask中的mWorker對象。也就是線程池THREAD_POOL_EXECUTOR所激活的時候元旬,會調(diào)用mWorker的call()方法榴徐。

在FutureTask的run()方法中的這個try的步驟后面,會對callable成員是否運(yùn)行成功做判斷匀归,如果為true的話坑资,調(diào)用set(result)。繼續(xù)點(diǎn)進(jìn)去這個在FutureTask的run()方法中的這個try的步驟后面穆端,會對callable成員是否運(yùn)行成功做判斷袱贮,如果為true的話,調(diào)用set(result)方法看体啰,對內(nèi)存狀態(tài)等進(jìn)行判斷后攒巍,調(diào)用了finishCompletion()方法。我們看一下這個finishCompletion()方法:

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (U.compareAndSwapObject(this, WAITERS, q, null)) {
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }

    done();

    callable = null;        // to reduce footprint
}

在這里狡赐,我們找到了這個done()方法,也就是在AsyncTask的構(gòu)造方法中初始化mFuture對象時復(fù)寫的done()方法了钦幔。

看到這里枕屉,我們就知道,在AsyncTask構(gòu)造方法中初始化的兩個成員mWorker和mFuture鲤氢,在某個任務(wù)中搀擂,是先執(zhí)行mWorker的call()方法西潘,然后才執(zhí)行mFuture的done()方法。

InternalHandler

前面將了那么哨颂,在各個步驟中調(diào)用哪些方法喷市,調(diào)用后怎么發(fā)送消息,那么威恼,消息被哪個Handler接收呢品姓?接收后怎么處理呢?我們順著AsyncTask的構(gòu)造方法箫措,可以找到這個InternalHandler類:

private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }

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

可以看到腹备,這個類會根據(jù)消息的類型,通知result去更新進(jìn)度或結(jié)果斤蔓。

關(guān)于

本文為簡單的學(xué)習(xí)筆記植酥,如有錯誤,請多多指出弦牡。
我的GitHub: https://github.com/EKwongChum

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末友驮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驾锰,更是在濱河造成了極大的恐慌卸留,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稻据,死亡現(xiàn)場離奇詭異艾猜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捻悯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門匆赃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人今缚,你說我怎么就攤上這事算柳。” “怎么了姓言?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵瞬项,是天一觀的道長。 經(jīng)常有香客問我何荚,道長囱淋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任餐塘,我火速辦了婚禮妥衣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己税手,他們只是感情好蜂筹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芦倒,像睡著了一般艺挪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兵扬,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天麻裳,我揣著相機(jī)與錄音,去河邊找鬼周霉。 笑死掂器,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的俱箱。 我是一名探鬼主播国瓮,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狞谱!你這毒婦竟也來了乃摹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤跟衅,失蹤者是張志新(化名)和其女友劉穎孵睬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伶跷,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掰读,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叭莫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹈集。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雇初,靈堂內(nèi)的尸體忽然破棺而出拢肆,到底是詐尸還是另有隱情,我是刑警寧澤靖诗,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布郭怪,位于F島的核電站,受9級特大地震影響刊橘,放射性物質(zhì)發(fā)生泄漏鄙才。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一促绵、第九天 我趴在偏房一處隱蔽的房頂上張望攒庵。 院中可真熱鬧据途,春花似錦、人聲如沸叙甸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裆蒸。三九已至,卻和暖如春糖驴,著一層夾襖步出監(jiān)牢的瞬間僚祷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工贮缕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辙谜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓感昼,卻偏偏與公主長得像装哆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子定嗓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容