八、AsyncTask解析

如果在一個進程中,頻繁創建和銷毀線程,顯然不是高效的做法。正確的做法是采用線程池,一個線程池中會緩存一定數量的線程,通過線程池可以避免因為頻繁創建和銷毀線程所帶來的消耗。

1.AsyncTask簡介

Asynctask是一個抽象類,它是Android封裝的一個輕量級異步類(輕量級體現在使用方便,代碼簡潔),它可以在線程池中執行后臺任務,然后把執行的進度和最終的結果呈現給主線程,并且更新UI。
Asynctask內部封裝了兩個線程池(SerialExecutorTHREAD_POOL_EXECUTOR),和一個Handler(IntentHandler)

  • SerialExecutor線程池用于任務的排隊,讓需要執行的多個耗時任務按順序排列。
  • THREAD_POOL_EXECUTOR線程池才真正執行任務。
  • IntentHandler用于從工作線程切換到主線程
1.1 AsyncTask的泛型參數

AsyncTask的類型聲明:

public abstract class AsyncTask<Params, Progress, Result> {}

AsyncTask是一個抽象的泛型類。

  • Params: 開始異步任務執行時傳入的參數類型。
  • Progress:異步任務執行過程中,返回下載進度值的類型。
  • Result: 異步任務執行完成,返回的結果類型。
    如果AsyncTask確定不需要傳遞具體參數,那么這三個泛型參數可以用Void來代替。
1.2 AsyncTask的核心方法
  • onPreExecute()(主線程中執行):這個方法會在后臺任務開始執行前調用,在主線程執行。用于進行一些界面上的初始化操作,比如顯示一個對話框列表什么的。
  • doInBackground(Params...)(子線程中執行): 這個方法在子線程中執行,負責處理耗時操作。
    任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個參數指定的是Void,就可以不返回執行結果。如果要更新UI,可以通過調用publishProgress(Progess...)方法來完成比如反饋當前的進度。
  • onProgressUpdate(Progress...)(主線程中執行):當在后臺進程中調用publishProgress(Progress...)方法后,這個方法就會被調用,方法中攜帶的參數就是后臺任務中傳遞過來的。在這個方法中可以對UI進行更新操作。
  • onPostExecute(Result)(主線程中執行):當doInBackground(Params...)執行完畢并通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到這個方法里面。利用參數的數據,就可以進行UI的修改。

上面幾個方法的調用順序:onPreExcute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExcute()
如果不需要執行更新UI操作,順序是:onPreExcute() --> doInBackground() --> onPostExcute()。

  • onCancelled()(主線程中執行):該方法被調用,onPostExecute()方法將不會被執行,需要注意的是,AsncTask中的cancel()不是真正的取消任務,只是將任務標記為取消狀態,我們需要在doInBackground()內判斷終止任務。就好比想要終止一個線程,調用interrupt()方法,只是進行標記為中斷,需要在線程內部進行標記判斷,然后中斷線程。
1.3 AsyncTask的簡單使用
public class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }
    @Override
    protected Boolean doInBackground(Void... voids) {
        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("當前下載進度: " + 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();
        }
    }
}

這里模擬了一個下載任務,在doInBackground方法中,去執行具體的下載邏輯,在onProgressUpdate()方法中顯示下載的進度。在onPostExecute()方法中提示下載任務的結果。
調用方法:

  new DownloadTask().execute();
1.4 使用AsyncTask的注意事項
    1. 異步任務的實例必須在UI線程中創建,即AsynacTask對象必須在UI線程中創建。
    1. execute(Params... params)必須在UI線程中調用。
    1. 不要手動調用onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute()這幾個方法。
    1. 不能在doInBackground()中更改UI組件的信息。
    1. 一個任務實例只能執行一次,如果執行第二次會拋出異常。

2.AsyncTask的源碼分析

AsyncTask的構造函數:

 public AsyncTask() {   this((Looper) 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);
                    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);
                }
            }
        };
    }

