天天看點

Asynctask 不一樣的解讀

Asynctask

先看看官宣也就是AsyncTask的類注釋,這部分非常重要:

AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask可以适當且友善地使用UI線程。此類允許您執行背景操作并在UI線程上釋出結果,而無需操作線程和handler。

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) …

AsyncTask旨在成為{@link Thread}和{@link Handler}的助手類,并不構成通用的線程架構。理想情況下,AsyncTasks應該用于短操作(最多幾秒鐘)。

When first introduced, AsyncTasks were executed serially on a single background thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single thread to avoid common application errors caused by parallel execution.

首次引入時,AsyncTasks在單個背景線程上串行執行。 從Android 1.6開始,這被改為一個線程池,允許多個任務并行運作。 從Android 3.0開始,任務在單個線程上執行,以避免由并行執行引起的常見應用程式錯誤。

簡要的解讀一下注釋:

  • Asynctask可以讓你在異步的同時簡單的更新UI線程,而不是使用Thread和Handler這種複雜的組合。
  • 不構成通用的線程架構。asynctask應該用于短時間的CPU密集型操作。
  • 首次引入時,AsyncTasks在單個背景線程上串行執行。 從Android 1.6開始,這被改為一個線程池,允許多個任務并行運作。 從Android 3.0開始,任務在單個線程上執行,以避免由并行執行引起的常見應用程式錯誤。

實作原理

Asynctask集合了FutureTask、Handler、LinkedBlockingQueue、ArrayDeque…優點。有時間可逐一學習此處安且不表。

FutureTask的優點:

下面這兩點是Thread無法直接實作的。

  • 線程結束可以将結果傳回
  • 線程可取消

Handler優點:

  • 實作了Android線程間的通信

LinkedBlockingQueue優點

  • 線程安全的雙端隊列,遵循FIFO原則,因為隊列的特性他的增删效率很高(數組和連結清單的差別)。

Asynctask的優缺點

優點

  • Asynctask重寫doInBackGround方法就能實作異步任務,可以說是最簡單的任務并發方式,并且在onProgressUpdate/onPostExecute中能直接更新UI。
  • Asynctask作為内部類的時候能直接通路外部類的UI控件,這一點比用Thread和handler要少寫無數行代碼。

缺點

  • 線程池等待隊列大小已經設定好了128,當建立快于銷毀容易超出這個範圍。網絡謠傳,從源碼上看不出這個值會起到作用。
  • Asynctask直接持有activity或fragment,執行超長時間的任務容易記憶體洩漏,簡短的洩漏可忽略。
  • 一個項目中所有的AysncTask都是串行執行,對的你沒看錯,當你想要高并發卻發現自己在串行的單行道上時,簡直有一萬頭神獸奔過。

綜上所述:官方不建議用asynctask來替代Thread來執行超長時間的任務,如果需要的話還是用Thread或者FeatureTask。

源碼解讀

程式猿之間沒有什麼是用代碼溝通不了的,如果有那就加上注釋。

幾個重要屬性

public final WorkerRunnable<Params, Result> mWorker
  • 這個屬性實作了

    java.util.concurrent.Callable

    ,這是AsyncTask能被取消的原因之一。
public final FutureTask mFuture
  • FutureTask<V> implements RunnableFuture<V>

    雖然他繼承了Runnable但是他隻做狀态管理,詳細見源碼。
public static final Executor THREAD_POOL_EXECUTOR;
  • 這是一個線程執行排程器,這個線程池申明為常量類型,說明一個項目中所有的AsyncTask的子類公用一個線程池。
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
  • 這又是一個線程執行排程器,不僅static還volatile說明了這個排程器的原子性。再看看他的名字SERIAL_EXECUTOR,串行執行器。
public static final BlockingQueue sPoolWorkQueue
  • 這是一個線程安全的雙端隊列,遵循FIFO原則,因為隊列的特性他的增删效率很高(數組和連結清單的差別)。
final ArrayDeque mTasks = new ArrayDeque();
  • 這是

    SerialExecutor

    中的一個對象,他是一個并非線程安全的雙端隊列,遵循FIFO原則(繼承自Deque雙端隊列都FIFO)。

