AsyncTask簡單介紹
大家應該都知道AsyncTask這個類,也應該或多或少都接觸過這個類,它可以幫我們完成一次性異步任務并把結果返回到主線程,而且在后臺運行過程中可以不斷的在主線程更新UI,所以其實如果使用它確實可以幫我替代自己手動new Thread + Handler,雖然平時好像主流項目中已經很少用使用它了取而代之的是更加火爆的RxJava。
而且我也很少會用到AsyncTask,但這畢竟是Android提供給我們的原生異步工具,最基本的使用還是需要掌握的,而且2017年我也希望可以克服自己對閱讀源碼的恐懼和不自信,所以就打算拿它來開第一刀,那么接下來我們先來說說他的使用和需要用到的方法吧。
AsyncTask基本使用和常用方法介紹
1.官方Demo
// 這是AsyncTask官方文檔給出的使用示例
// AsyncTask本身是一個抽象類,我們需要自己實現里面的抽象方法和我們需要用到的方法
// AsyncTask<URL, Integer, Long>這里有三個泛型,分別表示我們執行時需要傳遞的參數,
// 更新進度傳遞數據的類型和返回結果的類型
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
// 該方法運行在后臺線程線程,去完成某些耗時操作
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
// 這是AsyncTask提供給我們的方法,用來通知AsyncTask調用onProgressUpdate
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
// 通過判斷任務是否被取消從而退出循環結束任務
if (isCancelled()) break;
}
return totalSize;
}
// 該方法在UI線程運行,用來不斷的更新UI
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
// 該方法在UI線程運行,doInBackground會在執行結束把任務結果通過這個方法傳遞給主線程
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
// 通過調用AsyncTask的execute來執行這個異步任務
new DownloadFilesTask().execute(url1, url2, url3);
看完這個Demo和我寫的一些注釋,我想我們應該可以順利的定義一個自己的AsyncTask了。
2.AsyncTask類介紹
我們先來看看AsyncTask的類定義:
public abstract class AsyncTask<Params, Progress, Result>{...}
這里我們一目了然的看到了AsyncTask是一個抽象類,我們需要自己實現自己的AsyncTask來使用,而這里我們看到了三個泛型,他們代表的意義分別為:
①Params,執行時任務發送到任務的參數的類型,在調用execute(Params)時傳遞過去,在耗時操作的方法doInBackground(Params)中去使用。
②Progress,在后臺做耗時操作期間更新進度時參數的類型,在publishProgress(Progress),onProgressUpdate(Progress)這兩個方法中使用。
③Result,耗時操作的返回結果的類型,在onPostExecute(Result)中使用。
3.AsyncTask常用方法介紹
// 自定義AsyncTask會使用的方法
①onPreExecute():在UI線程被調用,用于在執行耗時操作前的一些初始化工作,doInBackground()前被調用,比如初始化進度條(隨便舉例的)。
②doInBackground(Params... params):抽象方法,在后臺線程運行,自定義AsyncTask必須要重寫的一個方法,可以把某些耗時操作寫到這里。
③onProgressUpdate(Progress... values):在UI線程運行,用來更新UI數據,比如更新進度條顯示,更新其他控件顯示。
④publishProgress(Progress... values):在后臺線程運行,在doInBackground()方法中被調用,通過內部Handler通知onProgressUpdate()更新UI。
⑤onPostExecute(Result result):在UI線程被調用,用于在doInBackground()執行完拿到結果并傳遞給主線程去做相應的處理。
⑥onCancelled()&onCancelled(Result result):在UI線程被調用,任務被取消后會調用這兩個方法。
⑦isCancelled():判斷任務是否被取消,可以在doInBackground()的循環中調用,從而結束任務(只是舉例)。
// 自定義的AsyncTask的實例會調用的方法
⑧execute(Params... params):在UI線程調用,開始執行任務。
⑨executeOnExecutor(Executor exec, Params... params):開始執行任務,并使用指定的線程池。
⑩cancel(boolean mayInterruptIfRunning):在UI線程被調用,取消當前任務,參數意義為是否可以中斷當前任務運行。
AsyncTask執行流程和源碼閱讀記錄
1.執行流程(用上面的數值表示任務名稱):實例化AsyncTask后先調用⑧or⑨,然后調用①,緊接著立馬調用②,如果在②中調用④,則會執行③,當然也會在執行②的過程中調用⑦去判斷任務是否被取消了,從而結束任務,最后執行完②之后會執行⑤,從而拿到返回的結果,當然如果任務被取消了不會執行⑤,而是回調⑥。
2.源碼中的體現:
我們execute(Params... params)執行任務之后,它會自己調用executeOnExecutor(Executor exec, Params... params)這個方法,而這里它傳入了一個默認的線程池,而這個線程池是一個順序執行的線程池,但其實它并不是一直都是順序執行的,Android1.6開始是并行執行,之前是順序執行,而3.0之后又改為了順序執行,所以這也是為什么如果我同時執行很多個AsyncTask,會感覺等待的時間很長,就是因為它會一個一個任務的處理,而不是并發處理。
點進下一個方法可以看得到它首先會檢查當前AsyncTask的狀態,如果是運行中和運行結束都會拋出異常,因為一個AsyncTask只能執行一次,然后如果是尚未執行的狀態(PENDING)則把狀態修改為運行中,然后調用準備函數,這里有個變量mWorker和mFuture,基本步驟就是把傳進來參數賦值給mWorker的mParams和調用默認線程池執行mFuture(線程池如果沒了解過的話可以去看我之前剛寫的關于線程池的使用筆記,其實那個就是為了這個做鋪墊寫的)。
我們先來看看上面那兩個變量,都是在AsyncTask初始化構造的時候被實例化了出來,而WorkerRunnable只是一個自定義的Callable,用來拿到剛才的參數并執行doInBackground(mParams)并把執行結果返回回去,而FutureTask是一個用于異步獲取執行結果或取消執行任務的Java類,可以通過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,之后可以在外部通過FutureTask的get方法異步獲取執行結果,而它mFuture = new FutureTask<Result>(mWorker){...},而這里FutureTask放入了自定義的Callable(WorkerRunnable),從而去執行它的call方法并拿到結果。
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
// WorkerRunnable重寫的call里面的邏輯
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
我們還應該注意到一個細節就是WorkerRunnable重寫的call方法里面的finally最后去調用了postResult(result),看名字就是知道是傳遞結果的一個方法,下面我們來看看這個方法,如下圖,我們看到它通過getHandler()這個靜態方法(因為方法是斜著的你也注意到了吧?)拿到了自定義的Handler的靜態內部類,通過Message類的obtainMessage去創建了Message,what為MESSAGE_POST_RESULT,傳遞的參數為new AsyncTaskResult<Result>(this, result)(接下來我們會介紹到它的)。
我們先來看看這個自定義的Handler的靜態內部類,如下圖,初始化構造的時候設置了主線程的Loop,而處理的消息也只是分為兩類一個是傳遞結果(MESSAGE_POST_RESULT),一個傳遞進度(MESSAGE_POST_PROGRESS),然后會調用result.mTask.xxxx(不同消息,方法不同),而我們可以知道其實這個mTask就是AsyncTask本身,因為創建AsyncTaskResult是我們第一個參數傳了this過去,所以,如果是傳遞結果就會調用AsyncTask的finish(Result result)方法,而傳遞進度就會調用onPostExecute(Result result)這個方法,很顯然是publishProgress(Progress... values)發送了這條消息不是么。
// 這就是AsyncTaskResult,果然mTask就是AsyncTask 類型,而mData[0]就是我們需要的結果
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
下面我們應該接下去去看看剛才說到的finish(Result result)這個方法,如下圖,如果不是被取消就會把結果傳遞到onPostExecute(Result result)里面從而完成AsyncTask的正常流程去,如果被取消了就會執行取消任務的相關回調,這里就不一一贅述了。
接下來我們去看看一下我們沒說的到源碼一開始的時候我們說了3.0之后AsyncTask又被Android改成了順序執行任務,而我們一開始使用的就是AsyncTask提供給我們的默認的線程池,而其實AsyncTask也提供了一個它自定義的線程池可以并發AsyncTask,下面是代碼:
// AsyncTask的默認的線程池
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// SERIAL_EXECUTOR實現是new出來的一個自定義類類
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
// AsyncTask自帶的自定義的線程池
public static final Executor THREAD_POOL_EXECUTOR;
// 線程池的參數,定義了核心線程數,最大線程數,線程工廠,空閑線程活躍時間,任務等待隊列
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() {
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>(128);
// 利用靜態代碼塊創建自定義線程池
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;
}
// 如果想要并發完成任務調用executeOnExecutor(Executor exec, Params... params)就可以了
// 這樣就實現了這三個任務并發執行了
new DownloadFilesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "url");
new DownloadFilesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "url");
new DownloadFilesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "url");
AsyncTask中默認的是sDefaultExecutor,那我們再來看看它的代碼,如下圖,它直接實現自Executor,兩個參數一個是雙端隊列(我也是Google之后才知道的)和一個Runnable,還記得這個默認的線程池的excute是什么被調用的么?是在doInBackground(Params... params)最后幾步調用的,然后把mFuture作為Runnable傳遞過去,默認的線程池的excute做了兩件事,一個是向雙端隊列尾部插入一條數據,然后判斷另一個變量mActive是否為空,那么一上來肯定為空,會執行scheduleNext()方法,移除并拿到雙端隊列的一個數據賦值給mActive,然后再調用自定義的線程池執行mActive,雖然看上去它把任務又交給了自定義的線程池,好像可以并發執行似得,但是其實仔細一看它是r.run()執行完了才會執行finally,然后在執行下一個任務的,所以就成了順序執行。
mWorker.mParams = params;
exec.execute(mFuture);
我們再來看最后一個方法,就是我們在doInBackground(Params... params)中調用的publishProgress(Progress... values),從而去通知AsyncTask調用對應的方法去更新UI,如下圖,剛才我們見過了它的Handler的靜態內部類了,所以這里一目了然發送一個消息過去。
AsyncTask注意點總結
1.Andorid3.0之后AsyncTask順序執行任務,所以如果有多個任務又想同時并發執行的話可以調用下面的代碼來實現該功能。
new xxxAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 若干參數);
new xxxAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 若干參數);
new xxxAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 若干參數);
......
2.一個AsyncTask只能執行一次。
3.任務實例必須在UI線程上創建。
4.execute(Params ...)必須在UI線程上調用。
5.不要手動調用onPreExecute(),onPostExecute(),doInBackground(),onProgressUpdate()。
6.多AsyncTask執行順序,如圖(字確實丑,見諒):
我希望可以站在初學者&自學者的角度把Android中的知識點很清楚的介紹給大家,希望大家喜歡。 如果有錯誤希望指出來,有問題或者沒看懂,都可以來問我的