【原始碼解析】AsyncTask的用法與規則
引言
AsyncTask
,相信大家已經很熟悉了。它的內部封裝了
Thread
和
Handler
,這讓我們可以將一些耗時操作放到
AsyncTask
,並且能將結果及時更新到UI上。
AsyncTask
主要用於短時間耗時操作,長時間耗時操作不建議使用
AsyncTask
。下面透過Google官方的一個例子來認識
AsyncTask
的用法。
一個例子
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
的原型為:
public abstract class AsyncTask<Params, Progress, Result> {} 複製程式碼
它接收三個泛型引數,分別表示 引數型別、進度型別、結果型別。
上述的例子中
DownloadFilesTask
接收引數型別為
URL
型別,使用
Integer
型別表示任務進度,最終的任務結果是一個
Long
型別。
注意:上面三個泛型型別不一定都得用一個明確的型別,對於沒有使用的型別,可以使用
Void
型別代替。
繼承
AsyncTask
至少需要重寫
doInBackground
方法,同時
AsyncTask
也提供了另外三個方法供我們重寫,分別是
onPreExecute
、
onProgressUpdate
、
onPostExecute
。
- 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執行緒。 - 不要主動呼叫
onPreExecute
、doInBackground
、onProgressUpdate
、onProgressUpdate
方法。 - 單執行緒下,AsyncTask物件的任務只能執行一次,否則會報執行時錯誤。
AsyncTask執行任務的規則
在
AsyncTask
誕生之初,任務是在一個後臺執行緒中順序執行的。從Android 1.6開始,就變成了可以在後臺執行緒中並行執行任務。然後,到了Android 3.0版本,又改成了單執行緒順序執行,以此避免併發任務產生的錯誤行為。
為了驗證上述結論,下面看一個Demo例子。
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
,並將任務執行後的時間節點列印出來。
手機的系統版本是Android 8.0,從上面的Log資訊可以看出,
AsyncTask
的確是序列執行的。由於現有測試機最低系統版本都是Android 4.1,已經很難找到Android 3.0以下的老古董機子了?,所以我們只能透過原始碼去驗證Android 1.6到Android 3.0期間,
AsyncTask
是否是並行執行的。
原始碼分析
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
這種預設行為呢,可以修改麼?答案是肯定的。
我們可以直接呼叫
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建立了五個核心執行緒,第六個任務需要等待空閒執行緒才能繼續執行。所以會出現第六個任務和前五個任務執行時間不一致的現象,特此說明。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2664093/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- AsyncTask原始碼解析原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- 【Urule原始碼解析1】開源視覺化規則引擎原始碼視覺化
- RecyclerView用法和原始碼深度解析View原始碼
- AsyncTask 面試解析面試
- 從原始碼角度談談AsyncTask的使用及其原理原始碼
- Tars | 第4篇 Subset路由規則業務分析與原始碼探索路由原始碼
- EventBus原理與原始碼解析原始碼
- 20條IPTables防火牆規則用法!防火牆
- AsyncTask的理解與應用
- 老生常談:Promise 用法與原始碼分析Promise原始碼
- PyTorch ResNet 使用與原始碼解析PyTorch原始碼
- Optional原始碼解析與實踐原始碼
- Function原始碼解析與實踐Function原始碼
- Netty原始碼解析 -- FastThreadLocal與HashedWheelTimerNetty原始碼ASTthread
- Spring Boot中實現規則引擎原始碼教程Spring Boot原始碼
- sentinel流控規則校驗之原始碼分析原始碼
- AsyncTask 程式碼分析
- 從原始碼解析-掌握AsyncTask工作原理 為什麼序列執行和記憶體洩漏原始碼記憶體
- Mysql-基本的規則與規範MySql
- axios CancelToken 取消頻繁傳送請求的用法和原始碼解析iOS原始碼
- Golang的GMP排程模型與原始碼解析Golang模型原始碼
- 原始碼解析Spring AOP的載入與生效原始碼Spring
- DAPP錢包質押挖礦開發模式的優勢 | DAPP錢包原始碼邏輯規則解析APP模式原始碼
- 矛盾與規則的結算
- 比特幣挖礦與原始碼解析比特幣原始碼
- canvas非零繞組規則與奇偶規則Canvas
- 區塊鏈DApp開發模式詳情 | 去中心化應用開發原始碼規則解析區塊鏈APP模式中心化原始碼
- JsonPath:針對json的強大的規則解析與引數查詢工具JSON
- 前端工程程式碼規範(一)——命名規則與工程約定前端
- redux的原始碼解析Redux原始碼
- SpringAOP的原始碼解析Spring原始碼
- Protobuf編碼規則
- IPPswap孵化器:仿生盤開發邏輯與規則解析
- 區塊鏈主鏈開發規則及原始碼示例區塊鏈原始碼
- DAPP系統開發原始碼規則解析 | 如何在以太坊搭建DAPP開發去中心化程式?APP原始碼中心化
- redux 原始碼解析與實際應用Redux原始碼
- OkHttp 開源庫使用與原始碼解析HTTP原始碼