從源碼解析AsyncTask注意事項

AsyncTask是Android中除Handler外另一種方便處理耗時任務后的執(zhí)行UI操作的工具類爪幻。其實這個說法本身有一定歧義怨咪,因為TsyncTask內(nèi)部原理還是用到了Handler,可以說TsyncTask是Handler和線程使用的封裝后的工具截碴。
AsyncTask有以下幾個注意事項

注意事項:

1.類的加載需要在主線程中
2.對象要在主線程中創(chuàng)建
3.execute在主線程中執(zhí)行
4.不直接調用onPostExecute,onProgressUpdate忿偷,onPostExecute,doInBackground等方法
5.execute只能執(zhí)行一次
6.Android3.0以后串行執(zhí)行

源碼解析

這些注意事項后續(xù)都會結合AsyncTask的源碼進行解析臊泌。首先來看構造方法:

public AsyncTask() {
    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);
        }
    };

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

mWorker是一個WorkerRunnable鲤桥,WorkerRunnable是一個實現(xiàn)Callable的抽象類,內(nèi)部定義了Params[] mParams;變量渠概。這個mWorker其實就是執(zhí)行耗時任務的子線程茶凳。mFture就是mWorker的FutureTask。AsyncTask的執(zhí)行需要調用execute()方法播揪,然后調用了executeOnExecutor方法贮喧。

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

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

首先會判斷AsyncTask的mStatus,RUNNING和FINISHED都會報錯剪芍,一旦調用該方法狀態(tài)就會是RUNNING塞淹,AsyncTask執(zhí)行完后mStatus就會是FINISHED,這就解釋了注意事項5:execute只能執(zhí)行一次罪裹。onPreExecute()方法需要執(zhí)行在doInBackground之前的UI操作需要在UI線程中饱普,所以executeOnExecutor方法必須要在UI線程中。這就解釋了注意事項3:execute在主線程中執(zhí)行状共。sDefaultExecutor的execute(mFuture)繼續(xù)執(zhí)行異步類的執(zhí)行操作套耕。sDefaultExecutor是SerialExecutor對象,從命名上可以猜測sDefaultExecutor的一個單執(zhí)行的串行線程池峡继。還是從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 內(nèi)部定義了名為mTasks的ArrayDeque來存儲線程冯袍,可以從execute方法中看出,每次執(zhí)行SerialExecutor 只是從mTasks按順序取出一個線程轉交給THREAD_POOL_EXECUTOR去執(zhí)行碾牌】捣撸可以看出AsyncTask有2個線程池分別是SerialExecutor和THREAD_POOL_EXECUTOR。SerialExecutor不做具體的線程執(zhí)行操作而是每次取出一個線程給THREAD_POOL_EXECUTOR只做為“排隊線程池“”使用舶吗,而THREAD_POOL_EXECUTOR才是執(zhí)行線程操作的“執(zhí)行線程池”征冷。這就解釋了注意事項:6THREAD_POOL_EXECUTOR是AsyncTask的靜態(tài)變量。

public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

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;

從上述參數(shù)可以知道THREAD_POOL_EXECUTOR的核心線程數(shù)的CPU的量誓琼,最大線程數(shù)的2倍CPU數(shù)量+1检激,非核心線程的閑置超時時長為1秒。
THREAD_POOL_EXECUTOR執(zhí)行的構造方法的mFuture腹侣。這就需要重新回看構造方法叔收,主要分析下mWorker:

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

這里可以發(fā)現(xiàn)doInBackground()方法,所以doInBackground()本質上還是運行在子線程中去執(zhí)行耗時任務傲隶。doInBackground()方法執(zhí)行完之后應該需要將UI數(shù)據(jù)傳給UI線程去更新UI饺律,postResult()方法接受了result數(shù)據(jù),所以這里應該就說執(zhí)行向UI線程傳遞的任務伦籍±渡梗看下postResult()是如何實現(xiàn)該功能的腮出。

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

