Android AsyncTask解析

??在Android中,如果想要在子線程里進行UI操作,就需要借助Android的異步消息處理機制。為了更加方便在子線程中更新UI元素,Android從1.5版本開始引入了一個AsyncTask類,使用它就可以非常靈活方便的從子線程切換到UI線程。

??AsyncTask很早就出現(xiàn)在Android的API里了,所以我相信大多數(shù)朋友對它的用法都已經(jīng)非常熟悉。不過今天我還是準(zhǔn)備從AsyncTask的基本用法開始講起,然后我們再來一起分析下AsyncTask源碼,看看它是如何實現(xiàn)的。

AsyncTsk的基本用法

首先AsyncTask是一個抽象類,所以如果我們想使用它,就必須要創(chuàng)建一個子類去繼承它。在繼承時我們可以為AsyncTask類指定三個泛型參數(shù):

  • Params
    在執(zhí)行AsyncTask時需要傳入的參數(shù),可用于在后臺任務(wù)中使用。
  • Progress
    后臺任務(wù)執(zhí)行時,如果需要在界面上顯示當(dāng)前的進度,則使用這里指定的泛型作為進度單位。
  • Result
    當(dāng)任務(wù)執(zhí)行完畢后,如果需要對結(jié)果進行返回,則是以這里指定的泛型作為返回值類型。

因此,一個最簡單的自定義AsyncTask就可以寫成如下方式:

class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
       ......
}

這里我們把AsyncTask的第一個泛型參數(shù)指定為Void,表示在執(zhí)行AsyncTask的時候不需要傳入?yún)?shù)給后臺任務(wù)。第二個泛型參數(shù)指定為Integer,表示使用整型數(shù)據(jù)來作為進度顯示單位。第三個泛型參數(shù)指定為Boolean,則表示使用布爾型數(shù)據(jù)來反饋執(zhí)行結(jié)果。

當(dāng)然,目前我們自定義的DownloadTask還是一個空任務(wù),并不能進行任何實際的操作,我們還需要去重寫AsyncTask中的幾個方法才能完成對任務(wù)的定制。經(jīng)常需要去重寫的方法有以下四個:

  1. onPreExecute()

這個方法會在后臺任務(wù)開始執(zhí)行之間調(diào)用,用于進行一些界面上的初始化操作,比如顯示一個進度條對話框等。

  1. doInBackground(Params...)

這個方法中的所有代碼都會在子線程中運行,我們應(yīng)該在這里去處理所有的耗時任務(wù)。任務(wù)一旦完成就可以通過return語句來將任務(wù)的執(zhí)行結(jié)果進行返回,如果AsyncTask的第三個泛型參數(shù)指定的是Void,就可以不返回任務(wù)執(zhí)行結(jié)果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當(dāng)前任務(wù)的執(zhí)行進度,可以調(diào)用publishProgress(Progress...)方法來完成。

  1. onProgressUpdate(Progress...)

當(dāng)在后臺任務(wù)中調(diào)用了publishProgress(Progress...)方法后,這個方法就很快會被調(diào)用,方法中攜帶的參數(shù)就是在后臺任務(wù)中傳遞過來的。在這個方法中可以對UI進行操作,利用參數(shù)中的數(shù)值就可以對界面元素進行相應(yīng)的更新。

  1. onPostExecute(Result)

當(dāng)后臺任務(wù)執(zhí)行完畢并通過return語句進行返回時,這個方法就很快會被調(diào)用。返回的數(shù)據(jù)會作為參數(shù)傳遞到此方法中,可以利用返回的數(shù)據(jù)來進行一些UI操作,比如說提醒任務(wù)執(zhí)行的結(jié)果,以及關(guān)閉掉進度條對話框等。

因此,一個比較完整的自定義AsyncTask就可以寫成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
 
    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }
 
    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = doDownload();
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }
 
    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setMessage("當(dāng)前下載進度:" + values[0] + "%");
    }
 
    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss();
        if (result) {
            Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();
        }
    }
}

這里我們模擬了一個下載任務(wù),在doInBackground()方法中去執(zhí)行具體的下載邏輯,在onProgressUpdate()方法中顯示當(dāng)前的下載進度,在onPostExecute()方法中來提示任務(wù)的執(zhí)行結(jié)果。如果想要啟動這個任務(wù),只需要簡單地調(diào)用以下代碼即可:

new DownloadTask().execute();

注意:

  • 必須在UI線程中創(chuàng)建和調(diào)用AsyncTask實例。
  • 永遠不應(yīng)該調(diào)用在AsyncTask類中重寫的方法。他們被自動調(diào)用
  • AsyncTask只能被調(diào)用一次。再次執(zhí)行它會拋出異常

以上就是AsyncTask的基本用法,怎么樣,是不是感覺在子線程和UI線程之間進行切換變得靈活了很多?我們并不需求去考慮什么異步消息處理機制,也不需要專門使用一個Handler來發(fā)送和接收消息,只需要調(diào)用一下publishProgress()方法就可以輕松地從子線程切換到UI線程了。

