深入解析AsyncTask的原理

博文出處:深入解析AsyncTask的原理,歡迎大家關注我的博客妆毕,謝謝慎玖!

前言

在初學 Android 的時候,AsyncTask 應該是大家都比較熟悉的笛粘。我們都知道 AsyncTask 可以在后臺開啟一個異步的任務,當任務完成后可以更新在 UI 上。而在 AsyncTask 中薪前,比較常用的方法有: onPreExecute 润努、 doInBackground 、 onPostExecute 和 onProgressUpdate 等示括。而上述的方法中除了 doInBackground 運行在子線程中铺浇,其他的都是運行在主線程的,相信大家對這幾個方法也了如指掌了垛膝。

在這里先劇透一下鳍侣, AsnycTask 原理就是“線程池 + Handler”的組合。如果你對Handler消息傳遞的機制不清楚吼拥,那么可以查看我上一篇的博文:《探究Android異步消息的處理之Handler詳解》倚聚,里面會有詳細的介紹。那么接下來凿可,就一起來看看 AsyncTask 實現(xiàn)的原理吧惑折!

class

public abstract class AsyncTask<Params, Progress, Result> {
    ...
}

AsyncTask 為抽象類,其中有三個泛型枯跑。這三個泛型相信大家都很熟悉了惨驶,第一個是傳遞的參數(shù)類型,第二個是任務執(zhí)行的進度敛助,第三個是任務執(zhí)行的結果類型了粗卜。如果其中某個不需要可以傳入Void。

線程池

那么我們先來看看 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<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(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);

從上面我們可以看到纳击,該線程池(即 THREAD_POOL_EXECUTOR)的核心線程數(shù)為 CPU 的核心數(shù)量 + 1续扔,最大線程數(shù)為 CPU 的核心數(shù)量 * 2 + 1,過剩線程的存活時間為1s评疗。這里要注意的是 sPoolWorkQueue 是靜態(tài)阻塞式的隊列灰追,意味著所有的 AsyncTask 用的都是同一個 sPoolWorkQueue ,也就是說最大的容量為128個任務婉烟,若超過了會拋出異常习霹。最后一個參數(shù)就是線程工廠了,用來制造線程加匈。

我們再來看看 AsyncTask 內部的任務執(zhí)行器 SERIAL_EXECUTOR 存璃,該執(zhí)行器用來把任務傳遞給上面的 THREAD_POOL_EXECUTOR 線程池。在 AsyncTask 的設計中雕拼,SERIAL_EXECUTOR 是默認的任務執(zhí)行器纵东,并且是串行的,也就導致了在 AsyncTask 中任務都是串行地執(zhí)行啥寇。當然偎球,AsyncTask 也是支持任務并行執(zhí)行的洒扎,這個點我們在下面再講。

/**
 * 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 volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

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 的內部看到衰絮,是循環(huán)地取出 mActive 袍冷,并且把 mActive 放置到上面的 THREAD_POOL_EXECUTOR 中去執(zhí)行。這樣就導致了任務是串行地執(zhí)行的猫牡。

Handler

講完了線程池胡诗,那么剩下的就是 Handler 了。下面是 AsyncTask 內部實現(xiàn)的一個 Handler :

private static InternalHandler sHandler;

private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;

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

@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
    final AsyncTask mTask;
    final Data[] mData;

    AsyncTaskResult(AsyncTask task, Data... data) {
        mTask = task;
        mData = data;
    }
}

在源碼中淌友,有一個靜態(tài)的 sHandler 煌恢,還有定義了兩條消息的類型。一條表示傳送結果震庭,另一條表示傳送進度瑰抵。再來分析一下 InternalHandler 的源碼:在 InternalHandler 的 handleMessage 方法中,根據(jù)消息類型分別有不同的處理归薛。其中的 result.mTask 可以從 AsyncTaskResult 類中看到谍憔,就是 AsyncTask 本身。而后邊的方法有一些眼熟啊主籍。其中 finish 應該是在任務結束時回調的习贫,若任務完成會回調 onPostExecute 方法,否則會回調 onCancelled 方法千元;而消息類型為 MESSAGE_POST_PROGRESS 中的 onProgressUpdate 方法不就是我們在使用 AsyncTask 時重寫的那個方法么苫昌。在這里我們已經可以看出點端倪了。但是我們先賣個關子幸海,繼續(xù)向下看祟身。

構造器

再來看看 AsyncTask 的構造器:

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
    
/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 */
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);
            }
        }
    };
}

@WorkerThread
protected abstract Result doInBackground(Params... params);