Handler!AsyncTask的消息傳遞也是使用的Handler芝薇。既然用的Handler胚嘲,如果要做UI操作,Handler必須要在主線程中創(chuàng)建洛二。而sHandler是靜態(tài)成員變量馋劈,在類加載的時候已經(jīng)初始化了,這就解釋了注意事項1:類的加載需要在主線程中晾嘶。

private static InternalHandler sHandler = new 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;
        }
    }
}

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

這里Handler做了2個處理妓雾,一是更新精度,這個msg.what的通過publishProgress()方法觸發(fā)垒迂;二是處理耗時計算完成或者結束的情況械姻。執(zhí)行結束的話就會調用result.mTask.onPostExecute()方法。result.mTask就是AsyncTask机断。onPostExecute()方法需要在主線程中調用萎战,這解釋了注意事項2:AsyncTask對象要在主線程中創(chuàng)建州藕。至于注意事項4:不直接調用onPostExecute换棚,onProgressUpdate浓领,onPostExecute,doInBackground等方法奋蔚。這個比較好理解她混,因為這些方法在AsyncTask內(nèi)部都已經(jīng)調用了,手動調用會打亂AsyncTask的工作流程泊碑。

潛在問題

AsyncTask的內(nèi)存泄漏原理和Handler類似坤按。具體處理就不詳細說明了。有些讀者可能說在Activity的onDestory()中將AsyncTask取消掉馒过,這里就講些下AsyncTask的取消AsyncTask.cancel(mayInterruptIfRunning);晋涣。調用cancel方法并不能真正立即把task取消掉,而只是把task的狀態(tài)置為Cancel而已,可以通過isCancelled()方法來判斷,然后在doingbackground或其他方法中判斷是否被取消,然后做相應的處理,具體根據(jù)開發(fā)者需要去編寫沉桌。
需要注意的是調用的cancel方法不會執(zhí)行onPostExecute(result),而是onCancelled(result)算吩。另一個問題就是在屏幕旋轉等造成Activity重新創(chuàng)建時AsyncTask數(shù)據(jù)丟失的問題留凭。當Activity銷毀并創(chuàng)新創(chuàng)建后,還在運行的AsyncTask會持有一個Activity的非法引用即之前的Activity實例偎巢。導致onPostExecute()沒有任何作用蔼夜。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市压昼,隨后出現(xiàn)的幾起案子求冷,更是在濱河造成了極大的恐慌瘤运,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匠题,死亡現(xiàn)場離奇詭異拯坟,居然都是意外死亡,警方通過查閱死者的電腦和手機韭山,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門郁季,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钱磅,你說我怎么就攤上這事梦裂。” “怎么了盖淡?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵年柠,是天一觀的道長。 經(jīng)常有香客問我褪迟,道長冗恨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任牵咙,我火速辦了婚禮派近,結果婚禮上,老公的妹妹穿的比我還像新娘洁桌。我一直安慰自己渴丸,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布另凌。 她就那樣靜靜地躺著谱轨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吠谢。 梳的紋絲不亂的頭發(fā)上土童,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音工坊,去河邊找鬼献汗。 笑死,一個胖子當著我的面吹牛王污,可吹牛的內(nèi)容都是我干的罢吃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼昭齐,長吁一口氣:“原來是場噩夢啊……” “哼尿招!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤就谜,失蹤者是張志新(化名)和其女友劉穎怪蔑,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丧荐,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡缆瓣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了篮奄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捆愁。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖窟却,靈堂內(nèi)的尸體忽然破棺而出昼丑,到底是詐尸還是另有隱情,我是刑警寧澤夸赫,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布菩帝,位于F島的核電站,受9級特大地震影響茬腿,放射性物質發(fā)生泄漏呼奢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一切平、第九天 我趴在偏房一處隱蔽的房頂上張望握础。 院中可真熱鬧,春花似錦悴品、人聲如沸禀综。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽定枷。三九已至,卻和暖如春届氢,著一層夾襖步出監(jiān)牢的瞬間欠窒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工退子, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留岖妄,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓寂祥,卻偏偏與公主長得像衣吠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子壤靶,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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