AsyncTask使用和閱讀源碼筆記

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,會感覺等待的時間很長,就是因為它會一個一個任務的處理,而不是并發處理。

execute(Params... params).png

點進下一個方法可以看得到它首先會檢查當前AsyncTask的狀態,如果是運行中和運行結束都會拋出異常,因為一個AsyncTask只能執行一次,然后如果是尚未執行的狀態(PENDING)則把狀態修改為運行中,然后調用準備函數,這里有個變量mWorker和mFuture,基本步驟就是把傳進來參數賦值給mWorker的mParams和調用默認線程池執行mFuture(線程池如果沒了解過的話可以去看我之前剛寫的關于線程池的使用筆記,其實那個就是為了這個做鋪墊寫的)。
executeOnExecutor(Executor exec, Params... params).png

我們先來看看上面那兩個變量,都是在AsyncTask初始化構造的時候被實例化了出來,而WorkerRunnable只是一個自定義的Callable,用來拿到剛才的參數并執行doInBackground(mParams)并把執行結果返回回去,而FutureTask是一個用于異步獲取執行結果或取消執行任務的Java類,可以通過傳入Runnable或者Callable的任務給FutureTask,直接調用其run方法或者放入線程池執行,之后可以在外部通過FutureTask的get方法異步獲取執行結果,而它mFuture = new FutureTask<Result>(mWorker){...},而這里FutureTask放入了自定義的Callable(WorkerRunnable),從而去執行它的call方法并拿到結果。
Paste_Image.png

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)(接下來我們會介紹到它的)。

postResult(Result result).png

我們先來看看這個自定義的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)發送了這條消息不是么。

Paste_Image.png
//  這就是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的正常流程去,如果被取消了就會執行取消任務的相關回調,這里就不一一贅述了。

finish(Result result).png

接下來我們去看看一下我們沒說的到源碼一開始的時候我們說了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);

AsyncTask默認使用線程池.png

我們再來看最后一個方法,就是我們在doInBackground(Params... params)中調用的publishProgress(Progress... values),從而去通知AsyncTask調用對應的方法去更新UI,如下圖,剛才我們見過了它的Handler的靜態內部類了,所以這里一目了然發送一個消息過去。

publishProgress(Progress... values).png

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執行順序,如圖(字確實丑,見諒):

多AsyncTask執行順序.png

我希望可以站在初學者&自學者的角度把Android中的知識點很清楚的介紹給大家,希望大家喜歡。 如果有錯誤希望指出來,有問題或者沒看懂,都可以來問我的

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容