當(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ù)
-
Params :啟動(dòng)任務(wù)時(shí)輸入?yún)?shù)的類型飘言。
【就是傳進(jìn)去耗時(shí)操作任務(wù)需要用到的參數(shù),比如聯(lián)網(wǎng)請(qǐng)求String類型的URL】
-
Progress:后臺(tái)任務(wù)執(zhí)行中返回進(jìn)度值的類型
【可用于更新ProgressBar】如果要設(shè)置進(jìn)度驼侠,則第二個(gè)參數(shù)一般設(shè)置為Integer
-
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的源碼解析部分藐吮。