分析AsyncTask的源碼

雖然AsyncTask這么簡單好用,但你知道它是怎樣實現(xiàn)的嗎?那么接下來,我們就來分析一下AsyncTask的源碼,對它的實現(xiàn)原理一探究竟。注意這里我選用的是Android 4.0的源碼,如果你查看的是其它版本的源碼,可能會有一些出入。

從之前DownloadTask的代碼就可以看出,在啟動某一個任務(wù)之前,要先new出它的實例,因此,我們就先來看一看AsyncTask構(gòu)造函數(shù)中的源碼,如下所示:

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return postResult(doInBackground(mParams));
        }
    };
    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                final Result result = get();
                postResultIfNotInvoked(result);
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occured while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            } catch (Throwable t) {
                throw new RuntimeException("An error occured while executing "
                        + "doInBackground()", t);
            }
        }
    };
}

這段代碼雖然看起來有點長,但實際上并沒有任何具體的邏輯會得到執(zhí)行,只是初始化了兩個變量,mWorker和mFuture,并在初始化mFuture的時候?qū)Worker作為參數(shù)傳入。mWorker是一個Callable對象,mFuture是一個FutureTask對象,這兩個變量會暫時保存在內(nèi)存中,稍后才會用到它們。

接著如果想要啟動某一個任務(wù),就需要調(diào)用該任務(wù)的execute()方法,因此現(xiàn)在我們來看一看execute()方法的源碼,如下所示:


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

簡單的有點過分了,只有一行代碼,僅是調(diào)用了executeOnExecutor()方法,那么具體的邏輯就應(yīng)該寫在這個方法里了,快跟進去瞧一瞧:

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

果然,這里的代碼看上去才正常點??梢钥吹?,在第15行調(diào)用了onPreExecute()方法,因此證明了onPreExecute()方法會第一個得到執(zhí)行。可是接下來的代碼就看不明白了,怎么沒見到哪里有調(diào)用doInBackground()方法呢?別著急,慢慢找總會找到的,我們看到,在第17行調(diào)用了Executor的execute()方法,并將前面初始化的mFuture對象傳了進去,那么這個Executor對象又是什么呢?查看上面的execute()方法,原來是傳入了一個sDefaultExecutor變量,接著找一下這個sDefaultExecutor變量是在哪里定義的,源碼如下所示:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
……
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

可以看到,這里先new出了一個SERIAL_EXECUTOR常量,然后將sDefaultExecutor的值賦值為這個常量,也就是說明,剛才在executeOnExecutor()方法中調(diào)用的execute()方法,其實也就是調(diào)用的SerialExecutor類中的execute()方法。那么我們自然要去看看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類中也有一個execute()方法,這個方法里的所有邏輯就是在子線程中執(zhí)行的了,注意這個方法有一個Runnable參數(shù),那么目前這個參數(shù)的值是什么呢?當(dāng)然就是mFuture對象了,也就是說在第9行我們要調(diào)用的是FutureTask類的run()方法,而在這個方法里又會去調(diào)用Sync內(nèi)部類的innerRun()方法,因此我們直接來看innerRun()方法的源碼:

void innerRun() {
    if (!compareAndSetState(READY, RUNNING))
        return;
    runner = Thread.currentThread();
    if (getState() == RUNNING) { // recheck after setting thread
        V result;
        try {
            result = callable.call();
        } catch (Throwable ex) {
            setException(ex);
            return;
        }
        set(result);
    } else {
        releaseShared(0); // cancel
    }
}

可以看到,在第8行調(diào)用了callable的call()方法,那么這個callable對象是什么呢?其實就是在初始化mFuture對象時傳入的mWorker對象了,此時調(diào)用的call()方法,也就是一開始在AsyncTask的構(gòu)造函數(shù)中指定的,我們把它單獨拿出來看一下,代碼如下所示:

public Result call() throws Exception {
    mTaskInvoked.set(true);
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    return postResult(doInBackground(mParams));
}

在postResult()方法的參數(shù)里面,我們終于找到了doInBackground()方法的調(diào)用處,雖然經(jīng)過了很多周轉(zhuǎn),但目前的代碼仍然是運行在子線程當(dāng)中的,所以這也就是為什么我們可以在doInBackground()方法中去處理耗時的邏輯。接著將doInBackground()方法返回的結(jié)果傳遞給了postResult()方法,這個方法的源碼如下所示:


private Result postResult(Result result) {
    Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

如果你已經(jīng)熟悉了異步消息處理機制,這段代碼對你來說一定非常簡單吧。這里使用sHandler對象發(fā)出了一條消息,消息中攜帶了MESSAGE_POST_RESULT常量和一個表示任務(wù)執(zhí)行結(jié)果的AsyncTaskResult對象。這個sHandler對象是InternalHandler類的一個實例,那么稍后這條消息肯定會在InternalHandler的handleMessage()方法中被處理。InternalHandler的源碼如下所示:

private static class InternalHandler extends Handler {
    @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;
        }
    }
}

