關(guān)于 AsyncTask 的一次深度解析

前言##

任何一個Android 開發(fā)者對AsnycTask 都應(yīng)該不陌生;使用AsyncTask可以很方便的異步處理耗時操作暇赤;AsyncTask內(nèi)部對Handler和Thread進行了封裝心例,簡化了Handler的使用方式,使用起來非常方便鞋囊。首先看一下Android SDK 中關(guān)于AsyncTask的使用示例:

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

在onCreate中止后,執(zhí)行這個任務(wù)即可:

 new DownloadFilesTask().execute(url1, url2, url3);

使用起來,的確是很方便(當然和當下非常流行的Retrofit、Volley相比還是有些繁瑣)译株。
但是你想過下面這些問題嗎瓜喇;為什么doInBackground就可以用來進行耗時操作悦屏?為什么onPostExecute和onProgressUpdate就可以進行UI更新里覆?是誰規(guī)定了這些方法處于UI線程的?publishProgress 傳遞的參數(shù)是怎樣來到onProgressUpdate方法中的万栅?怎樣取消一個正在執(zhí)行的耗時操作匪补?如果你自己去封裝Handler和Thread你會怎么做伞辛?
好了,帶著這些疑問夯缺,讓我們?nèi)タ纯碅syncTask的實現(xiàn)原理蚤氏。

AsyncTask的實現(xiàn)原理可以說是十分巧妙,整個代碼除去注釋僅有300多行踊兜,但功能卻非常強大竿滨,原因就是他用到了Java自帶的并發(fā)工具包 java.util.concurrent,這個包包含有一系列能夠讓 Java 的并發(fā)編程變得更加簡單輕松的類捏境。好了為了方便理解AsyncTask的實現(xiàn)原理于游,先普及一波AsyncTask中用到一些java基礎(chǔ)知識。

基礎(chǔ)知識##

阻塞隊列 BlockingQueue####

BlockingQueue 通常用于一個線程生產(chǎn)對象垫言,而另外一個線程消費這些對象的場景贰剥。下圖是對這個原理的闡述:

一個線程往里邊放,另外一個線程從里邊取的一個 BlockingQueue骏掀。
一個線程將會持續(xù)生產(chǎn)新對象并將其插入到隊列之中鸠澈,直到隊列達到它所能容納的臨界點柱告。也就是說截驮,它是有限的。如果該阻塞隊列到達了其臨界點际度,負責生產(chǎn)的線程將會在往里邊插入新對象時發(fā)生阻塞葵袭。它會一直處于阻塞之中,直到負責消費的線程從隊列中拿走一個對象乖菱。
負責消費的線程將會一直從該阻塞隊列中拿出對象坡锡。如果消費線程嘗試去從一個空的隊列中提取對象的話,這個消費線程將會處于阻塞之中窒所,直到一個生產(chǎn)線程把一個對象丟進隊列鹉勒。

鏈阻塞隊列 LinkedBlockingQueue#####

LinkedBlockingQueue 類實現(xiàn)了 BlockingQueue 接口。
LinkedBlockingQueue 內(nèi)部以一個鏈式結(jié)構(gòu)(鏈接節(jié)點)對其元素進行存儲吵取。如果需要的話禽额,這一鏈式結(jié)構(gòu)可以選擇一個上限。如果沒有定義上限,將使用 Integer.MAX_VALUE 作為上限脯倒。
LinkedBlockingQueue 內(nèi)部以 FIFO(先進先出)的順序?qū)υ剡M行存儲实辑。隊列中的頭元素在所有元素之中是放入時間最久的那個,而尾元素則是最短的那個藻丢。

ArrayDeque####

數(shù)組隊列 ArrayDeque的特點

  • 大小自增長的隊列
  • 內(nèi)部使用數(shù)組存儲數(shù)據(jù)
  • 線程不安全
  • 內(nèi)部數(shù)組長度為8剪撬、16、32….. 2的n次方
  • 頭指針head從內(nèi)部數(shù)組的末尾開始悠反,尾指針tail從0開始残黑,在頭部插入數(shù)據(jù)時,head減一斋否,在尾部插入數(shù)據(jù)時萍摊,tail加一。當head==tail時說明數(shù)組的容量滿足不了當前的情況如叼,此時需要擴大容量為原來的二倍冰木。

執(zhí)行器服務(wù) ExecutorService####

java.util.concurrent.ExecutorService 接口表示一個異步執(zhí)行機制,使我們能夠在后臺執(zhí)行任務(wù)笼恰。因此一個 ExecutorService 很類似于一個線程池踊沸。

