AsyncTask非同步任務類

瀟湘劍雨發表於2019-01-19

目錄介紹

  • 01.先看下AsyncTask用法
  • 02.AsyncTask原始碼深入分析
    • 2.1 構造方法原始碼分析
    • 2.2 看execute(Params... params)方法
    • 2.3 mWorker和mFuture的建立過程
  • 03.非同步機制的實現
  • 04.不同的SDK版本區別
  • 05.AsyncTask的缺陷和問題
    • 5.1 AsyncTask對應執行緒池
    • 5.2 AsyncTask生命週期問題
    • 5.3 AsyncTask記憶體洩漏問題
    • 5.4 AsyncTask結果丟失問題
    • 5.5 AsyncTask並行還是序列問題

好訊息

  • 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計47篇[近20萬字],轉載請註明出處,謝謝!
  • 連結地址:github.com/yangchong21…
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!

問題答疑

  • AsyncTask是如何實現非同步機制的,底層原理是什麼?
  • AsyncTask呼叫execute方法時,如果不是執行在主執行緒中會出現什麼情況,如何解決?
  • 為什麼非同步任務物件不能執行多次,即不能建立一個物件執行多次execute方法?
  • doInBackground這個方法可以做什麼操作?它是在主執行緒中還是工作執行緒中?為什麼?
  • AsyncTask任務是否可以被中途取消?為什麼?
  • AsyncTask對應執行緒池是如何操作的?它有什麼弊端,為什麼現在幾乎很少用呢?
  • AsyncTask的執行策略是並行還是序列的?
  • 帶著問題去看這篇文章,相信看完之後你對異常AsyncTask有了初步理解……

01.先看下AsyncTask用法

  • 來看一下AsyncTask的基本使用,程式碼如下所示
    • 定義了自己的MyAsyncTask並繼承自AsyncTask;並重寫了其中的是哪個回撥方法:onPreExecute(),onPostExecute(),doInBackground();
    class MyAsyncTask extends AsyncTask<Integer, Integer, Integer> {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.i(TAG, "onPreExecute...(開始執行後臺任務之前)");
        }
    
        @Override
        protected void onPostExecute(Integer i) {
            super.onPostExecute(i);
            Log.i("TAG", "onPostExecute...(開始執行後臺任務之後)");
        }
    
        @Override
        protected Integer doInBackground(Integer... params) {
            Log.i(TAG, "doInBackground...(開始執行後臺任務)");
            return 0;
        }
    }
    複製程式碼
  • 開始呼叫非同步任務
    new MyAsyncTask().execute();
    複製程式碼

02.AsyncTask原始碼深入分析

2.1 構造方法原始碼分析

  • 原始碼如下所示,主要是看AsyncTask(@Nullable Looper callbackLooper)中的程式碼
    • 這裡面只是初始化了兩個成員變數:mWorker和mFuture他們分別是:WorkerRunnable和FutureTask,對於熟悉java的逗比應該知道這兩個類其實是java裡面執行緒池先關的概念。
    • 非同步任務的構造方法主要用於初始化執行緒池先關的成員變數
    //建立一個新的非同步任務。必須在UI執行緒上呼叫此建構函式
    public AsyncTask() {
        this((Looper) null);
    }
    
    //建立一個新的非同步任務。必須在UI執行緒上呼叫此建構函式
    public AsyncTask(@Nullable Handler handler) {
        this(handler != null ? handler.getLooper() : null);
    }
    
    public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);
    
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return 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);
                }
            }
        };
    }
    複製程式碼

