Android AsyncTask 詳解 Android AsyncTask 詳解

Android 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 is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

這是Google Android 開發文檔上關於AsyncTask的介紹,大概意思是AsyncTask設計爲一個對於Thread和Handle的輔助類,主要讓開發者方便的使用UI Thread和後臺Thread的操作( 比如在後臺線程下載文件,同時要在UI線程更新下載進度 )。同時這不是一個通用的多線程編程框架,他被設計爲用於能夠在 最多幾秒的時間內返回結果的任務。

簡單使用

這裏我們模擬一個後臺下載一些文件,並在用戶界面顯示一個ProgressDialog來顯示下載進度的功能。

/**
 * author: zyinux
 * date: on 2018/10/26
 */
public class DownloadTask extends AsyncTask<String,Integer,Boolean> {

    ProgressDialog progressDialog;

    Context context;

    public DownloadTask(Context context) {
        this.context=context;

    }

    @Override
    protected void onPreExecute() {
        progressDialog=new ProgressDialog(context);
        progressDialog.setMax(100);
    // progressDialog.setCancelable(false);
    //注意這裏我將上一行代碼註釋掉,使得dialog能夠被取消,至於爲什麼這麼做後面解釋
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.show();
    }

    @Override
    protected Boolean doInBackground(String... strings) {
        int pro=0;

        while (true){
            pro++;
            publishProgress(pro);
            if (pro>=100){
                break;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setProgress(values[0]);
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        progressDialog.dismiss();
        if (aBoolean){
            Toast.makeText(context,"下載成功",Toast.LENGTH_SHORT).show();
        }else {
            Toast.makeText(context,"下載失敗",Toast.LENGTH_SHORT).show();
        }
    }
}

下面是Activity中調用的主要代碼

new DownloadTask(this).execute("testurl");
//使用非常簡單,new 之後執行execute傳入執行的參數即可

運行效果如圖



很普通的效果,下面來分析下上面的代碼
首先是

public class DownloadTask extends AsyncTask<String,Integer,Boolean>

這一行泛型尖括號裏的三個類型,具體對應三個

  • Params 執行時發送給任務的參數的類型
  • Progress 後臺執行過程進度的類型
  • Result 執行結果返回值的類型
    當不需要這些參數的時候可以設置爲<Void,Void,Void>
    接着是實現的幾個主要方法
protected void onPreExecute() {}
protected Boolean doInBackground(String... strings) {}
protected void onProgressUpdate(Integer... values) {}
protected void onPostExecute(Boolean aBoolean) { }
  • onPreExecute 方法執行在UI線程,會在做後臺任務之前調用,可以在這裏執行一些初始化操作,例如上面的顯示Dialog
  • doInBackground 改方法執行在後臺線程,任務中的耗時操作都應該在這裏執行,AsyncTask內部維持了一個線程池,來對該方法調用執行優化。該方法的參數類型就是上面設置的 Params ,也就是執行調用代碼中execute裏傳遞來的參數。在該方法內部可以調用publishProgress方法來傳遞當前的進度。
  • onProgressUpdate 在publishProgress方法後,系統會調用該方法,該方法運行在UI Thread,所以可以在這裏做UI更新的操作,比如更新ProgressDialog的進度。這裏傳遞的參數的類型就是上文裏的 Progress。
  • onPostExecute 在doInBackground方法執行完成後會執行該方法,同樣運行在UI Thread。而傳入的參數就是doInBackground方法的返回值,該類型由上文的Result指定。

繁雜部分和源碼淺析

上面基本講解了AsyncTask的使用方法了。細心的小夥伴可能注意到我上面的這兩句代碼

// progressDialog.setCancelable(false);
//注意這裏我將上一行代碼註釋掉,使得dialog能夠被取消,至於爲什麼這麼做後面解釋

現在來解釋這裏這麼寫的原因,假設我們運行app,並執行DownloadTask,這時候屏幕上彈出一個進度框,目前爲止一切都沒有問題。這個時候我們點擊屏幕的其他地方,進度框會被取消掉,接着我們再次執行DownloadTask,小夥伴們猜猜現在會發生什麼?

由於不太方便錄屏和傳gif圖,我這裏就簡單說下會發生的事情:進度框會再度彈出,這沒什麼問題,但是進度條會停留在0%不動,直到一段時間之後彈出Toast顯示下載完成,接着進度條開始慢慢增加,當達到百分之百時再次彈出Toast提示下載完成。

爲什麼會這樣?

首先我們知道,取消dialog並不會取消掉AsyncTask,所以再次執行DownloadTask時,相當於此時有兩個AsyncTask任務在執行。這就引出了一個問題,多個AsyncTask執行時是串行還是並行?

串行還是並行?

先說答案,默認是串行的,爲什麼,我們來看源碼。
當執行 new DownloadTask(this).execute("testurl"); 後:

    @MainThread
    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) {
        //看這裏我們直到onPreExecute方法一定在doInBackground方法之前調用,並且是在UI Thread
        onPreExecute();
        /**
        *具體執行方法在這裏 我們直到這個exec就是上一步傳進來的sDefaultExecutor 
        *這是一個用來管理線程池的框架
        */
        exec.execute(mFuture);
        return this;
    }

    初始化的地方,重點這裏初始化爲static final 是一個類靜態變量,是類實例共享的
    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);
            }
        }
    }

    將任務保存在ArrayDeque中,這是一個FIFO的隊列,最後執行這個隊列中的每一個任務。所以當執行多個AsyncTask時,他們是串行執行的。
上面說了這時一般情況,那麼特殊情況呢?
DownloadTask task=new DownloadTask(this);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

這時候多個任務就不是一定排隊按順序執行了,具體執行順序要看系統對線程的調度了,小夥伴們可以自己測試一下看看。不過一般不推薦這麼使用,除非你有特殊需求。

一些坑的地方

關於cancel方法

public final boolean cancel(boolean mayInterruptIfRunning) {}

傳入的參數表示當前任務執行時是否可以取消。但是當你的doInBackground方法中執行一個循環或者一個IO流讀寫任務,即使你傳入了true,改方法也無法取消這個任務的執行。區別在於調用這個方法後,doInBackground執行完成時會調用onCancelled方法,而不是onPostExecute方法,所以cancel無法保證任務能夠被取消

內存泄漏

上面的示列代碼從Activity中傳入了一個context。而AsyncTask的生命週期和Activity是無關的,那麼當Activity被finish後,AsyncTask依然存在,而他持有着Activity的引用導致Activity無法被垃圾回收。
同時如果使用了非靜態匿名內部類來實現AsyncTask,由於Java內部類的特點,他同樣會持有當前Activity的引用造成內存泄漏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章