ExecutorService 簡單實現(xiàn)

ExecutorService executorService = Executors.newFixedThreadPool(10);  
  
executorService.execute(new Runnable() {  
    public void run() {  
        System.out.println("Asynchronous task");  
    }  
});  
  
executorService.shutdown();  

首先使用 newFixedThreadPool() 工廠方法創(chuàng)建一個 ExecutorService。這里創(chuàng)建了一個十個線程執(zhí)行任務(wù)的線程池社证。
然后逼龟,將一個 Runnable 接口的匿名實現(xiàn)類傳遞給 execute() 方法。這將導(dǎo)致 ExecutorService 中的某個線程執(zhí)行該 Runnable追葡。

線程池執(zhí)行者 ThreadPoolExecutor####

java.util.concurrent.ThreadPoolExecutor 是 ExecutorService 接口的一個實現(xiàn)腺律。ThreadPoolExecutor 使用其內(nèi)部池中的線程執(zhí)行給定任務(wù)(Callable 或者 Runnable)。

構(gòu)造方法:

//構(gòu)造方法
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

下面看看幾個參數(shù)的含義及作用

  • corePoolSize — 核心線程數(shù)宜肉,即允許閑置的線程數(shù)目
  • maximumPoolSize — 最大線程數(shù)匀钧,即這個線程池的容量
  • keepAliveTime — 非核心線程的閑置存活時間
  • unit — 上一個參數(shù)的單位
  • workQueue — 任務(wù)隊列(阻塞隊列)
  • threadFacotry — 線程創(chuàng)建工廠
  • handler — 當線程池或者任務(wù)隊列容量已滿時用于 reject

Callable&&Future####

提到Callable 可能覺得比較陌生,但是Runnable大家應(yīng)該很熟悉谬返;這么說吧之斯,Callable就是帶返回值的Runnable。Callable聲明如下:

public interface Callable<V> {  
    /** 
     * Computes a result, or throws an exception if unable to do so. 
     * 
     * @return computed result 
     * @throws Exception if unable to compute a result 
     */  
    V call() throws Exception;  
} 

返回值就是Callable 傳入的泛型參數(shù)的類型遣铝。而這個返回值就由Future獲取佑刷。

FutureTask####

FutureTask則是一個RunnableFuture<V>,而RunnableFuture實現(xiàn)了Runnbale又實現(xiàn)了Futrue<V>這兩個接口

public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable, Future<V> {  
    /** 
     * Sets this Future to the result of its computation 
     * unless it has been cancelled. 
     */  
    void run();  
}

另外它還可以包裝Runnable和Callable<V>酿炸, 由構(gòu)造函數(shù)注入依賴瘫絮。

public FutureTask(Callable<V> callable) {  
    if (callable == null)  
        throw new NullPointerException();  
    this.callable = callable;  
    this.state = NEW;       // ensure visibility of callable  
}

下面用一個簡單的demo,介紹一下Callable與FutureTask的使用

/**
 * 定義一個Callable 任務(wù)填硕,返回類型為Integer
 */
public class CallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int hours=5;
        int amount = 0;
        while(hours>0){
            System.out.println("I'm working,rest is "+hours);
            amount++;
            hours--;
            Thread.sleep(1000);
        }
        return amount;
    }
}

public class FutureTaskTest {
    
    public static void main(String args[]) throws ExecutionException {
        CallableTask worker = new CallableTask();
        FutureTask<Integer> mTasks = new FutureTask<>(worker);
        new Thread(mTasks).start();

        while (!mTasks.isDone()) {
            try {
                System.out.println("job has't finsished ..." + mTasks.get());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        int amount;
        try {
            amount = mTasks.get();
            System.out.println("Job finished commited " + amount);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


    }
}

我們看一下輸出日志:

main 方法通過一個新的線程開始執(zhí)行mTasks 麦萤,而mTasks 又會等待worker 的執(zhí)行完畢,得到其最終執(zhí)行的結(jié)果。

好了關(guān)于基礎(chǔ)知識的了解到這里就差不多了频鉴。

AsyncTask 屬性##

有了一些基礎(chǔ)知識栓辜,首先讓我們看一下AsyncTask類的聲明:

public abstract class AsyncTask<Params, Progress, Result> {
    private static final String LOG_TAG = "AsyncTask";
    //獲取當前的cpu核數(shù)
    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;

    //ThreadFactory 線程工廠,通過工廠方法newThread來獲取新線程
    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());
        }
    };
    //創(chuàng)建一個鏈式阻塞隊列垛孔,默認大小128
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     * 假設(shè)當前所使用手機CPU為4核藕甩,那么將創(chuàng)建一個
     * 線程池核心容量為5
     * 線程池最大容量為9
     * 非核心線程空閑時間為1秒
     * 任務(wù)隊列大小為128 ---------- 的線程池(執(zhí)行器),執(zhí)行并發(fā)任務(wù)
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     * 串行任務(wù)執(zhí)行器聲明
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    //消息類型:發(fā)送結(jié)果
    private static final int MESSAGE_POST_RESULT = 0x1;
    //消息類型: 發(fā)送過程
    private static final int MESSAGE_POST_PROGRESS = 0x2;
    // 設(shè)置默認執(zhí)行器為串行執(zhí)行
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    // 內(nèi)部Handler實現(xiàn)
    private static InternalHandler sHandler;
    
