Android AsyncTask(1)-使用方法和線程池解析

當(dāng)我們?cè)赼ndroid開發(fā)中需要開N個(gè)線程的時(shí)候艇炎,很多人的做法是直接new N 個(gè) Thread踪栋。這樣做不但不利于線程管理叙身、而且大量的線程沒有得到釋放將會(huì)消耗性能羹唠,而且一般的Thread是沒有返回線程執(zhí)行完畢的返回值的,我們?cè)贏ndroid開發(fā)中可以通過handler來告訴主線程線程是否執(zhí)行完畢了健田。其實(shí)烛卧,Google工程師已經(jīng)為我們?cè)O(shè)計(jì)好了一套強(qiáng)大的異步任務(wù)管理類 - AsyncTask。那么它到底強(qiáng)大在何處呢妓局?接下來就開始慢慢講解它的強(qiáng)大之處总放。

一、AsyncTask優(yōu)點(diǎn):

  • 內(nèi)部采用了線程池機(jī)制好爬,可以有效的管理線程间聊。
  • 可以自定義線程池,實(shí)現(xiàn)多個(gè)線程按順序同步執(zhí)行抵拘、異步并發(fā)執(zhí)行哎榴。
  • 提供了回調(diào)方法,后臺(tái)任務(wù)執(zhí)行完畢后會(huì)返回需要的數(shù)據(jù)僵蛛,拿到數(shù)據(jù)后可以直接更新UI控件

二尚蝌、AsyncTask用法步驟

2.1 構(gòu)建自定義AsyncTask的參數(shù)

AsyncTask是一個(gè)抽象類,通常用于被繼承充尉,繼承AsyncTask需要指定三個(gè)泛型參數(shù)


  1. Params :啟動(dòng)任務(wù)時(shí)輸入?yún)?shù)的類型飘言。

     【就是傳進(jìn)去耗時(shí)操作任務(wù)需要用到的參數(shù),比如聯(lián)網(wǎng)請(qǐng)求String類型的URL】
    
  2. Progress:后臺(tái)任務(wù)執(zhí)行中返回進(jìn)度值的類型

     【可用于更新ProgressBar】如果要設(shè)置進(jìn)度驼侠,則第二個(gè)參數(shù)一般設(shè)置為Integer
    
  3. Result:后臺(tái)執(zhí)行任務(wù)完成之后返回結(jié)果的類型

     【耗時(shí)操作結(jié)束后返回的類型姿鸿,比如返回Bitmap】
    

    你設(shè)置的第三個(gè)參數(shù)類型是什么谆吴,doInBackGround的返回值就是什么類型



    doInBackground返回的Bitmap最終會(huì)傳遞到onPostExecute(Bitmap bitmap)中作為參數(shù)
    【AsyncTask底層是把異步耗時(shí)任務(wù)得到的數(shù)據(jù)result通過handler發(fā)送到了主線程】

2.2 必須實(shí)現(xiàn)AsyncTask的抽象方法

執(zhí)行順序從上往下。案例代碼如下面2.3節(jié)代碼所示:
(1)onPreExecute:執(zhí)行后臺(tái)耗時(shí)操作前被調(diào)用苛预,通常用于完成一些初始化操作
(2)doInBackGround:必須實(shí)現(xiàn)句狼,異步執(zhí)行后臺(tái)線程將要完成的任務(wù)【該方法在子線程運(yùn)行】
(3)onProgressUpdate:在doInBackGround方法中調(diào)用publishProgress方法,就可以更新
任務(wù)的執(zhí)行進(jìn)度
(4)onPostExecute:當(dāng)doInBackGround完成后热某,系統(tǒng)會(huì)自動(dòng)調(diào)用腻菇,并將doInBackGround方法返回的值傳給該方法

2.3 AsyncTask的用法案例

1.實(shí)例化自定義AsyncTask類,傳入一個(gè)圖片url和ImagView昔馋,execute執(zhí)行筹吐。

