以前這篇Android基礎知識:多線程基礎總結裏提到了Android
中幾種多線程任務使用方式,這篇就詳細看一下其中AsyncTask
的使用和原理。android
AsyncTask
從名字就能夠看出看來是Android
中提供的一個異步任務類,它的使用方法以下:
1.實現自定義AsyncTask,複寫對應方法bash
class MyAsyncTask extends AsyncTask<String, Integer, String> { int percent = 0; @Override protected void onPreExecute() { //doInBackground執行以前 Log.d(TAG, "onPreExecute:" + Thread.currentThread().getName() + " 異步任務準備開始 "); } @Override protected void onProgressUpdate(Integer... values) { //doInBackground執行中 Log.d(TAG, "onProgressUpdate:" + Thread.currentThread().getName() + " 任務進行中:" + values[0] + "%"); } @Override protected void onPostExecute(String result) { //doInBackground執行以後 Log.d(TAG, "onPostExecute:" + Thread.currentThread().getName() + " " + result); } @Override protected String doInBackground(String... strings) { String param = strings[0]; Log.d(TAG, "doInBackground:" + Thread.currentThread().getName() + " 異步任務執行中,得到參數:" + param); while (percent < 100) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } percent += 10; publishProgress(percent); } Log.d(TAG, "doInBackground:" + Thread.currentThread().getName() + " 異步任務完成!"); return "任務完成啦"; } } 複製代碼
2.建立自定義的AsyncTask對象,調用execute方法開始任務markdown
MyAsyncTask myAsyncTask = new MyAsyncTask(); myAsyncTask.execute("測試"); 複製代碼
運行結果: 多線程
AsyncTask
它有三個泛型,分別對應開始傳入的參數類型,中間進度類類型和最後任務結束返回的結果類型,實現時須要根據要求進行設置。另外還有四個主要複寫的方法分別是
doInBackground
、
onPreExecute
、
onProgressUpdate
和
onPostExecute
。
doInBackground
方法以前被調用,能夠在進行異步任務作一些準備工做,運行在主線程。doInBackground
方法以後被調用,得到一部人物大的返回結果,能夠進行一些處理結果的操做,運行在主線程。doInBackground
方法中調用publishProgress
方法後被調用,用於異步任務時更新進度,運行在主線程。示例代碼裏在doInBackground
方法中模擬了耗時任務並輸出了日誌,經過日誌能夠確認複寫的幾個方法的執行順序而且發現onPreExecute
、onProgressUpdate
、onPostExecute
這三個方法是運行在主線程中的,而doInBackground
方法是運行在子線程中的。這也和預期的同樣,因此使用時耗時任務都是寫在doInBackground
方法裏的。另外AsyncTask
能夠在調用execute
方法時傳入所需的參數,在doInBackground
方法裏能夠獲取到這些傳入參數,而且這裏的傳參是個可變參數對參數個數不作限制。關於AsyncTask
的使用就是這些,接下來來看它的運行原理。異步
瞭解運行原理就要查看他的源碼,AsyncTask
源碼很少,一共不到800行就實現了須要的功能。咱們按照AsyncTask
的使用步驟來看它的源碼流程。在實現本身定義的AsyncTask
後,第一個步驟就是建立自定義的AsyncTask
對象,因此就從構造函數開始看起。ide
public AsyncTask() { this((Looper) null); } public AsyncTask(@Nullable Handler handler) { this(handler != null ? handler.getLooper() : null); } public AsyncTask(@Nullable Looper callbackLooper) { // 給mHandler初始化賦值 mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper); // 建立WorkerRunnable mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { // 設置線程優先級 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //調用doInBackground方法並得到返回結果 result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { // 將返回結果post傳遞 postResult(result); } return result; } }; // 建立FutureTask傳入WorkerRunnable 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 occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; } 複製代碼
AsyncTask
有三個構造函數,最常使用的是無參的構造函數,固然也能夠調用有參的傳入一個Handler
或者Looper
,無論調用那個最終都會走到參數是Looper
的這個構造中來,這個構造函數中首先對成員變量中的mHandler
進行賦值,賦值先判斷了傳入的Looper
是否爲空,若是爲空或者傳入的Looper
是主線程的Looper
就調用getMainHandler
直接將主線程的Handler
賦值給mHandler
,不然就用傳入的Looper
建立一個Handler
給成員變量賦值。函數
接着程建立了一個WorkerRunnable
,看名字就知道應該是個工做線程,看到它複寫了call
方法再結合下面又把這個WorkerRunnable
傳入了一個FutureTask
能夠猜想,它是由一個Callable
實現的,點擊跟蹤找一下這個類,他是一個內部類。oop
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
複製代碼
能夠看到就是一個實現了Callable
接口的抽象類,其中封裝了咱們的傳入參數。回到構造函數中WorkerRunnable
的call
方法中顯示設置了當前線程的優先級爲後臺進程,接着執行了doInBackground
方法得到異步任務的返回結果,最後在finally
代碼塊中調用postResult
方法將結果傳遞。post
構造函數中的最後一步是建立了一個異步任務對象FutureTask
將WorkRunnable
傳入,在異步任務完成時會調用done
方法,複寫的done
方法中一樣先經過get
方法得到異步任務結果,再經過postResultIfNotInvoked
方法將結果傳遞。關於Callable
和FutureTask
不清楚的能夠去看前一篇多線程基礎中有簡單的介紹。測試
在進行下一步調用execute
方法以前,先看一下AsyncTask
類還有一個靜態代碼塊,咱們知道靜態代碼塊會隨類的加載而加載,因此先來看下這個靜態代碼塊。
// CPU核心數 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); //線程池核心線程數 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); // 線程池最大線程數 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; // 線程池中線程空閒存活實現 private static final int KEEP_ALIVE_SECONDS = 30; // 線程工廠 private static final ThreadFactory sThreadFactory = new ThreadFactory() { // 原子型指令Integer類型 private final AtomicInteger mCount = new AtomicInteger(1); // 返回一個名爲AsyncTask #加上編號的線程 public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; // 線程池的阻塞隊列長度128 private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); /** * An {@link Executor} that can be used to execute tasks in parallel. */ public static final Executor THREAD_POOL_EXECUTOR; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } 複製代碼
靜態代碼塊中初始化了一個線程池,並將其賦值給成員變量中的THREAD_POOL_EXECUTOR
,從註釋看是一個用於執行並行任務的線程池。下面就進入execute
方法來看看AsyncTask
究竟是怎麼運行的。
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } 複製代碼
execute
方法中進而調用了executeOnExecutor
方法,除了傳入了params
還傳入了一個executeOnExecutor
,先來看看executeOnExecutor
是什麼?
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; /** * An {@link Executor} that executes tasks one at a time in serial * order. This serialization is global to a particular process. */ public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); 複製代碼
它引用了成員變量中的SERIAL_EXECUTOR
,而SERIAL_EXECUTOR
又是一個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 { // 調用傳入的Runnable的run方法 r.run(); } finally { scheduleNext(); } } }); // 第一次進入或對列爲空mActive爲空直接調用scheduleNext if (mActive == null) { scheduleNext(); } } // 從隊列中取出任務交給THREAD_POOL_EXECUTOR線程池處理 protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } } 複製代碼
SerialExecutor
中有一個任務隊列mTasks
,它的execute
方法中將傳入的Runnable
從新封裝入一個新的Runnable
,在新的Runnable
的run
方法中調用原來Runnable
的run
方法,而且在finally
代碼塊中調用了scheduleNext
方法,最終將這個新的Runnable
任務加入mTasks
任務隊列。以後判斷了mActive
是否爲空,爲空的話也調用scheduleNext
方法。這個mActive
看到是在scheduleNext
方法中從任務隊列中取出的Runnable
對象,scheduleNext
方法中取出任務後不爲空將這個任務交給THREAD_POOL_EXECUTOR
這個線程池去處理,THREAD_POOL_EXECUTOR
就是靜態代碼塊中初始化的線程池,也是最終處理異步任務的線程池。
接着再回到executeOnExecutor
方法中,AsyncTask
的execute
方法中調用的executeOnExecutor
方法。
@MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { // 判斷AsyncTask的狀態不爲PENDING拋出對應異常 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)"); } } // 狀態爲PENDING就將狀態修改成RUNNING mStatus = Status.RUNNING; // 調用onPr方法eExecute onPreExecute(); // 將execute方法中傳入的參數傳遞到構造函數中建立的WorkerRunnable中 mWorker.mParams = params; // 將構造函數中建立的FutureTask交給exec線程池即sDefaultExecutor線程池處理 exec.execute(mFuture); return this; } 複製代碼
方法中先判斷AsyncTask
的狀態,AsyncTask
的狀態有如下幾種。
public enum Status {
/**
* 表示任務還沒有執行
*/
PENDING,
/**
* 表示任務正在執行
*/
RUNNING,
/**
* 表示任務已經完成
*/
FINISHED,
}
複製代碼
AsyncTask
的不是還沒有執行狀態就拋出對應的異常,不然就調用onPreExecute
這個異步任務執行前的方法,而後將接收到的參數傳入構造函數中創WorkerRunnable
中,最後調用exec.execute
方法將異步任務交給exec
線程池即sDefaultExecutor
線程池處理。接着就會按照以前看過的同樣,異步任務會先進入SerialExecutor
線程池,在SerialExecutor
中放入取出串行任務隊列,最終交給THREAD_POOL_EXECUTOR
線程池執行任務。
以前看過WorkRunnable
任務執行完成後會調用postResult
或postResultIfNotInvoked
方法。
private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { postResult(result); } } 複製代碼
postResultIfNotInvoked
方法中仍是調用了postResult
方法,因此進入這個postResult
方法跟蹤查看。
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; } 複製代碼
postResult
方法中調用了getHandler
方法得到成員變量中的mHandler
發送了個消息,消息中將異步任務的返回結果封裝成了一個AsyncTaskResult
對象放入消息的obj
發送。AsyncTaskResult
對象將是將當前的AsyncTask
和異步任務結果作了個封裝。
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
複製代碼
還記得mHandler
是在哪兒建立的嗎?是在AsyncTask
的構造函數中經過getMainHandler
方法建立的。
private static Handler getMainHandler() { synchronized (AsyncTask.class) { if (sHandler == null) { sHandler = new InternalHandler(Looper.getMainLooper()); } return sHandler; } } 複製代碼
getMainHandler
方法中用主線程Looper
建立一個InternalHandler
。剛纔的postResult
方法中發送的消息,就交給的是這個Handler
處理。
private static class InternalHandler extends Handler { public InternalHandler(Looper looper) { super(looper); } @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; } } } 複製代碼
InternalHandler
中的handleMessage
方法中處理了兩種消息,一種是異步任務完成結果的消息,另外一種是更新進度的消息。仍是先來看任務結果的消息,其對應MESSAGE_POST_RESULT
。調用result.mTask.finish
方法,即AsyncTask
的finish
方法。
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; } 複製代碼
finish
方法中先判斷了此時任務有沒有被取消,取消了就會走onCancelled
方法,沒取消就會調用onPostExecute
這個異步任務完成後的方法,而後將AsyncTask
的狀態修改成已完成。至此AsyncTask
的一次執行流程結束。
最後來看看異步任務更新進度的方法。要進行進度更新須要咱們在doInBackground
方法中調用publishProgress(percent)
方法將進度傳入。因而來看這個publishProgress(percent)
方法。
protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } } 複製代碼
方法中先判斷任務沒被取消就獲取Handler
發送更新進度消息,進度數據一樣封裝到AsyncTaskResult
類型裏。而後再看怎麼處理消息。
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; } } 複製代碼
處理消息很簡單就是調用了AsyncTask
的onProgressUpdate
方法將進度數據傳入便可。至此AsyncTask
的運行原理就所有講完了,最後經過兩張圖梳理下整個過程。
AsyncTask
必定要在主線程建立調用嗎?實踐是檢驗真理的惟一標準。
new Thread(new Runnable() { @Override public void run() { MyAsyncTask myAsyncTask = new MyAsyncTask(); myAsyncTask.execute("測試"); } }).start(); 複製代碼
運行結果日誌:
AsyncTask
並無發生錯誤,只是
onPreExecute
方法調用的線程變了,變成建立的子線程了,這也很好理解,根據以前看過的源碼能夠知道
onPreExecute
方法是在
executeOnExecutor
方法中直接調用的,因此就是運行在
AsyncTask
建立線程裏。而其餘方法要麼是經過線程池中線程調用,要麼是經過
InternalHandler
到主線程中調用,因此運行線程與以前無異。
那麼子線程中建立使用AsyncTask
單單只是onPreExecute
方法運行線程不一樣嗎?爲何會有必須在主線程建立調用這種說法呢?
其實這種說法是創建在老版本的AsyncTask
上的,在上面的運行原理中全部的源碼相關都是截取的API28
版本的AsyncTask
的源碼。其中Handler
是得到的主線程的Looper
建立的。
sHandler = new InternalHandler(Looper.getMainLooper());
複製代碼
而在早期版本中,這裏看API10
版本的AsyncTask
中的代碼。
private static final InternalHandler sHandler = new InternalHandler();
複製代碼
早期版本中建立Handler
時沒有使用主線程的Looper
因此建立AsyncTask
的線程就是Handler
所在線程,這樣會致使onPostExecute
和onProgressUpdate
方法都會運行在這個線程,若是建立AsyncTask
不在主線程就會致使onPostExecute
和onProgressUpdate
方法也不運行在主線程,進而在這兩個方法中沒法直接對UI
進行更新。
關於這點一樣仍是源碼版本差別,新版本源碼從以前的運行原理描述中能夠看到AsyncTask
在調用execute
方法開啓任務後,是先將任務放入一個SerialExecutor
線程池,其中維護了一個隊列,每次都是從這個隊列中一個個取任務再交給真正處理任務的線程池。而在早期版本中是沒有這個SerialExecutor
這個線程池的,任務都是直接放入任務線程池執行的。
public final AsyncTask<Params, Progress, Result> execute(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; sExecutor.execute(mFuture); return this; } private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory); 複製代碼
上面這段是API10
對應Android2.3.7
版本中AsyncTask
的execute
方法,能夠看到任務是直接加入到sExecutor
這個任務線程池中的。
在使用AsyncTask
若爲非靜態內部類,在Activity
銷燬時,AsyncTask
中的耗時任務尚未完成,這時AsyncTask
會還持有Activity
的引用,形成其沒法被會正常回收,形成內存泄漏。
解決方法也很簡單隻要使用靜態類static
加上弱引用WeakReference
就能夠了。另外從源碼中看出AsyncTask
有提供一個cancel
方法,那麼在Activity
的destory
方法裏調用這個方法取消任務可不能夠解決內存泄漏呢?答案是不能夠的,具體仍是看源碼。
private final AtomicBoolean mCancelled = new AtomicBoolean(); public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); } 複製代碼
看到AsyncTask
的cancel
方法只是將mCancelled
這個狀態修改成true
,另外調用了mFuture.cancel
方法。
public boolean cancel(boolean mayInterruptIfRunning) { if (!(state == NEW && U.compareAndSwapInt(this, STATE, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; try { // in case call to interrupt throws exception if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null) t.interrupt(); } finally { // final state U.putOrderedInt(this, STATE, INTERRUPTED); } } } finally { finishCompletion(); } return true; } 複製代碼
在mFuture.cancel
方法中最終仍是調用了線程的t.interrupt
方法,而Thread
的interrupt
方法是不會當即中斷線程的,它一樣是僅僅修改了一箇中斷線程的標誌狀態,須要本身在代碼中判斷處理或者等該線程進入阻塞纔會拋出一個InterruptedException
異常退出線程。因此AsyncTask
的cancel
方法是不能阻止內存泄漏的。
關於AsyncTask
的內容就是以上這些了,AsyncTask
做爲官方提供的一種處理異步任務的封裝類,其實也並無那麼好用,稍不注意就會發生各類問題,不過做爲了解,理解它的運行原理仍是頗有必要的。