以前這篇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方法開始任務多線程
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
的使用就是這些,接下來來看它的運行原理。ide
瞭解運行原理就要查看他的源碼,AsyncTask
源碼很少,一共不到800行就實現了須要的功能。咱們按照AsyncTask
的使用步驟來看它的源碼流程。在實現本身定義的AsyncTask
後,第一個步驟就是建立自定義的AsyncTask
對象,因此就從構造函數開始看起。函數
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
給成員變量賦值。oop
接着程建立了一個WorkerRunnable
,看名字就知道應該是個工做線程,看到它複寫了call
方法再結合下面又把這個WorkerRunnable
傳入了一個FutureTask
能夠猜想,它是由一個Callable
實現的,點擊跟蹤找一下這個類,他是一個內部類。post
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
複製代碼
能夠看到就是一個實現了Callable
接口的抽象類,其中封裝了咱們的傳入參數。回到構造函數中WorkerRunnable
的call
方法中顯示設置了當前線程的優先級爲後臺進程,接着執行了doInBackground
方法得到異步任務的返回結果,最後在finally
代碼塊中調用postResult
方法將結果傳遞。測試
構造函數中的最後一步是建立了一個異步任務對象FutureTask
將WorkRunnable
傳入,在異步任務完成時會調用done
方法,複寫的done
方法中一樣先經過get
方法得到異步任務結果,再經過postResultIfNotInvoked
方法將結果傳遞。關於Callable
和FutureTask
不清楚的能夠去看前一篇多線程基礎中有簡單的介紹。ui
在進行下一步調用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
做爲官方提供的一種處理異步任務的封裝類,其實也並無那麼好用,稍不注意就會發生各類問題,不過做爲了解,理解它的運行原理仍是頗有必要的。