new MyAsyncTask(url, imageView).execute();

2.自定義AsyncTask類代碼案例:

private class MyAsyncTask extends AsyncTask<Void, Void, Bitmap> {

    private String mUrl;
    private ImageView mImageView;

    public MyAsyncTask(String url, ImageView imageView) {
        mUrl = url;
        mImageView = imageView;
    }

    @Override
    protected Bitmap doInBackground(Void... params) {
        return getBitmapFromUrl(mUrl);
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        if (mUrl.equals(mImageView.getTag()))
            mImageView.setImageBitmap(bitmap);
    }
} 

三、AsyncTask線程池

AsyncTask之所以如此強(qiáng)大秘遏,核心功臣就是它背后的線程池丘薛。
在AsyncTask中提供有兩種線程池,一個(gè)是THREAD_POOL_EXECUTOR邦危,另一個(gè)是SERIAL_EXECUTOR榔袋。
【但是,特別注意铡俐,其實(shí)在AsyncTask中只有一個(gè)線程池THREAD_POOL_EXECUTOR,只不過SERIAL_EXECUTOR實(shí)現(xiàn)了線程隊(duì)列妥粟,最終還是使用的THREAD_POOL_EXECUTOR】

1审丘、THREAD_POOL_EXECUTOR 
        多個(gè)任務(wù)可以在線程池中異步并發(fā)執(zhí)行。
2勾给、SERIAL_EXECUTOR
        把多個(gè)線程按串行的方式執(zhí)行滩报,所以是同步執(zhí)行的。
        也就是說播急,只有當(dāng)一個(gè)線程執(zhí)行完畢之后脓钾,才會(huì)執(zhí)行下個(gè)線程。

3.1 異步桩警、同步可训、并行、串行的區(qū)別

  • 異步:
    發(fā)送方發(fā)出數(shù)據(jù)后捶枢,不用等接收方發(fā)回響應(yīng)握截,接著發(fā)送下個(gè)數(shù)據(jù)包的通訊方式。
    【比如烂叔,主main函數(shù)的代碼從上往下執(zhí)行谨胞,new一個(gè)Thread并在子線程中途執(zhí)行了sleep 10秒鐘,而主main函數(shù)后面的代碼不需要等子線程sleep完10秒再執(zhí)行蒜鸡,而是直接繼續(xù)執(zhí)行下面的代碼胯努±紊眩】
  • 同步:
    發(fā)送方發(fā)出數(shù)據(jù)后,需要等接收方發(fā)回響應(yīng)以后才發(fā)下一個(gè)數(shù)據(jù)包的通訊方式叶沛。
    【比如蒲讯,主main函數(shù)的代碼從上往下執(zhí)行,如果中途執(zhí)行了sleep 10秒鐘恬汁,則后面的代碼都要等10秒后才會(huì)執(zhí)行伶椿。】
  • 并行:
    也稱為并發(fā)氓侧。從宏觀上來理解脊另,就是在同一時(shí)間內(nèi)同時(shí)執(zhí)行多個(gè)線程任務(wù)。
    【比如约巷,同時(shí)開啟10張圖片下載偎痛,宏觀上他們是10張圖同時(shí)下載的《览桑】
  • 串行:
    可以理解為踩麦,只有當(dāng)一個(gè)線程執(zhí)行完畢之后,才會(huì)執(zhí)行下個(gè)線程氓癌。
    【比如谓谦,10張圖片下載線程串行執(zhí)行,只能是第一張下載完后贪婉,才會(huì)開始執(zhí)行下一張圖片下載反粥。】

3.2 THREAD_POOL_EXECUTOR 異步線程池

