AsyncTask異步任務類

目錄介紹

  • 01.先看下AsyncTask用法
  • 02.AsyncTask源碼深刻分析php

    • 2.1 構造方法源碼分析
    • 2.2 看execute(Params... params)方法
    • 2.3 mWorker和mFuture的建立過程
  • 03.異步機制的實現
  • 04.不一樣的SDK版本區別
  • 05.AsyncTask的缺陷和問題java

    • 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萬字],轉載請註明出處,謝謝!
  • 連接地址:https://github.com/yangchong2...
  • 若是以爲好,能夠star一下,謝謝!固然也歡迎提出建議,萬事起於忽微,量變引發質變!

問題答疑

  • AsyncTask是如何實現異步機制的,底層原理是什麼?
  • AsyncTask調用execute方法時,若是不是運行在主線程中會出現什麼狀況,如何解決?
  • 爲何異步任務對象不能執行屢次,即不能建立一個對象執行屢次execute方法?
  • doInBackground這個方法能夠作什麼操做?它是在主線程中仍是工做線程中?爲何?
  • AsyncTask任務是否能夠被中途取消?爲何?
  • AsyncTask對應線程池是如何操做的?它有什麼弊端,爲何如今幾乎不多用呢?
  • AsyncTask的執行策略是並行仍是串行的?
  • 帶着問題去看這篇文章,相信看完以後你對異常AsyncTask有了初步理解……

01.先看下AsyncTask用法

  • 來看一下AsyncTask的基本使用,代碼以下所示android

    • 定義了本身的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;
        }
    }
  • 開始調用異步任務git

    new MyAsyncTask().execute();

02.AsyncTask源碼深刻分析

2.1 構造方法源碼分析

  • 源代碼以下所示,主要是看AsyncTask(@Nullable Looper callbackLooper)中的代碼github

    • 這裏面只是初始化了兩個成員變量: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方法不是運行在主線程中會出現什麼狀況呢?segmentfault

    • 執行,可是並無什麼區別,程序仍是能夠正常執行。可是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這個方法源碼markdown

    • 具體的內部實現方法裏:首先判斷當前異步任務的狀態,其內部保存異步任務狀態的成員變量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.關於個人博客

相關文章
相關標籤/搜索