2.2 看execute(Params... params)方法

  • 看一下execute方法

    • 發現該方法中新增一個@MainThread的註解,通過該註解,可以知道我們在執行AsyncTask的execute方法時,只能在主執行緒中執行
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
    複製程式碼
  • 如果execute方法不是執行在主執行緒中會出現什麼情況呢?

    • 執行,但是並沒有什麼區別,程式還是可以正常執行。但是onPreExecute方法是與開始執行的execute方法是在同一個執行緒中的,所以如果在子執行緒中執行execute方法,一定要確保onPreExecute方法不執行重新整理UI的方法,否則將會丟擲異常。
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.i("tag", Thread.currentThread().getId() + "");
            new MAsyncTask().execute();
        }
    }).start();
    Log.i("tag", "mainThread:" + Thread.currentThread().getId() + "");
    
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        //更新UI
        title.setText("瀟湘劍雨");
        Log.i(TAG, "onPreExecute...(開始執行後臺任務之前)");
    }
    複製程式碼
    • 異常如下所示,在子執行緒中執行execute方法,那麼這時候如果在onPreExecute方法中重新整理UI,會報錯,即子執行緒中不能更新UI。
    Process: com.example.aaron.helloworld, PID: 659
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    複製程式碼
  • 接著看看executeOnExecutor這個方法原始碼

    • 具體的內部實現方法裡:首先判斷當前非同步任務的狀態,其內部儲存非同步任務狀態的成員變數mStatus的預設值為Status.PENDING,所以第一次執行的時候並不丟擲這兩個異常,那麼什麼時候回進入這個if判斷並丟擲異常呢,通過檢視原始碼可以知道,當我們執行了execute方法之後,如果再次執行就會進入這裡的if條件判斷並丟擲異常
    • 在executeOnExecutor中若沒有進入異常分之,則將當前非同步任務的狀態更改為Running,然後回撥onPreExecute()方法,這裡可以檢視一下onPreExecute方法其實是一個空方法,主要就是為了用於我們的回撥實現,同時這裡也說明了onPreExecute()方法是與execute方法的執行在同一執行緒中。
  • 然後將execute方法的引數賦值給mWorker物件那個,最後執行exec.execute(mFuture)方法,並返回自身。

    • image
  • 模擬測試一下丟擲異常的操作

    • 看到我們定義了一個AsyncTask的物件,並且每次執行點選事件的回撥方法都會執行execute方法,當我們點選第一次的時候程式正常執行,但是當我們執行第二次的時候,程式就崩潰了。
    final MyAsyncTask mAsyncTask = new MyAsyncTask();
    title.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Log.i("tag", Thread.currentThread().getId() + "");
                    mAsyncTask.execute();
                }
            }).start();
            Log.i("tag", "mainThread:" + Thread.currentThread().getId() + "");
        }
    });
    複製程式碼
    • 若這時候第一次執行的非同步任務尚未執行完成則會丟擲異常:
    Cannot execute task:the task is already running.
    複製程式碼
    • 若第一次執行的非同步任務已經執行完成,則會丟擲異常:
    Cannot execute task:the task has already been executed (a task can be executed only once)
    複製程式碼
  • 然後看一下exec.execute(mFuture)的實現

    • 這裡的exec其實是AsyncTask定義的一個預設的Executor物件:
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    複製程式碼
    • 那麼,SERIAL_EXECUTOR又是什麼東西呢?
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    複製程式碼
    • 繼續檢視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);
            }
        }
    }
    複製程式碼
    • 可以發現其繼承Executor類其內部儲存著一個Runnable列表,即任務列表,在剛剛的execute方法中執行的exec.execute(mFuture)方法就是執行的這裡的execute方法。
    • 這裡具體看一下execute方法的實現:
      • 1)首先呼叫的是mTasks的offer方法,即將非同步任務儲存至任務列表的隊尾
      • 2)判斷mActive物件是不是等於null,第一次執行是null,然後呼叫scheduleNext()方法
      • 3)在scheduleNext()這個方法中會從佇列的頭部取值,並賦值給mActive物件,然後呼叫THREAD_POOL_EXECUTOR去執行取出的取出的Runnable物件。
      • 4)在這之後如果再有新的任務被執行時就等待上一個任務執行完畢後才會得到執行,所以說同一時刻只會有一個執行緒正在執行。
      • 5)這裡的THREAD_POOL_EXECUTOR其實是一個執行緒池物件。

2.3 構造方法中mWorker和mFuture的建立過程

  • 看一下執行過程中mWorker的執行邏輯:
    • 可以看到在執行執行緒池的任務時,我們回撥了doInBackground方法,這也就是我們重寫AsyncTask時重寫doInBackground方法是後臺執行緒的原因。
    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的執行邏輯
    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);
            }
        }
    };
    複製程式碼
    • 這裡具體看一下postResultIfNotInvoked方法:
    private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }
    複製程式碼
    • 其內部還是呼叫了postResult方法:
    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
    複製程式碼
    • 這裡可以看到起呼叫了內部的Handler物件的sendToTarget方法,傳送非同步訊息

03.非同步機制的實現

  • 看AsyncTask內部定義了一個Handler物件
    • 內部的handleMessage方法,有兩個處理邏輯,分別是:更新進入條和執行完成,這裡的更新進度的方法就是我們重寫AsyncTask方法時重寫的更新進度的方法,這裡的非同步任務完成的訊息會呼叫finish方法
    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:
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
    複製程式碼
  • 然後看看呼叫finish方法做了什麼
    • 首先會判斷當前任務是否被取消,若被取消的話則直接執行取消的方法,否則執行onPostExecute方法,也就是我們重寫AsyncTask時需要重寫的非同步任務完成時回撥的方法。
    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
    複製程式碼
  • 既然有處理訊息的,那麼肯定有傳送訊息的。
    • 可以從構造方法中看到,當通過執行doInBackground方法拿到結果後,最後在finally執行傳送該訊息邏輯
    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
    複製程式碼
    • 可以看到MESSAGE_POST_PROGRESS這個訊息傳送是處理進度,需要在工作執行緒中
    @Override
    protected ReusableBitmap doInBackground(Void... params) {
        // enqueue the 'onDecodeBegin' signal on the main thread
        publishProgress();
        return decode();
    }
    
    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
    複製程式碼