這里對消息的類型進行了判斷,如果這是一條MESSAGE_POST_RESULT消息,就會去執(zhí)行finish()方法,如果這是一條MESSAGE_POST_PROGRESS消息,就會去執(zhí)行onProgressUpdate()方法。那么finish()方法的源碼如下所示:

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

可以看到,如果當(dāng)前任務(wù)被取消掉了,就會調(diào)用onCancelled()方法,如果沒有被取消,則調(diào)用onPostExecute()方法,這樣當(dāng)前任務(wù)的執(zhí)行就全部結(jié)束了。

我們注意到,在剛才InternalHandler的handleMessage()方法里,還有一種MESSAGE_POST_PROGRESS的消息類型,這種消息是用于當(dāng)前進度的,調(diào)用的正是onProgressUpdate()方法,那么什么時候才會發(fā)出這樣一條消息呢?相信你已經(jīng)猜到了,查看publishProgress()方法的源碼,如下所示:

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

非常清晰了吧!正因如此,在doInBackground()方法中調(diào)用publishProgress()方法才可以從子線程切換到UI線程,從而完成對UI元素的更新操作。其實也沒有什么神秘的,因為說到底,AsyncTask也是使用的異步消息處理機制,只是做了非常好的封裝而已。

各版本AsyncTask之間的差異

各版本AsyncTask之間的差異主要集中在線程池的使用這一塊。主要的分界點有2個,分為3個階段。第一階段是3.0以前的版本,第二是3.0-4.4版本的階段,第三個是4.4版本以后的階段。那我們一個一個階段來看。

3.0之前版本中AsyncTask

這里使用2.3版本的AsyncTask源代碼:


private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 128;
    private static final int KEEP_ALIVE = 1;

    private static final BlockingQueue<Runnable> sWorkQueue =
            new LinkedBlockingQueue<Runnable>(10);

    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 ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

在3.0版本以前AsyncTask的線程只有1個線程池,核心線程數(shù)為5,最大線程數(shù)為128,任務(wù)隊列容量為10。
也就是說當(dāng)線程池中的線程數(shù)量沒到5個,那么有新的任務(wù)會直接啟動一個核心線程來執(zhí)行任務(wù),如果線程池中的線程數(shù)量達到了5個,然后任務(wù)會被插入到任務(wù)隊列中等待執(zhí)行,要是任務(wù)隊列也滿了,就會判斷線程池中的數(shù)量是否已經(jīng)達到最大線程數(shù)128,如果沒有達到就會立刻啟動一個非核心線程來執(zhí)行任務(wù)。如果線程數(shù)量已經(jīng)達到線程池規(guī)定的最大值,那么就會拒絕執(zhí)行該任務(wù)。也就是說該線程池最多能同時接納138個任務(wù),其中有128個任務(wù)可以同時執(zhí)行。而且該版本只有一個execute(Params... params) 方法,說明不能自定義線程池來執(zhí)行任務(wù)。

3.0 - 4.4版本中AsyncTask

這里使用4.2版本的AsyncTask源代碼:

private static final int CORE_POOL_SIZE = 5;
    private static final int MAXIMUM_POOL_SIZE = 128;
    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>(10);

    /**
     * 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);
 //以下為新增部分
  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);
            }
        }
    }

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

這個版本的線程池與上個版本并沒有什么不同,只是新增加了一個SerialExecutor,從代碼中我們可以看到這是一個順序執(zhí)行任務(wù)的Executor,雖然最后任務(wù)還是交給了THREAD_POOL_EXECUTOR來執(zhí)行,但是使用這個Executor可以保證任務(wù)時按先進先出的順序來執(zhí)行。同時新增加了executeOnExecutor(Executor exec,Params... params) 方法,這個方法被聲明是public的并且接受一個Executor參數(shù),說明我們可以自定義線程池或者使用SerialExecutor來執(zhí)行任務(wù)。如果使用execute()方法的話,默認(rèn)會使用SerialExecutor來執(zhí)行任務(wù)。

4.4版本以后AsyncTask

這里使用4.4版本的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);

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

4.4版本以后的線程池數(shù)量改為了動態(tài)的,以雙核心為例,先獲取CPU的核心數(shù)為2,線程池的核心線程為3,最大線程數(shù)為5,而阻塞隊列的容量變?yōu)榱?28。為什么會有這樣的改動?可能谷歌公司覺得開啟的線程數(shù)過多會影響效率吧。而阻塞隊列從容量為10變?yōu)榱?28是一個很有意思的事情。在4.4以前的版本,如果已經(jīng)達到了線程池的核心線程數(shù)5,切阻塞隊列也達到了10,再有任務(wù)加入,就會啟動新的非核心線程,也就是說只要同時又16個任務(wù)進入就會開啟非核心線程。而現(xiàn)在需要132(3+128+1)個任務(wù)加入才會開啟非核心線程。也就是說要開啟新的線程的成本更大了。

好了,以上就是AsyncTask的詳細介紹

參考文章:
Android AsyncTask完全解析,帶你從源碼的角度徹底理解
AsyncTask各版本源碼分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容