ThreadPoolExecutor構(gòu)造函數(shù)需要傳遞以下幾個(gè)參數(shù):

       /**
         * @param corePoolSize 線程池的核心運(yùn)行線程數(shù)量
         * 
         * @param maximumPoolSize 線程池中可以創(chuàng)建的最大運(yùn)行線程數(shù)量【包括核心運(yùn)行線程】
         * 
         * @param keepAliveTime 當(dāng)線程池線程數(shù)量超過corePoolSize時(shí)疲迂,
         * 多余的空余線程在緩沖隊(duì)列的存活時(shí)間才顿,超時(shí)后將會(huì)被移除
         * 
         * @param unit 線程池維護(hù)線程所允許的空閑時(shí)間的單位,一般設(shè)置為秒
         * 
         * @param workQueue 線程池所使用的緩沖隊(duì)列尤蒿,可以設(shè)置緩沖隊(duì)列容納的線程數(shù)量
         * 
         * @param threadFactory 線程工廠郑气,用于創(chuàng)建線程。
         *    當(dāng)一個(gè)異步任務(wù)執(zhí)行execute的時(shí)候?qū)?huì)通過該工廠new出一個(gè)thread
         * 
         */
        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 threadFactory, defaultHandler);
        }

那么我們?cè)撊绾卫斫膺@幾個(gè)參數(shù)的意思呢腰池?
首先我們傳入線程池參數(shù):

Executor executor = new ThreadPoolExecutor(3,5,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(5));

然后尾组,同時(shí)execute執(zhí)行10個(gè)任務(wù)
看圖示解析:


  • 第一個(gè)參數(shù)傳入是3,表示初始化核心并發(fā)運(yùn)行的有三個(gè)線程示弓,當(dāng)execute任務(wù)大于3時(shí)演怎,將會(huì)放入緩沖隊(duì)列中
  • 當(dāng)緩沖隊(duì)列滿了之后,【緩沖隊(duì)列最多存5個(gè)線程】避乏,此時(shí)已經(jīng)加入進(jìn)來了8個(gè)進(jìn)程爷耀。
  • 但是,這還并沒有結(jié)束拍皮,我們?cè)O(shè)置了第二個(gè)參數(shù)為5歹叮,就意味著跑杭,在線程池中可以創(chuàng)建的最大運(yùn)行線程數(shù)量為5個(gè)。現(xiàn)在已經(jīng)有3個(gè)核心線程創(chuàng)建了咆耿,
    所以還可以再創(chuàng)建兩個(gè)線程德谅。整個(gè)線程池最大支持容納10個(gè)線程
  • 重點(diǎn)來了:這個(gè)時(shí)候萨螺,將會(huì)并發(fā)執(zhí)行5個(gè)核心線程窄做,是哪5個(gè)呢?答案是:1,2,3,9,10慰技。因?yàn)樾聞?chuàng)建的兩個(gè)運(yùn)行線程也會(huì)開始工作椭盏。
    【1,2,3線程是核心運(yùn)行線程,4,5,6,7,8在緩沖隊(duì)列吻商,9,10在運(yùn)行線程池中掏颊。】
  • 那么5個(gè)線程結(jié)束之后艾帐?是繼續(xù)并發(fā)執(zhí)行3個(gè)乌叶,還是5個(gè)呢?答案是5個(gè)柒爸,因?yàn)檫\(yùn)行線程已經(jīng)創(chuàng)建為5個(gè)了准浴。
  • 特別注意:【只有當(dāng)緩沖隊(duì)列滿的時(shí)候,才會(huì)創(chuàng)建新的運(yùn)行線程捎稚,否則默認(rèn)只按corePoolSize的數(shù)量執(zhí)行普筹。直到超出maximumPoolSize大小蜡感,拋出異痴迹】
  • 當(dāng)再加一個(gè)線程進(jìn)來時(shí)肿仑,AsyncTask中有一個(gè)拒絕策略的handler拋出異常谈撒,此時(shí)線程池已經(jīng)無法再容納更多線程任務(wù)了腥泥。
    不多說,上演示圖:


從AsyncTask默認(rèn)構(gòu)造的THREAD_POOL_EXECUTOR可以看出啃匿,AsyncTask最大支持的緩沖任務(wù)隊(duì)列是128個(gè)蛔外。