下面我們來看看兩個線程排程器和兩個任務隊列之間的故事。

常見的屬性就不聊了,直接看重點。

sPoolWorkQueue

// 線程保活時間
private static final int KEEP_ALIVE_SECONDS = 30;
// 建立任務管理隊列
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
// 建立線程池
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
// 判斷是否要移出隊列中的任務
Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
           

解讀一下:

我覺得網上說,快速建立AsyncTask并執行會導緻崩潰的原因是隻看到了

workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)

這半句,當存活時間超過30秒則移出隊列。其實隻要超過sPoolWorkQueue隊列上限就會阻塞在加入隊列之外,同時每次從sPoolWorkQueue擷取任務時,判斷隊列長度達到63就開始移出隊首的任務。

SerialExecutor

/**
     * 一個{@link Executor},它按順序執行一個任務。 此序列化對于特定過程是全局的。
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

	private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    // AsyncTask.excute最終執行的是這個方法,
    //這個方法整體加了同步鎖,也就是說execute()方法是串行執行的
    public synchronized void execute(final Runnable r) {
        // 建立一個新Runnable對象,該對象才是外部線程池中真正執行的任務
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    //新Runnable對象運作的時候,FutureTask當作普通對象運作
                    r.run();
                } finally {
         //FutureTask當作普通對象運作,是以隻有當FutureTask的run方法執行完,
         //才加入讓下一個任務進入到外部的排程器執行
                    scheduleNext();
                }
            }
        });
        /* 第一次的加入任務後,mActive始終不為空也就是說,
        隻有在上面新任務運作時,上一個FutureTask任務執行結束了,
        才會把下一個任務加入到THREAD_POOL_EXECUTOR排程器去管理
         這樣就保證了
         當最後一個任務執行完,這個時候mActive為空,再次嘗試查找一下
        */ 
        if (mActive == null) {
            scheduleNext();
        }
    }
	//SerialExecutor移出最前面的任務,讓THREAD_POOL_EXECUTOR排程器去管理
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

           

解讀一下:

  1. FutureTask當作普通對象用。
  2. 不管你同時建立多少個Task并執行,最終都是串行執行的。
  3. 既然是串行的,那sPoolWorkQueue的大小設定為128,如果同時加入的任務過多會導緻崩潰應該是謠言。

有圖為證:

Asynctask 不一樣的解讀

消息機制和方法調用

// 所有的事件都在工作線程通知到asyncTask的handler
    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
        // 更新狀态時通知UI線程,onProgressUpdate()方法中能直接更新UI原因在此
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
        // 更新狀态時通知UI線程,onProgressUpdate()方法中能直接更新UI原因在此
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
    // 将InternalHandler初始化為主線程的handler
    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

           

之是以

onPostExecute()

onProgressUpdate()

方法中能直接更新UI原因是因為本身已經封裝好了Handler。

應用

// 同時執行多個任務
new MyTask().execute("任務1","任務二","任務三".......);

  class MyTask extends AsyncTask<String,String,String>{

        @Override
        protected String doInBackground(String... strings) {
            // 執行完成任務,可以用publishProgress觸發onPostExecute            
            publishProgress("asdf");
            // 任務進度更新,可以用publishProgress觸發onProgressUpdate  
            publishProgress("");
            return null;
        }


        @Override
        protected void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
        }
    }
           

錯誤用法

場景:把多張圖放到多個線程執行壓縮操作壓縮完之後通知主線程,使用AsyncTask偷個懶,代碼如下方式:

for(int i=0;i<9;i++){
    new MyTask().execute("任務"+i);
}
           

然後我們看看結果:

雖然是在多線程上運作,但是确實串行的按順序執行。這樣不僅沒有性能優化,反而因為建立線程帶來了額外的開銷。是以說AsyncTask隻能是Thread和Handle的輔助工具,并不能進行替換。

Asynctask 不一樣的解讀

總結

官方不建議用asynctask來替代Thread來執行超長時間的任務,如果需要的話還是用Thread或者FeatureTask。

有多處與網上的主流觀點相悖,如有錯誤歡迎打臉。

繼續閱讀