    private final WorkerRunnable<Params, Result> mWorker;
    //獲取mWorker 執(zhí)行結(jié)果周荐;結(jié)束mWorker等功能
    private final FutureTask<Result> mFuture;
    //任務(wù)默認狀態(tài)為掛起
    private volatile Status mStatus = Status.PENDING;
    //使用原子Boolean型變量狭莱,標記當前任務(wù)是否已被取消
    private final AtomicBoolean mCancelled = new AtomicBoolean();
    //標記當前任務(wù)是否已被執(zhí)行過
    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
    
    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);
            }
        }
    }

    /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }
} 

部分屬性已通過注釋標記,應(yīng)該很容易理解概作;下面主要看一下以下幾個聲明的具體含義:

SerialExecutor 實現(xiàn)###

SerialExecutor 串行執(zhí)行的線程池腋妙,在其內(nèi)部首先創(chuàng)建一個隊列用于存儲AsyncTask任務(wù);其中execute方法的作用就是將需要執(zhí)行的AsyncTask任務(wù)添加到這個隊列尾部讯榕,并立即執(zhí)行骤素,執(zhí)行完畢后調(diào)用scheduleNext 從隊列頭部獲取下一個任務(wù),如果這個任務(wù)不為空愚屁;就將任務(wù)交給THREAD_POOL_EXECUTOR去處理济竹;這樣如果同時有多個AsyncTask執(zhí)行時,多個添加到mTasks的任務(wù)將通過輪詢的方式逐一執(zhí)行完畢霎槐,完成串行執(zhí)行的功能送浊。

WorkerRunnable###

這個WorkRunnable又是什么呢?看看他的聲明:

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

這個類繼承了Callable丘跌,并添加了一個參數(shù)袭景;我們知道Callable的參數(shù)類型是其call方法的返回類型,那么這個Params參數(shù)又有什么意義呢闭树,這個我們后面再看耸棒。

最后使用枚舉定義了任務(wù)的幾個狀態(tài);PENDING(掛起)蔼啦,RUNNING (正在運行)榆纽,F(xiàn)INISHED(已完成)。之前的代碼也可以看到捏肢,默認狀態(tài)為PENDING。

好了饥侵,至此已經(jīng)完成了AsyncTask 類當中鸵赫,大部分屬性的介紹,下面就來看看他的實現(xiàn)原理躏升。

AsyncTask 實現(xiàn)原理##

回到我們一開始提到的那個示例代碼辩棒,當我們定義了好自己的AsyncTask之后,要開始運行這個任務(wù)時非常簡單,只需要一行代碼:

new DownloadFilesTask().execute(url1, url2, url3);

我們就從這行代碼出發(fā)一睁,看看發(fā)生了什么钻弄。

構(gòu)造方法###