3.3 SERIAL_EXECUTOR 串行線程池

在ActivityThread中的有一段代碼,設(shè)置了當(dāng)Android 版本api 小于12,也就是版本小于3.1時(shí)溯乒,默認(rèn)是使用AsyncTask默認(rèn)的異步線程池THREAD_POOL_EXECUTOR
Android 3.1 之后使用的是 SERIAL_EXECUTOR

四夹厌、執(zhí)行AsyncTask的兩個(gè)方法:

1、execute 
    第一個(gè)執(zhí)行方法裆悄,使用的是AsyncTask默認(rèn)的線程池
2矛纹、executeOnExecutor
    第二個(gè)執(zhí)行方法,需要傳遞進(jìn)去一個(gè)Executor光稼,可以實(shí)現(xiàn)自定義線程池

4.1 execute 【默認(rèn)線程池執(zhí)行】

剛剛提到了或南,AsyncTask在3.1版本之前默認(rèn)使用THREAD_POOL_EXECUTOR線程池孩等,也就是說,3.1版本之前的使用AsynTask采够,將會(huì)是異步并發(fā)執(zhí)行肄方。

4.1.1 api8系統(tǒng)執(zhí)行execute

我們來看2.3系統(tǒng)同時(shí)execute執(zhí)行10個(gè)異步任務(wù),得到的結(jié)果:
3.1版本之前的系統(tǒng)默認(rèn)最大并發(fā)執(zhí)行5個(gè)線程蹬癌,緩沖線程隊(duì)列最大128個(gè)权她。雖然開了10個(gè)異步任務(wù),但是只能同時(shí)并發(fā)執(zhí)行5個(gè)逝薪,其他的任務(wù)都得等前面5個(gè)執(zhí)行完后才繼續(xù)執(zhí)行隅要,接著也是5次并發(fā)執(zhí)行。


4.1.2 api11以上系統(tǒng)執(zhí)行execute

那么翼闽,3.1版本之后系統(tǒng)拾徙,默認(rèn)是使用SERIAL_EXECUTOR串行任務(wù)執(zhí)行,可以預(yù)料到異步任務(wù)將會(huì)是一個(gè)個(gè)順序執(zhí)行感局。

果不其然尼啡,5.0系統(tǒng)同時(shí)execute執(zhí)行10個(gè)異步任務(wù),是一個(gè)個(gè)線程按加入順序同步執(zhí)行的询微。也就是說崖瞭,線程池中只有一個(gè)核心線程在工作,其他線程都要等之前的線程執(zhí)行完才能執(zhí)行撑毛。


4.2 executeOnExecutor 【自定義線程池執(zhí)行书聚,僅支持3.1以上系統(tǒng)】

很顯然,AsyncTask的默認(rèn)線程池完全不能滿足我們的需求藻雌,這個(gè)時(shí)候就需要用到executeOnExecutor自定義線程池了雌续。

但是,現(xiàn)在問題來了胯杭,3.1版本以下的系統(tǒng)是不支持自定義線程池執(zhí)行的驯杜!所以3.1版本以下的系統(tǒng)需要實(shí)現(xiàn)同步執(zhí)行異步任務(wù),只能用Thread自己定義線程池做个。


4.2.1 使用默認(rèn)提供的AsyncTask.THREAD_POOL_EXECUTOR線程池

AsyncTask.THREAD_POOL_EXECUTOR鸽心,代碼如下:

new MyAsyncTask(progressBar).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

這樣,3.1版本以上的系統(tǒng)便可以實(shí)現(xiàn)異步并發(fā)執(zhí)行任務(wù)了居暖。我們可以看到顽频,和3.1版本之前執(zhí)行默認(rèn)execute一樣,采用了AsyncTask默認(rèn)的線程池太闺,最大并發(fā)執(zhí)行5個(gè)線程糯景,后面的線程都只能等之前5個(gè)結(jié)束之后再執(zhí)行。


