AsyncTask
,相信你們已經很熟悉了。它的內部封裝了Thread
和Handler
,這讓咱們能夠將一些耗時操做放到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
也提供了另外三個方法供咱們重寫,分別是onPreExecute
、onProgressUpdate
、onPostExecute
。源碼分析
publishProgress
方法被調用後執行,它運行在UI線程中。一般用於展現整個任務的一個進度。doInBackground
的返回結果會透傳給onPostExecute
的參數值,它運行在主線程中。一般咱們從這裏獲取任務執行完成後的結果數據。AsyncTask
類必須在UI線程加載。(在4.1系統版本以上會自動完成)AsyncTask
對象必須在UI線程建立,也就是說AsyncTask
的構造方法必須在UI線程中調用。(通過測試AsyncTask
對象能夠在子線程建立,只要保證execute
方法在UI線程執行就OK的。可是沒有人會這樣作,由於畫蛇添足!!!)execute
方法必須在UI線程中調用。這樣作是保證onPreExecute
方法運行在UI線程。onPreExecute
、doInBackground
、onProgressUpdate
、onProgressUpdate
方法。在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
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版本的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
這種默認行爲呢,能夠修改麼?答案是確定的。
咱們能夠直接調用AsyncTask
的executeOnExecutor
方法,並將一個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建立了五個核心線程,第六個任務須要等待空閒線程才能繼續執行。因此會出現第六個任務和前五個任務執行時間不一致的現象,特此說明。