一、AsyncTask的基本用法
由于A(yíng)syncTask是一個(gè)抽象類(lèi),所以如果我們想使用它,就必須要創(chuàng )建一個(gè)子類(lèi)去繼承它。在繼承時(shí)我們可以為AsyncTask類(lèi)指定三個(gè)泛型參數,這三個(gè)參數的用途如下:
1. Params
在執行AsyncTask時(shí)需要傳入的參數,可用于在后臺任務(wù)中使用。
2. Progress
后臺任務(wù)執行時(shí),如果需要在界面上顯示當前的進(jìn)度,則使用這里指定的泛型作為進(jìn)度單位。
3. Result
當任務(wù)執行完畢后,如果需要對結果進(jìn)行返回,則使用這里指定的泛型作為返回值類(lèi)型。
因此,一個(gè)最簡(jiǎn)單的自定義AsyncTask就可以寫(xiě)成如下方式:
這里我們把AsyncTask的第一個(gè)泛型參數指定為Void,表示在執行AsyncTask的時(shí)候不需要傳入參數給后臺任務(wù)。第二個(gè)泛型參數指定為Integer,表示使用整型數據來(lái)作為進(jìn)度顯示單位。第三個(gè)泛型參數指定為Boolean,則表示使用布爾型數據來(lái)反饋執行結果。
當然,目前我們自定義的DownloadTask還是一個(gè)空任務(wù),并不能進(jìn)行任何實(shí)際的操作,我們還需要去重寫(xiě)AsyncTask中的幾個(gè)方法才能完成對任務(wù)的定制。經(jīng)常需要去重寫(xiě)的方法有以下四個(gè):
1. onPreExecute()
這個(gè)方法會(huì )在后臺任務(wù)開(kāi)始執行之間調用,用于進(jìn)行一些界面上的初始化操作,比如顯示一個(gè)進(jìn)度條對話(huà)框等。
2. doInBackground(Params...)
這個(gè)方法中的所有代碼都會(huì )在子線(xiàn)程中運行,我們應該在這里去處理所有的耗時(shí)任務(wù)。任務(wù)一旦完成就可以通過(guò)return語(yǔ)句來(lái)將任務(wù)的執行結果進(jìn)行返回,如果AsyncTask的第三個(gè)泛型參數指定的是Void,就可以不返回任務(wù)執行結果。注意,在這個(gè)方法中是不可以進(jìn)行UI操作的,如果需要更新UI元素,比如說(shuō)反饋當前任務(wù)的執行進(jìn)度,可以調用publishProgress(Progress...)方法來(lái)完成。
3. onProgressUpdate(Progress...)
當在后臺任務(wù)中調用了publishProgress(Progress...)方法后,這個(gè)方法就很快會(huì )被調用,方法中攜帶的參數就是在后臺任務(wù)中傳遞過(guò)來(lái)的。在這個(gè)方法中可以對UI進(jìn)行操作,利用參數中的數值就可以對界面元素進(jìn)行相應的更新。
4. onPostExecute(Result)
當后臺任務(wù)執行完畢并通過(guò)return語(yǔ)句進(jìn)行返回時(shí),這個(gè)方法就很快會(huì )被調用。返回的數據會(huì )作為參數傳遞到此方法中,可以利用返回的數據來(lái)進(jìn)行一些UI操作,比如說(shuō)提醒任務(wù)執行的結果,以及關(guān)閉掉進(jìn)度條對話(huà)框等。
因此,一個(gè)比較完整的自定義AsyncTask就可以寫(xiě)成如下方式:
這里我們模擬了一個(gè)下載任務(wù),在doInBackground()方法中去執行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進(jìn)度,在onPostExecute()方法中來(lái)提示任務(wù)的執行結果。如果想要啟動(dòng)這個(gè)任務(wù),只需要簡(jiǎn)單地調用以下代碼即可:
以上就是AsyncTask的基本用法,我們并不需求去考慮什么異步消息處理機制,也不需要專(zhuān)門(mén)使用一個(gè)Handler來(lái)發(fā)送和接收消息,只需要調用一下publishProgress()方法就可以輕松地從子線(xiàn)程切換到UI線(xiàn)程了。
二、AsyncTask內部線(xiàn)程池
AnsycTask執行任務(wù)時(shí),內部會(huì )創(chuàng )建一個(gè)進(jìn)程作用域的線(xiàn)程池來(lái)管理要運行的任務(wù),也就就是說(shuō)當你調用了AsyncTask.execute()后,AsyncTask會(huì )把任務(wù)交給線(xiàn)程池,由線(xiàn)程池來(lái)管理創(chuàng )建Thread和運行Therad。對于內部的線(xiàn)程池不同版本的Android的實(shí)現方式是不一樣的:
3.0之前規定同一時(shí)刻能夠運行的線(xiàn)程數為5個(gè),線(xiàn)程池總大小為128。也就是說(shuō)當我們啟動(dòng)了10個(gè)任務(wù)時(shí),只有5個(gè)任務(wù)能夠立刻執行,另外的5個(gè)任務(wù)則需要等待,當有一個(gè)任務(wù)執行完畢后,第6個(gè)任務(wù)才會(huì )啟動(dòng),以此類(lèi)推。而線(xiàn)程池中最大能存放的線(xiàn)程數是128個(gè),當我們嘗試去添加第129個(gè)任務(wù)時(shí),程序就會(huì )崩潰。
因此在3.0版本中AsyncTask的改動(dòng)還是挺大的,在3.0之前的AsyncTask可以同時(shí)有5個(gè)任務(wù)在執行,而3.0之后的AsyncTask同時(shí)只能有1個(gè)任務(wù)在執行。為什么升級之后可以同時(shí)執行的任務(wù)數反而變少了呢?這是因為更新后的AsyncTask已變得更加靈活,如果不想使用默認的線(xiàn)程池,還可以自由地進(jìn)行配置。比如使用如下的代碼來(lái)啟動(dòng)任務(wù):
這樣就可以使用我們自定義的一個(gè)Executor來(lái)執行任務(wù),而不是使用SerialExecutor。上述代碼的效果允許在同一時(shí)刻有15個(gè)任務(wù)正在執行,并且最多能夠存儲200個(gè)任務(wù)。
三、AsyncTask缺陷
1.生命周期
關(guān)于A(yíng)syncTask存在一個(gè)這樣廣泛的誤解,很多人認為一個(gè)在A(yíng)ctivity中的AsyncTask會(huì )隨著(zhù)Activity的銷(xiāo)毀而銷(xiāo)毀。然后事實(shí)并非如此。AsyncTask會(huì )一直執行doInBackground()方法直到方法執行結束。一旦上述方法結束,會(huì )依據情況進(jìn)行不同的操作。
·如果cancel(boolean)調用了,則執行onCancelled(Result)方法
·如果cancel(boolean)沒(méi)有調用,則執行onPostExecute(Result)方法
AsyncTask的cancel方法需要一個(gè)布爾值的參數,參數名為mayInterruptIfRunning,意思是如果正在執行是否可以打斷,如果這個(gè)值設置為true,表示這個(gè)任務(wù)可以被打斷,否則,正在執行的程序會(huì )繼續執行直到完成。如果在doInBackground()方法中有一個(gè)循環(huán)操作,我們應該在循環(huán)中使用isCancelled()來(lái)判斷,如果返回為true,我們應該避免執行后續無(wú)用的循環(huán)操作。
總之,我們使用AsyncTask需要確保AsyncTask正確地取消。
2.不好好工作的cancel()
簡(jiǎn)而言之的答案,有時(shí)候起作用。
如果你調用了AsyncTask的cancel(false),doInBackground()仍然會(huì )執行到方法結束,只是不會(huì )去調用onPostExecute()方法。但是實(shí)際上這是讓?xiě)贸绦驁绦辛藳](méi)有意義的操作。那么是不是我們調用cancel(true)前面的問(wèn)題就能解決呢?并非如此。如果mayInterruptIfRunning設置為true,會(huì )使任務(wù)盡早結束,但是如果的doInBackground()有不可打斷的方法會(huì )失效,比如這個(gè)BitmapFactory.decodeStream() IO操作。但是你可以提前關(guān)閉IO流并捕獲這樣操作拋出的異常。但是這樣會(huì )使得cancel()方法沒(méi)有任何意義。
3.內存泄露
還有一種常見(jiàn)的情況就是,在A(yíng)ctivity中使用非靜態(tài)匿名內部AsyncTask類(lèi),由于Java內部類(lèi)的特點(diǎn),AsyncTask內部類(lèi)會(huì )持有外部類(lèi)的隱式引用。由于A(yíng)syncTask的生命周期可能比Activity的長(cháng),當Activity進(jìn)行銷(xiāo)毀AsyncTask還在執行時(shí),由于A(yíng)syncTask持有Activity的引用,導致Activity對象無(wú)法回收,進(jìn)而產(chǎn)生內存泄露。
4.結果丟失
另一個(gè)問(wèn)題就是在屏幕旋轉等造成Activity重新創(chuàng )建時(shí)AsyncTask數據丟失的問(wèn)題。當Activity銷(xiāo)毀并重新創(chuàng )建后,還在運行的AsyncTask會(huì )持有一個(gè)Activity的非法引用即之前的Activity實(shí)例。導致onPostExecute()沒(méi)有任何作用。
5.串行還是并行
下面的兩個(gè)任務(wù)時(shí)同時(shí)執行呢,還是AsyncTask1執行結束之后,AsyncTask2才能執行呢?實(shí)際上是結果依據API不同而不同。
關(guān)于A(yíng)syncTask時(shí)串行還是并行有很多疑問(wèn),這很正常,因為它經(jīng)過(guò)多次的修改。如果你并不明白什么時(shí)串行還是并行,可以通過(guò)接下來(lái)的例子了解,假設我們在一個(gè)方法體里面有如下兩行代碼:
在1.6(Donut)之前:
在第一版的AsyncTask,任務(wù)是串行調度。一個(gè)任務(wù)執行完成另一個(gè)才能執行。由于串行執行任務(wù),使用多個(gè)AsyncTask可能會(huì )帶來(lái)有些問(wèn)題。所以這并不是一個(gè)很好的處理異步(尤其是需要將結果作用于UI試圖)操作的方法。
從1.6到2.3(Gingerbread)
后來(lái)Android團隊決定讓AsyncTask并行來(lái)解決1.6之前引起的問(wèn)題,這個(gè)問(wèn)題是解決了,新的問(wèn)題又出現了。很多開(kāi)發(fā)者實(shí)際上依賴(lài)于順序執行的行為。于是很多并發(fā)的問(wèn)題蜂擁而至。
3.0(Honeycomb)到現在
好吧,開(kāi)發(fā)者可能并不喜歡讓AsyncTask并行,于是Android團隊又把AsyncTask改成了串行。當然這一次的修改并沒(méi)有完全禁止AsyncTask并行。你可以通過(guò)設置executeOnExecutor(Executor)來(lái)實(shí)現多個(gè)AsyncTask并行。關(guān)于A(yíng)PI文檔的描述如下
If we want to make sure we have control over the execution, whether it will run serially or parallel, we can check at runtime with this code to make sure it runs parallel
6.真的需要AsyncTask么
并非如此,使用AsyncTask雖然可以以簡(jiǎn)短的代碼實(shí)現異步操作,但是正如本文提到的,你需要讓AsyncTask正常工作的話(huà),需要注意很多條條框框。推薦的一種進(jìn)行異步操作的技術(shù)就是使用Loaders。這個(gè)方法從Android 3.0 (Honeycomb)開(kāi)始引入,在android支持包中也有包含??梢酝ㄟ^(guò)查看官方的文檔來(lái)詳細了解Loaders。
參考:
譯文:Android中糟糕的AsyncTask - 技術(shù)小黑屋
http://droidyue.com/blog/2014/11/08/bad-smell-of-asynctask-in-android/
Android實(shí)戰技巧:深入解析AsyncTask - 浪人的星空 - 博客頻道 - CSDN.NET
http://blog.csdn.net/hitlion2008/article/details/7983449
Android AsyncTask完全解析,帶你從源碼的角度徹底理解 - 郭霖的專(zhuān)欄 - 博客頻道 - CSDN.Net
http://blog.csdn.net/guolin_blog/article/details/11711405
聯(lián)系客服