04.不同的SDK版本區別

  • 呼叫AsyncTask的execute方法不能立即執行程式的原因析及改善方案通過查閱官方文件發現,AsyncTask首次引入時,非同步任務是在一個獨立的執行緒中順序的執行,也就是說一次只執行一個任務,不能並行的執行,從1.6開始,AsyncTask引入了執行緒池,支援同時執行5個非同步任務,也就是說時只能有5個執行緒執行,超過的執行緒只能等待,等待前的執行緒某個執行完了才被排程和執行。換句話說,如果個程式中的AsyncTask例項個數超過5個,那麼假如前5都執行很長時間的話,那麼第6個只能等待機會了。這是AsyncTask的一個限制,而且對於2.3以前的版本無法解決。如果你的應用需要大量的後臺執行緒去執行任務,那麼只能放棄使用AsyncTask,自己建立執行緒池來管理Thread。不得不說,雖然AsyncTask較Thread使用起來方便,但是它最多隻能同時執行5個執行緒,這也大大侷限了它的作用,你必須要小心設計你的應用,錯開使用AsyncTask時間,盡力做到分時,或者保證數量不會大於5個,否就會遇到上次提到的問題。可能是Google意識到了AsynTask的侷限性了,從Android3.0開始對AsyncTask的API做出了一些調整:每次只啟動一個執行緒執行一個任務,完了之後再執行第二個任務,也就是相當於只有一個後臺線在執行所提交的任務。

05.AsyncTask的缺陷和問題

5.1 AsyncTask對應執行緒池

  • Asynctask對應的執行緒池ThreadPoolExecutr都是程式範圍內共享的,都是static的,所以是Asynctask控制著程式範圍內所有的子類例項。由於這個限制的存在,當使用預設執行緒池時,如果執行緒數超過執行緒池的最大容量,執行緒池就會爆掉(3.0後預設序列執行,不會出現個問題)。針對這種情況,可以嘗試自定義執行緒池,配合Asynctask使用。
  • 關於預設執行緒池:
    • AsyncTask裡面執行緒池是一個核心執行緒數為CPU + 1,最大執行緒數為CPU * 2 + 1,工作佇列長度為128的執行緒池,執行緒等待佇列的最大等待數為28,但是可以自定義執行緒池。執行緒池是由AsyncTask來處理的,執行緒池允許tasks並行執行,需要注意的是併發情況下資料的一致性問題,新資料可能會被老資料覆蓋掉類似volatile變數。所以希望tasks能夠序列執行的話,使用SERIAL_EXECUTOR。

5.2 AsyncTask生命週期問題

  • 很多開發者會認為一個在Activity中建立的AsyncTask隨著Activity的銷燬而銷燬。然而事實並非如此。AsynTask會一直執行,直到doInBackground()方法執行完畢,然後,如果cancel(boolean)被呼叫,那麼onCancelled(Result result)方法會被執行;否則,執行onPostExecuteResult result)方法。如果我們的Activity銷燬之前,沒有取消AsyncTask,這有可能讓我們的應用崩潰(crash)。因為它想要處理的view已經不存在了。所以,我們是必須確保在銷燬活動之前取消任務。總之,我們使用AsyncTask需要確保AsyncTask正確的取消。

5.3 AsyncTask記憶體洩漏問題

  • 如果AsyncTask被宣告為Activity的非靜態的內部類,那麼AsyncTask會保留一個對Activity的引用。如果Activity已經被銷燬,AsyncTask的後臺執行緒還在執行,它將續在記憶體裡保留這個引用,導致Activity無法被回收,引起記憶體洩漏。

5.4 AsyncTask結果丟失問題

  • 螢幕旋轉或Activity在後臺被系統殺掉等情況會導致Actvity的重新建立,之前執行的AsyncTask會持有一個之前Activity的引用,這個引用已經無效,這時呼叫onPostExecute()再去更新介面將不再生效。

5.5 AsyncTask並行還是序列問題

  • 在Android1.6之前的版本,AsyncTask是序列的,在1.6-2.3的版本,改成了並行的。在2.3之後的版本又做了修改,可以支援並行和序列,當想要序列執行時,直接行execute()方法,如果需要並行執行時,執行executeOnExecutor(Executor)。

關於其他內容介紹

01.關於部落格彙總連結

02.關於我的部落格

相關文章