Android AsyncTask的使用學習整理
AsyncTask非同步任務,用於執行耗時任務並在UI執行緒中更新結果。
我們都知道,Android UI執行緒中不能執行耗時的任務,否則就會出現ANR。對於耗時的操作就需要放到子執行緒中操作,操作完成後需要通知UI執行緒進行更新等操作,這就需要Android的非同步訊息機制(建立一個Message物件,使用Handler傳送出去,然後在Handler的handleMessage()方法中獲得剛才傳送的Message物件,然後在這裡進行UI操作)。
不過本文要說的是AsyncTask,其實早在Android 1.5版本就引入這個類,所以我知道大多數人對它的用法都已經非常熟悉了。基本用法在網上搜搜就有很多教程,然而,在使用時,仍需要注意其潛在的問題以及缺陷。
[TOC]
AsyncTask 簡單使用
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; private ProgressDialog mDialog; private AsyncTask mAsyncTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDialog = new ProgressDialog(this); mDialog.setMax(100); mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mDialog.setCancelable(false); mAsyncTask = new MyAsyncTask(); findViewById(R.id.tv).setOnClickListener(this); } @Override public void onClick(View view) { mAsyncTask.execute(); } private class MyAsyncTask extends AsyncTask<Void, Integer, Void> { @Override protected void onPreExecute() { mDialog.show(); Log.e(TAG, Thread.currentThread().getName() + " onPreExecute "); } @Override protected Void doInBackground(Void... params) { // 模擬資料的載入,耗時的任務 for (int i = 0; i < 100; i++) { try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } publishProgress(i); } Log.e(TAG, Thread.currentThread().getName() + " doInBackground "); return null; } @Override protected void onProgressUpdate(Integer... values) { mDialog.setProgress(values[0]); Log.e(TAG, Thread.currentThread().getName() + " onProgressUpdate "); } @Override protected void onPostExecute(Void result) { // 進行資料載入完成後的UI操作 mDialog.dismiss(); Log.e(TAG, Thread.currentThread().getName() + " onPostExecute "); } } }
如以上例項中,當UI執行緒中需求處理耗時的操作時,我們可以放在AsyncTask的doInBackground方法中執行,這個抽象的類,有幾個方法需要我們重新,除了doInBackground,我們可以在onPreExecute中為這個耗時方法進行一些預處理操作,同時我們在onPostExecute中對UI進行更新操作。例項中的publishProgress對應的回撥是onProgressUpdate,這樣可以實時更新UI,提供更好的使用者體驗。
AsyncTask 原理
AsyncTask主要有二個部分:一個是與主線的互動,另一個就是執行緒的管理排程。雖然可能多個AsyncTask的子類的例項,但是AsyncTask的內部Handler和ThreadPoolExecutor都是程序範圍內共享的,其都是static的,也即屬於類的,類的屬性的作用範圍是CLASSPATH,因為一個程序一個VM,所以是AsyncTask控制著程序範圍內所有的子類例項。
-
與主執行緒互動
與主執行緒互動是通過Handler來進行的,因為本文主要探討AsyncTask在任務排程方面的,所以對於這部分不做細緻介紹,感興趣的朋友可以繼續去看AsyncTask的原始碼部分。
-
執行緒任務的排程
內部會建立一個程序作用域的執行緒池來管理要執行的任務,也就就是說當你呼叫了AsyncTask#execute()後,AsyncTask會把任務交給執行緒池,由執行緒池來管理建立Thread和執行Therad。對於內部的執行緒池不同版本的Android的實現方式是不一樣的:
AsyncTask 發展
接下來我們先簡單的瞭解一下AsyncTask的歷史
- 首先在android 3.0之前的版本,ThreadPool的限制是5個,執行緒的併發量是128個,阻塞佇列長度10,也就是說超過138個則會丟擲異常。因此我們在使用的時候,一定要主要這部分限制,正確的使用。
- 到了在Android 3.0之後的,也許是Google也意識到這個問題,對AsyncTask的API做了調整:
· execute()提交的任務,按先後順序每次只執行一個也就是說它是按提交的次序,每次只啟動一個執行緒執行一個任務,完成之後再執行第二個任務,也就是相當於只有一個後臺執行緒在執行所提交的任務(Executors.newSingleThreadPool())。
· 新增了介面executeOnExecutor()這個介面允許開發者提供自定義的執行緒池來執行和排程Thread,如果你想讓所有的任務都能併發同時執行,那就建立一個沒有限制的執行緒池(Executors.newCachedThreadPool()),並提供給AsyncTask。這樣這個AsyncTask例項就有了自己的執行緒池而不必使用AsyncTask預設的。
· 新增了二個預定義的執行緒池 SERIAL_EXECUTOR和THREAD_POOL_EXECUTOR 。其實 THREAD_POOL_EXECUTOR 並不是新增的,之前的就有,只不過之前(Android 2.3)它是AsyncTask私有的,未公開而已。 THREAD_POOL_EXECUTOR 是一個corePoolSize為5的執行緒池,也就是說最多隻有5個執行緒同時執行,超過5個的就要等待。所以如果使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)就跟2.3版本的AsyncTask.execute()效果是一樣的。而SERIAL_EXECUTOR是新增的,它的作用是保證任務執行的順序,也就是它可以保證提交的任務確實是按照先後順序執行的。它的內部有一個佇列用來儲存所提交的任務,保證當前只執行一個,這樣就可以保證任務是完全按照順序執行的,預設的execute()使用的就是這個,也就是executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)與execute()是一樣的。
AsyncTask 原始碼簡析
這裡我們從AsyncTask的起點開始分析,主要有 execute()、executeOnExecutor()。
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }
- 從程式碼中可以看出,execute()其實也是通過執行executeOnExecutor()方法,只是將其中的Executor設定為預設值。
- 在executeOnExecutor()中將當前AsyncTask的狀態為RUNNING,上面的switch也可以看出,每個非同步任務在完成前只能執行一次。
- 接下來就執行了onPreExecute(),當前依然在UI執行緒,所以我們可以在其中做一些準備工作。
- 將我們傳入的引數賦值給了mWorker.mParams
- 最後exec.execute(mFuture)
相信大家對程式碼中出現的mWorker,以及mFuture都會有些困惑。接下來我們來看看mWorker找到這個類:
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { Params[] mParams; }
可以看到是Callable的子類,且包含一個mParams用於儲存我們傳入的引數,下面看初始化mWorker的程式碼:
public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } }; //... }
可以看到mWorker在構造方法中完成了初始化,並且因為是一個抽象類,在這裡new了一個實現類,實現了call方法,call方法中設定mTaskInvoked=true,且最終呼叫doInBackground(mParams)方法,並返回Result值作為引數給postResult方法.可以看到我們的doInBackground出現了,下面繼續看:
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
可以看到postResult中出現了我們熟悉的非同步訊息機制,傳遞了一個訊息message, message.what為MESSAGE_POST_RESULT;message.object= new AsyncTaskResult(this,result);
private static class AsyncTaskResult<Data> { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; } }
AsyncTaskResult就是一個簡單的攜帶引數的物件。
看到這,我相信大家肯定會想到,在某處肯定存在一個sHandler,且複寫了其handleMessage方法等待訊息的傳入,以及訊息的處理。
private static final InternalHandler sHandler = new InternalHandler(); private static class InternalHandler extends Handler { @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; } } }
這裡出現了我們的handleMessage,可以看到,在接收到MESSAGE_POST_RESULT訊息時,執行了result.mTask.finish(result.mData[0]);其實就是我們的AsyncTask.this.finish(result),於是看finish方法
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
可以看到,如果我們呼叫了cancel()則執行onCancelled回撥;正常執行的情況下呼叫我們的onPostExecute(result);主要這裡的呼叫是在handler的handleMessage中,所以是在UI執行緒中。最後將狀態置為FINISHED。
mWoker看完了,應該到我們的mFuture了,依然實在構造方法中完成mFuture的初始化,將mWorker作為引數,複寫了其done方法。
public AsyncTask() { ... 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 occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; }
任務執行結束會呼叫:postResultIfNotInvoked(get());get()表示獲取mWorker的call的返回值,即Result.然後看postResultIfNotInvoked方法
private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { postResult(result); } }
如果mTaskInvoked不為true,則執行postResult;但是在mWorker初始化時就已經將mTaskInvoked為true,所以一般這個postResult執行不到。好了,到了這裡,已經介紹完了execute方法中出現了mWorker和mFurture,不過這裡一直是初始化這兩個物件的程式碼,並沒有真正的執行。下面我們看真正呼叫執行的地方。execute方法中的:還記得上面的execute中的:exec.execute(mFuture)
exec為executeOnExecutor(sDefaultExecutor, params)中的sDefaultExecutor
下面看這個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(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
可以看到sDefaultExecutor其實為SerialExecutor的一個例項,其內部維持一個任務佇列;直接看其execute(Runnable runnable)方法,將runnable放入mTasks隊尾;再判斷當前mActive是否為空,為空則呼叫scheduleNext。方法scheduleNext,則直接取出任務佇列中的隊首任務,如果不為null則傳入THREAD_POOL_EXECUTOR進行執行。下面看THREAD_POOL_EXECUTOR為何方神聖:
public static final Executor THREAD_POOL_EXECUTOR =new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
可以看到就是一個自己設定引數的執行緒池,引數為:
private static final int CORE_POOL_SIZE = 5; private static final int MAXIMUM_POOL_SIZE = 128; private static final int KEEP_ALIVE = 1; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(10);
看到這裡,大家可能會認為,背後原來有一個執行緒池,且最大支援128的執行緒併發,加上長度為10的阻塞佇列,可能會覺得就是在快速呼叫138個以內的AsyncTask子類的execute方法不會出現問題,而大於138則會丟擲異常。其實不是這樣的,我們再仔細看一下程式碼,回顧一下sDefaultExecutor,真正在execute()中呼叫的為sDefaultExecutor.execute:
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); } } }
可以看到,如果此時有10個任務同時呼叫execute(s synchronized)方法,第一個任務入隊,然後在mActive = mTasks.poll()) != null被取出,並且賦值給mActivte,然後交給執行緒池去執行。然後第二個任務入隊,但是此時mActive並不為null,並不會執行scheduleNext();所以如果第一個任務比較慢,10個任務都會進入佇列等待;真正執行下一個任務的時機是,執行緒池執行完成第一個任務以後,呼叫Runnable中的finally程式碼塊中的scheduleNext,所以雖然內部有一個執行緒池,其實呼叫的過程還是線性的。一個接著一個的執行,相當於單執行緒。
總結:
AsyncTask在併發執行多個任務時發生異常。其實還是存在的,在3.0以前的系統中還是會以支援多執行緒併發的方式執行,支援併發數也是我們上面所計算的128,阻塞佇列可以存放10個;也就是同時執行138個任務是沒有問題的;而超過138會馬上出現java.util.concurrent.RejectedExecutionException;而在在3.0以上包括3.0的系統中會為單執行緒執行(即我們上面程式碼的分析)
【附錄】

資料圖
需要資料的朋友可以加入Android架構交流QQ群聊:513088520
點選連結加入群聊【Android移動架構總群】: 加入群聊
獲取免費學習視訊,學習大綱另外還有像高階UI、效能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)等Android高階開發資料免費分享。