【源碼解析】AsyncTask的用法與規則

引言

AsyncTask,相信你們已經很熟悉了。它的內部封裝了ThreadHandler,這讓咱們能夠將一些耗時操做放到AsyncTask,而且能將結果及時更新到UI上。AsyncTask主要用於短期耗時操做,長時間耗時操做不建議使用AsyncTask。下面經過Google官方的一個例子來認識AsyncTask的用法。bash

一個例子

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
  protected void onPreExecute() {
		showProgress();
  }
	
  protected Long doInBackground(URL... urls) {
      int count = urls.length;
      long totalSize = 0;
      for (int i = 0; i < count; i++) {
          totalSize += Downloader.downloadFile(urls[i]);
          publishProgress((int) ((i / (float) count) * 100));
          // Escape early if cancel() is called
          if (isCancelled()) break;
      }
      return totalSize;
  }
 
  protected void onProgressUpdate(Integer... progress) {
      setProgressPercent(progress[0]);
  }
 
  protected void onPostExecute(Long result) {
      showDialog("Downloaded " + result + " bytes");
  }
 }
複製代碼

AsyncTask是一個抽象類,咱們要使用時必須自定義一個類繼承於它。AsyncTask的原型爲:markdown

public abstract class AsyncTask<Params, Progress, Result> {
}
複製代碼

它接收三個泛型參數,分別表示參數類型、進度類型、結果類型多線程

上述的例子中DownloadFilesTask接收參數類型爲URL類型,使用Integer類型表示任務進度,最終的任務結果是一個Long類型。併發

注意:上面三個泛型類型不必定都得用一個明確的類型,對於沒有使用的類型,可使用Void類型代替。ide

繼承AsyncTask至少須要重寫doInBackground方法,同時AsyncTask也提供了另外三個方法供咱們重寫,分別是onPreExecuteonProgressUpdateonPostExecute源碼分析

  • onPreExecute方法。在任務開始執行以前執行,它運行在UI線程中。一般咱們能夠在這裏展現一個等待進度條。
  • doInBackground方法。貫穿整個耗時任務,它運行在子線程中。在這裏執行耗時操做。
  • onProgressUpdate方法。貫穿整個耗時任務,在publishProgress方法被調用後執行,它運行在UI線程中。一般用於展現整個任務的一個進度。
  • onProgressUpdate方法。在任務接收後調用,doInBackground的返回結果會透傳給onPostExecute的參數值,它運行在主線程中。一般咱們從這裏獲取任務執行完成後的結果數據。

AsyncTask的規則

  • AsyncTask類必須在UI線程加載。(在4.1系統版本以上會自動完成)
  • AsyncTask對象必須在UI線程建立,也就是說AsyncTask的構造方法必須在UI線程中調用。(通過測試AsyncTask對象能夠在子線程建立,只要保證execute方法在UI線程執行就OK的。可是沒有人會這樣作,由於畫蛇添足!!!)
  • execute方法必須在UI線程中調用。這樣作是保證onPreExecute方法運行在UI線程。
  • 不要主動調用onPreExecutedoInBackgroundonProgressUpdateonProgressUpdate方法。
  • 單線程下,AsyncTask對象的任務只能執行一次,不然會報運行時錯誤。

AsyncTask執行任務的規則

AsyncTask誕生之初,任務是在一個後臺線程中順序執行的。從Android 1.6開始,就變成了能夠在後臺線程中並行執行任務。而後,到了Android 3.0版本,又改爲了單線程順序執行,以此避免併發任務產生的錯誤行爲。測試

爲了驗證上述結論,下面看一個Demo例子。this

public class MainActivity extends Activity {
    public static final String TAG = "MyApplication";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyTask("task1").execute();
        new MyTask("task2").execute();
        new MyTask("task3").execute();
        new MyTask("task4").execute();
        new MyTask("task5").execute();
        new MyTask("task6").execute();

    }

    private class MyTask extends AsyncTask<Void, Void, Void> {
        private String taskName;

        MyTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        protected Void doInBackground(Void... integers) {
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Log.e(TAG, taskName + " finish at: " + df.format(new Date()));
        }
    }
}
複製代碼

這個例子比較簡單,就是在MainActivity啓動時,執行了六次MyTask,並將任務執行後的時間節點打印出來。url

手機的系統版本是Android 8.0,從上面的Log信息能夠看出,AsyncTask的確是串行執行的。因爲現有測試機最低系統版本都是Android 4.1,已經很難找到Android 3.0如下的老古董機子了😓,因此咱們只能經過源碼去驗證Android 1.6到Android 3.0期間,AsyncTask是不是並行執行的。spa

源碼分析

Android 2.3版本

AsyncTask是否串行或者並行執行,取決於它的execute方法。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    ...省略 
    mWorker.mParams = params;
    sExecutor.execute(mFuture);

    return this;
}
複製代碼

execute方法中經過sExecutor,實際爲ThreadPoolExecutor對象,它的初始化以下所示。

private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
複製代碼

ThreadPoolExecutor是一個多線程容器,其中能夠建立多個線程來執行多個任務。由此驗證了Android 1.6版本到Android 3.0版本直接,AsyncTask執行任務的機制的確也如今的機制不同,它可讓任務並行執行。

Android 8.0版本

咱們對比一下Android 8.0版本的execute方法。

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

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    ...省略
    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}
複製代碼

execute方法中調用了executeOnExecutor方法,並將sDefaultExecutor做爲Executor對象傳遞進去,sDefaultExecutor的初始化以下所示。

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new 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(); //任務執行完畢後繼續執行scheduleNext方法
                }
            }
        });
        if (mActive == null) { //第一個任務會執行該方法
            scheduleNext();
        }
    }
		
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) { //判斷mTask隊列中是否有下一個任務,有則取出來執行
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}
複製代碼

能夠看到,在Android 8.0版本中,建立了一個ArrayDeque隊列,每次只從隊列中獲取一個任務執行,執行完畢後會繼續判斷隊列中是否有任務,若是有則取出來執行,直到全部任務執行完畢爲止。因而可知,Android 8.0版本執行任務是串行執行的。

若是咱們想改變AsyncTask這種默認行爲呢,能夠修改麼?答案是確定的。

咱們能夠直接調用AsyncTaskexecuteOnExecutor方法,並將一個Executor對象傳遞過去,就能變成並行的執行方法了。

對於上面的例子,能夠這樣改動。

public class MainActivity extends Activity {
    public static final String TAG = "MyApplication";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyTask("task1").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task2").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task3").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task4").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyTask("task6").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

    }

    private class MyTask extends AsyncTask<Void, Void, Void> {
        private String taskName;

        MyTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        protected Void doInBackground(Void... integers) {
            try {
                Thread.sleep(6000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Log.e(TAG, taskName + " finish at: " + df.format(new Date()));
        }
    }
}
複製代碼

執行後,打印出來的Log信息以下圖所示。

注意:這裏前五個Task是同時執行的,由於AsyncTask.THREAD_POOL_EXECUTOR建立了五個核心線程,第六個任務須要等待空閒線程才能繼續執行。因此會出現第六個任務和前五個任務執行時間不一致的現象,特此說明。

相關文章
相關標籤/搜索