首先,new DownloadFileTask() 者吁,執(zhí)行DownloadFileTask的構(gòu)造方法窘俺,因此必然會執(zhí)行DownloadFileTask的父類AsyncTask的構(gòu)造方法,也就是 AsyncTask() :

    /**
     * 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 {
                //設(shè)置當前任務(wù)已被執(zhí)行
                mTaskInvoked.set(true);
                //設(shè)置線程執(zhí)行的優(yōu)先級
                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);
                }
            }
        };
    }

構(gòu)造方法的工作很簡單复凳,就是完成了mWorker 和 mFuture 的初始化工作瘤泪,也就是Callable和Future 的初始化,并關(guān)聯(lián)他們育八,讓mFuture 可以獲取mWorker 的執(zhí)行結(jié)果对途,或者停止mWorker 的執(zhí)行。

這里主要由兩個方法call()和done()髓棋,總的來說當mFuture 開始被執(zhí)行的時候实檀,call() 就會執(zhí)行,當這個任務(wù)執(zhí)行完畢后done()方法就會執(zhí)行按声。

那么這個mFuture 什么 時候會被執(zhí)行呢劲妙?繼續(xù)往下看

任務(wù)開始真正執(zhí)行###

new DownloadFilesTask().execute(url1, url2, url3);

繼續(xù)看,上面已經(jīng)完成了構(gòu)造方法的執(zhí)行儒喊,接下來看execute() 方法:

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

executeOnExecutor

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

到這里就很清楚了镣奋,mStatus 默認狀態(tài)為PENDING,因此任務(wù)開始執(zhí)行后首先將其狀態(tài)改為RUNNING怀愧;同時從異常判斷我們也可以看出一個AsyncTask的execute方法不能同時執(zhí)行兩次侨颈。

接下來,onPreExecute()芯义,我們是在onCreate 中開啟了AsyncTask的任務(wù)哈垢,因此這個時候,依舊屬于主線程扛拨,onPreExecute()方法也會工作在主線程耘分,我們可以在這個方法中執(zhí)行一些預(yù)備操作,初始相關(guān)內(nèi)容绑警。

mWorker求泰,前面已經(jīng)說過他就是實現(xiàn)了Callable接口,并添加了一個參數(shù)屬性计盒,在這里我們把executor中傳入的參數(shù)賦給了這個屬性渴频。exec=sDefaultExecutor=SerialExecutor ,這里任務(wù)就開始真正的執(zhí)行了北启;按照之前所說就會開始執(zhí)行mFuture這個任務(wù)卜朗,因此就會開始執(zhí)行mWorker的call方法拔第。

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                //設(shè)置當前任務(wù)已被執(zhí)行
                mTaskInvoked.set(true);
                //設(shè)置線程執(zhí)行的優(yōu)先級,被設(shè)置在后臺進行
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }

到這里场钉,我們終于看到了熟悉的doInBackground蚊俺,這是我們必須實現(xiàn)的一個方法,在其中完成耗時操作逛万,并返回結(jié)果泳猬。由于已經(jīng)設(shè)置了Process的優(yōu)先級,因此這個方法會處于后臺進程泣港。
在doInBackground 里暂殖,我們還可以返回當前執(zhí)行進度:

     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

我們調(diào)用了publishProgress 可以將doInBackground中耗時任務(wù)的進度發(fā)送出去,大家都知道這個進度會發(fā)送到onProgressUpdate() 方法中当纱,在onProgressUpdate我們可以方便的進行UI 更新呛每,比如進度條進度更新等。那么他是怎么實現(xiàn)的呢坡氯?這就要看publishProgress這個方法的實現(xiàn)了晨横。

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

我們再來分解一下這個方法的實現(xiàn):

   //返回一個InternalHandler對象
    private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }
AsyncTaskResult#####

AsyncTaskResult 顧名思義,很好理解了箫柳,就是AsyncTask的執(zhí)行結(jié)果手形,這是一個靜態(tài)的內(nèi)部類,包括兩個屬性mTask和mData 悯恍。

    private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

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

因此publishProgress中 new AsyncTaskResult 就是創(chuàng)建了一個AsyncTaskResult,他的兩個兩個屬性為當前的AsyncTask和任務(wù)任務(wù)執(zhí)行進度库糠。

到這里的邏輯很清楚了,如果當前任務(wù)沒有被取消涮毫, 那么就從消息池中獲取一個Message的實例瞬欧,同時設(shè)置這個Message對象的msg.what=MESSAGE_POST_PROGRESS,msg.obj為一個AsyncTaskResult對象,最后執(zhí)行sendToTarget方法罢防,通過之前對Handler實現(xiàn)機制的了解艘虎,我們知道sendXXX方法殊途同歸,所完成的任務(wù)都是將Message對象插入到MessageQueue當中咒吐,等著Looper的loop方法一個個取出野建。由于我們是在主線程開啟了AsyncTask任務(wù)的執(zhí)行,因此恬叹,一旦我們將一個消息插入到隊列候生,那么就會執(zhí)行Handler的handleMessage方法。下面就來看看你這個InternalHandler 的實現(xiàn)妄呕。

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

哈哈陶舞,很簡單;在handleMessage中首先取出結(jié)果绪励,并強制轉(zhuǎn)換為AsyncTaskResult對象肿孵,在msg.what=MESSAGE_POST_PROGRESS時,就會執(zhí)行result.mTask.onProgressUpdate(result.mData);
mTask 就是當前AsyncTask疏魏,因此就會執(zhí)行AsyncTask中聲明的onProgressUpdate方法停做。這樣,就把參數(shù)從一個子線程傳遞到了UI 線程大莫,非常方便開發(fā)人員用這個完成相關(guān)業(yè)務(wù)蛉腌。

我們再回到mWorker 的call() 方法中,當doInBackground執(zhí)行完畢后只厘,最后就會執(zhí)行postResult烙丛。

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

這個方法和publishProgress邏輯一樣,懂事把result 封裝到一個AsyncTaskResult 對象中羔味,做為一個Message對象的obj屬性插入到MessageQueue中河咽,只不過msg.what=MESSAGE_POST_RESULT.

這樣就會來到InternalHandler 的handleMessage中,這一次msg.what=MESSAGE_POST_RESULT.時執(zhí)行result.mTask.finish(result.mData[0]);

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

這個方法也很簡單赋元,任務(wù)未取消時忘蟹,onPostExecute(result) 方法被執(zhí)行。這個onPostExecute(result)就是我們最后要執(zhí)行的方法搁凸,在這個方法中得到最終的執(zhí)行結(jié)果媚值;并將任務(wù)狀態(tài)標記為FINISHED。

到這里护糖,終于完成了對AsyncTask常規(guī)用法的總結(jié)褥芒。先緩口氣。嫡良。锰扶。。皆刺。少辣。,如果再來一遍你會發(fā)現(xiàn)羡蛾,有個東西好像被遺忘了漓帅,什么呢?就是mFuture痴怨,我們在mWorker的call 執(zhí)行到最后時忙干,通過postResult方法將結(jié)果返回到了Handler中,依然實現(xiàn)了完成了任務(wù)浪藻,那么這個mFuture 還有什么用呢捐迫?我們再看一篇AsyncTask的構(gòu)造方法

    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                //設(shè)置當前任務(wù)已被執(zhí)行
                mTaskInvoked.set(true);
                .....省略。爱葵。施戴。反浓。
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                  。赞哗。雷则。。
                 省略肪笋。月劈。。藤乙。
            }
        };
    }

         private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }   

我們看一下這段代碼猜揪,就會發(fā)現(xiàn)這個postResultIfNotInvoked 內(nèi)部的if語句條件不滿足,什么都不會執(zhí)行坛梁,也就意味著mFuture的done 這個方法也什么都不會做而姐。這段代碼的確十分詭異,不知道是何用意罚勾∫闳耍或者說是我沒理解其中的奧義。╮(╯_╰)╭尖殃。
這么說來丈莺,搞了半天這個mFuture貌似沒什么卵用,其實也不是送丰〉薅恚回到我們一開始提出的一個問題,如何去終止一個正在執(zhí)行的耗時任務(wù)器躏,這個問題說白了就是如何去停止一個線程俐载。通過一個外部方法讓一個正在運行的線程停止其實應(yīng)該是一件很講究的事情,這個時候mFuture 就可以大顯身手了登失。

    public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }

AsyncTask cancel的實現(xiàn)遏佣,是靠mFuture 完成的。

這樣揽浙,平時使用AsyncTask時状婶,常用的幾個方法是怎樣實現(xiàn)的我們都了解了。

玩一玩AsyncTask

通過上面的內(nèi)容馅巷,我們已經(jīng)了解了AsyncTask的實現(xiàn)機制膛虫。下面通過一個demo來感受一下AsyncTask的奧秘。

    class MyAsyncTask extends AsyncTask<String, Integer, String> {

        @Override
        protected String doInBackground(String... params) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            publishProgress();
            return params[0];


        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.CHINA);
            Log.e(TAG, s + "execute finish at " + df.format(new Date()));
        }
    }

這里定義一個AsyncTask任務(wù)钓猬,實現(xiàn)邏輯很簡單稍刀,間隔一秒返回執(zhí)行任務(wù)時傳進來的參數(shù),在onPostExecute中打印方法執(zhí)行時間和執(zhí)行結(jié)果敞曹。

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_at);
        result = (TextView) findViewById(R.id.result);
        findViewById(R.id.execute).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "onClick: " + Runtime.getRuntime().availableProcessors());
                for (int i = 0; i < 127; i++) {
                    new MyAsyncTask().execute("Task#" + i);

                }

            }
        });
    }

我們通過一個for 循環(huán)會執(zhí)行127個MyAsyncTask任務(wù)账月,看一下日志:

AsyncTask 默認串行

很明顯综膀,一秒一個,默認就是串行執(zhí)行捶障。前面提到了ArrayDeque 是一個自增長的隊列僧须。因此纲刀,默認情況下项炼,可以創(chuàng)建無數(shù)個AsyncTask任務(wù)。

這127個任務(wù)串行執(zhí)行下去示绊,要等到他們都執(zhí)行完畢锭部,得2分多鐘,不行我不想等了面褐,我想要并行拌禾,那也不難,雖然
AsyncTask默認的執(zhí)行器是串行執(zhí)行展哭,但是我們可以這樣做:

                for (int i = 0; i < 127; i++) {
                   new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "AsyncTask#" + i);
                }

我們可以去修改execute 的實現(xiàn)方法湃窍,替換默認的sDefaultExecutor為AsyncTask.THREAD_POOL_EXECUTOR,再次執(zhí)行我們看一下日志:

并行

可以看到每一秒有5個線程同時執(zhí)行匪傍,這樣127個線程用26秒就可以執(zhí)行完了您市,比串行執(zhí)行快了差不多5倍。為什么是5倍呢役衡?這就要回到我們之前AsyncTask 屬性定義當中了茵休。
在定義并行執(zhí)行的線程池當時, CORE_POOL_SIZE = CPU_COUNT + 1; 線程池核心程數(shù)為CPU 核心數(shù)+1手蝎,因此在我當前的4核手機上最大線程數(shù)為5榕莺。

串行執(zhí)行的時候由于ArrayDeque 的優(yōu)點,可以依次執(zhí)行很多個任務(wù)棵介,那并行呢钉鸯?這里先說一下結(jié)論,以CPU 核心數(shù)為4的手機為例邮辽,最多一次可以執(zhí)行137個任務(wù)唠雕。這個也很好理解,
線程池最大容量為9逆巍,阻塞隊列容量為128及塘,也就說在并行的時候,最多允許137個任務(wù)被阻塞锐极,再多就不行了笙僚。

修改之前的代碼:

                for (int i = 0; i < 138; i++) {
                   new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "AsyncTask#" + i);
                }

運行程序時,會拋出異常:

java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@1429681 rejected from java.util.concurrent.ThreadPoolExecutor@bfb9326[Running, pool size = 9, active threads = 9, queued tasks = 128, completed tasks = 0]
public interface RejectedExecutionHandler {

    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

很明顯灵再,這次是因為我們要執(zhí)行的任務(wù)數(shù)已經(jīng)超過了線程池能夠承載的最多量肋层,因此拋出了異常亿笤。

最后##

AsyncTask 的實現(xiàn)說起來很簡單,就是封裝了Handler+Thread栋猖,但是其細節(jié)部分的實現(xiàn)净薛,有許多地方值得去深究。寫代碼的思路值得借鑒蒲拉。

AsyncTask 這個類的代碼在不同版本的SDK 有著些許差異肃拜,以上分析是基于 Android 6.0 ,也就是Android SDK 23 得出雌团。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末燃领,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子锦援,更是在濱河造成了極大的恐慌猛蔽,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灵寺,死亡現(xiàn)場離奇詭異曼库,居然都是意外死亡,警方通過查閱死者的電腦和手機略板,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門毁枯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蚯根,你說我怎么就攤上這事后众。” “怎么了颅拦?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵蒂誉,是天一觀的道長。 經(jīng)常有香客問我距帅,道長右锨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任碌秸,我火速辦了婚禮绍移,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讥电。我一直安慰自己蹂窖,他們只是感情好,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布恩敌。 她就那樣靜靜地躺著瞬测,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上月趟,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天灯蝴,我揣著相機與錄音,去河邊找鬼孝宗。 笑死穷躁,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的因妇。 我是一名探鬼主播问潭,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沙峻!你這毒婦竟也來了睦授?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤摔寨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后怖辆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體是复,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年竖螃,在試婚紗的時候發(fā)現(xiàn)自己被綠了淑廊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡特咆,死狀恐怖季惩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腻格,我是刑警寧澤画拾,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站菜职,受9級特大地震影響青抛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜酬核,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一蜜另、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫡意,春花似錦举瑰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春邮屁,著一層夾襖步出監(jiān)牢的瞬間整袁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工佑吝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坐昙,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓芋忿,卻偏偏與公主長得像炸客,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子戈钢,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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