4.2.1 自定義線程池

重磅炸彈來了,我們可以自己定義線程池來執(zhí)行異步并發(fā)莺奸,這樣我們就可以自由控制線程池了丑孩。
下面代碼可以實(shí)現(xiàn)10個(gè)核心任務(wù)并發(fā)執(zhí)行,而且緩存隊(duì)列最多能夠存儲(chǔ)100個(gè)任務(wù)灭贷,當(dāng)隊(duì)列滿了之后還可以創(chuàng)建40個(gè)運(yùn)行線程温学,瞬間爆炸。

Executor executor = new ThreadPoolExecutor(10,50,10,
                TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(100));

new MyAsyncTask(progressBar).executeOnExecutor(executor);
new MyAsyncTask(progressBar2).executeOnExecutor(executor);

如圖所示:10個(gè)線程并發(fā)執(zhí)行了


五甚疟、總結(jié)

前面已經(jīng)提到過了AsyncTask的優(yōu)點(diǎn)仗岖,我們?cè)陂_發(fā)中如果合理運(yùn)用AsyncTask,將會(huì)比自己new Thread好很多览妖。那么轧拄,就來總結(jié)一下AsyncTask的最佳適用場(chǎng)景:

  • AsyncTask比較適合有大量線程執(zhí)行的情況,讓線程池去管理會(huì)更加高效讽膏。
  • 假如想讓幾個(gè)線程按順序執(zhí)行時(shí)檩电,可以使用AsyncTask。(前提是系統(tǒng)版本不能小于11)
  • 盡量使用自定義線程池府树,按需調(diào)節(jié)線程池參數(shù)俐末。

5.1 AsyncTask缺點(diǎn)

  • 之前已經(jīng)解析過了,AsyncTask在3.1系統(tǒng)以下默認(rèn)使用AsyncTask的線程池奄侠,不可以自定義線程卓箫,假如在線程不多的情況下,是很耗性能的垄潮。因?yàn)榧偃缒阒挥幸粋€(gè)異步任務(wù)烹卒,還是會(huì)創(chuàng)建另外4個(gè)核心線程。

  • 容易造成內(nèi)存泄露弯洗,如果持有context引用的話


線程太多有風(fēng)險(xiǎn)旅急,開的時(shí)候需謹(jǐn)慎吶!下一節(jié)牡整,將帶來AsyncTask的源碼解析部分藐吮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市果正,隨后出現(xiàn)的幾起案子炎码,更是在濱河造成了極大的恐慌盟迟,老刑警劉巖秋泳,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異攒菠,居然都是意外死亡迫皱,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卓起,“玉大人和敬,你說我怎么就攤上這事∠吩模” “怎么了昼弟?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)奕筐。 經(jīng)常有香客問我舱痘,道長(zhǎng),這世上最難降的妖魔是什么离赫? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任芭逝,我火速辦了婚禮,結(jié)果婚禮上渊胸,老公的妹妹穿的比我還像新娘旬盯。我一直安慰自己,他們只是感情好翎猛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布胖翰。 她就那樣靜靜地躺著,像睡著了一般办成。 火紅的嫁衣襯著肌膚如雪泡态。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天迂卢,我揣著相機(jī)與錄音某弦,去河邊找鬼。 笑死而克,一個(gè)胖子當(dāng)著我的面吹牛靶壮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播员萍,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼腾降,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了碎绎?” 一聲冷哼從身側(cè)響起螃壤,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筋帖,沒想到半個(gè)月后奸晴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡日麸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年寄啼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡墩划,死狀恐怖涕刚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乙帮,我是刑警寧澤杜漠,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站察净,受9級(jí)特大地震影響碑幅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塞绿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一沟涨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧异吻,春花似錦裹赴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至雷猪,卻和暖如春睛竣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背求摇。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工射沟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人与境。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓验夯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親摔刁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挥转,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容