@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

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<Result>(this, result));
    message.sendToTarget();
    return result;
}

private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }
        return sHandler;
    }
}

在構造器中創(chuàng)建了 WorkerRunnable 和 FutureTask 的對象,而在 WorkerRunnable 內部的 call 方法中會去執(zhí)行需要我們重寫的 doInBackground 方法物独。而如果在 doInBackground 方法中調用 publishProgress 方法袜硫,就會使用發(fā)送消息到 sHandler 的 handleMessage 方法,之后就調用了 onProgressUpdate 方法了挡篓,具體可見上面的 InternalHandler 中的代碼婉陷。最后如果任務結束了在 postResult 中發(fā)送消息給 sHandler ,就是要在 handleMessage 中拿到消息并且執(zhí)行上面分析過的 finish 方法了官研。

execute

最后我們來講講 execute 方法秽澳,以下是源碼:

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return 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;
}

@MainThread
protected void onPreExecute() {
}

在上面中可以看到我們平常使用的 execute 方法會去調用 executeOnExecutor 方法。而在 executeOnExecutor 方法內又會去調用 onPreExecute 方法戏羽。這也就是為什么 onPreExecute 方法是在任務開始運行之前調用的原因了担神。這里要注意的是,如果想讓 AsyncTask 并行地去執(zhí)行任務始花,那么可以在 executeOnExecutor 方法中傳入一個并行的任務執(zhí)行器妄讯,這樣就達到了并行的效果孩锡。

總結

好了,AsyncTask 的源碼大致就這些了捞挥,也分析地差不多了浮创。我們總共分成了 class 忧吟、線程池砌函、Handler 、構造器和 execute 五部分來分析溜族。這樣可能會給人比較散亂的感覺讹俊,但是連起來看就會對 AsyncTask 的原理更加了解了。那么煌抒,下面我們就來總結一下吧:

  • AsyncTask 的線程池的線程數(shù)量是和 CPU 的核心數(shù)相關的仍劈。而線程池的隊列是阻塞式的并且是有限的,最大容量為128寡壮。這也意味著 AsyncTask 不適合于做一些大型的贩疙、長期在后臺運行的任務。因為這樣可能導致著隊列的溢出况既,會拋出異常这溅。所以 AsyncTask 適合于一些小型的任務。

  • onProgressUpdate棒仍、 onCancelled 和 onPostExecute 等都是通過 handler 的消息傳遞機制來調用的悲靴。所以 AsyncTask 可以理解為“線程池 + Handler”的組合。

  • 在 execute 方法中莫其,會先去調用 onPreExecute 方法癞尚,之后再在線程池中執(zhí)行 mFuture 。這時會調用 doInBackground 方法開始進行任務操作乱陡。 mWorker 和 mFuture 都是在構造器中初始化完成的浇揩。

  • AsyncTask 支持多線程進行任務操作,默認為單線程進行任務操作憨颠。

今天就到這里了胳徽,下次再見!

Goodbye ~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末烙心,一起剝皮案震驚了整個濱河市膜廊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淫茵,老刑警劉巖爪瓜,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匙瘪,居然都是意外死亡铆铆,警方通過查閱死者的電腦和手機蝶缀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薄货,“玉大人翁都,你說我怎么就攤上這事×禄” “怎么了柄慰?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長税娜。 經常有香客問我坐搔,道長,這世上最難降的妖魔是什么敬矩? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任概行,我火速辦了婚禮,結果婚禮上弧岳,老公的妹妹穿的比我還像新娘凳忙。我一直安慰自己,他們只是感情好禽炬,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布涧卵。 她就那樣靜靜地躺著,像睡著了一般瞎抛。 火紅的嫁衣襯著肌膚如雪艺演。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天桐臊,我揣著相機與錄音胎撤,去河邊找鬼。 笑死断凶,一個胖子當著我的面吹牛伤提,可吹牛的內容都是我干的。 我是一名探鬼主播认烁,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼肿男,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了却嗡?” 一聲冷哼從身側響起舶沛,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎窗价,沒想到半個月后如庭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡撼港,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年坪它,在試婚紗的時候發(fā)現(xiàn)自己被綠了骤竹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡往毡,死狀恐怖蒙揣,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情开瞭,我是刑警寧澤懒震,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站惩阶,受9級特大地震影響挎狸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜断楷,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崭别。 院中可真熱鬧冬筒,春花似錦、人聲如沸茅主。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诀姚。三九已至响牛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赫段,已是汗流浹背呀打。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留糯笙,地道東北人贬丛。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像给涕,于是被迫代替她去往敵國和親豺憔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容