在構造函數中,主要是初始化了3個成員變量, mHandler,mWorker,mFuture。并在初始化mFutrue的時候,mWorker作為參數傳入。 mWorker是一個Callable對象,mFuture是以一個FutureTask對象。FutureTask實現了Runnable接口。
mWorker中的call()方法執行了耗時操作,即result = doInBackground(mParams);,然后把得到的結果通過postResult(result);傳遞給內部的Handler跳轉到主線程中。這里實際是在實例化變量,并沒有開啟執行任務。
postResult()方法源碼:

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

mFuture對象被加載線程池中,并加載的過程。
new DownloadTask().execute();其中execute()就是執行了這一操作。
execute()源碼:

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

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        mStatus = Status.RUNNING;
        onPreExecute();
        mWorker.mParams = params;
        exec.execute(mFuture);
        return this;
    }

可以看出,execute()方法,實際上是執行了executeOnExecutor()方法,先執行了onPreExecute(),然后具體執行耗時任務是 exec.execute(mFuture);。對象exec是sDefaultExecutor,往上追溯就是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 是個內部靜態內部類,是所有實例化的AsyncTask對象共有的SerialExecutor,內部維護了一個隊列,通過鎖使得保證AsyncTask中的任務是串行執行的,即多個任務需要一個個加到該隊列中。
這個方法中主要有兩個步驟。

    1. 向隊列中加入一個新的任務,即之前實例化的mFuture對象。
  • 2.調用scheduleNext(),調用THREAD_POOL_EXECUTOR執行隊列頭部任務。
    由此可以知道SerialExecutor僅僅是為了保持任務執行隊列是串行的實際執行交給了THREAD_POOL_EXECUTOR

THREAD_POOL_EXECUTOR源碼:

 public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(), sThreadFactory);
        threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

實際上就是個線程池,開啟了一定數量的核心線程和工作線程。然后調用線程池的execute()方法,執行具體的耗時任務,即開頭構造函數mWorker中的call()方法。先執行doInBackground()方法,然后再執行postResult()方法。

在postResult()方法中,向在AsyncTask構造函數中初始化的mHandler發送了一個消息。
在構造函數中通過調用getMainHandler(),實例化mHandler的源碼:

   private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

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
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

InternalHandler中,

  • 如果收到的消息是MESSAGE_POST_PROGRESS, 回調onProgressUpdate()方法, 更新進度。
  • 如果收到的消息是MESSAGE_POST_RESULT,即執行完了doInBackground()方法并傳遞結果,那就調用AsyncTask.finish()方法。
   private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

如果任務已經取消了,就回調onCancelled()方法,否則就回調onPostExecute()方法。

InternalHandler是一個靜態類, 為了能夠將執行環境切換到主線程, 因此這個類必須在主線程中進行加載。 所以變相要求AsyncTask的類必須在主線程中進行加載。到此為止, 從任務執行的開始到結束都從源碼分析完了。

AsyncTask的串行和并行:
默認情況下因為有SerialExecutor類來維持保證隊列的串行。如果想要使用并行執行任務,那么可以直接跳過SerialExecutor類,使用executeOnExecutor()來執行任務。

3. AsyncTask使用不當的后果

  • 1)生命周期
    AsyncTask不與任何組件綁定生命周期,所以在Activity或Fragment中創建執行AsyncTask時候,最好在Activity/Fragment的onDestroy()方法中調用cancle(boolean)方法。
  • 2)內存泄漏
    如果AsyncTask被聲明為Activity的非靜態的內部類,那么AsyncTask會保留一個對創建了AsyncTask的Activity引用。如果Activity已經被銷毀,AsyncTask的后臺線程還在執行,它將繼續在內存中保存這個引用,導致Activity無法被回收,導致內存泄漏。
  • 3)結構丟失
    屏幕旋轉或者Activity在后臺被系統殺死等情況會導致Activity的重新創建,之前運行的AsyncTask(非內部靜態類)會持有之前的Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將不會再生效。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容