成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

進(jìn)程,線程講到AsyncTask

姘存按 / 3382人閱讀

摘要:系統(tǒng)創(chuàng)建好進(jìn)程后,實(shí)際上就啟動(dòng)執(zhí)行了該進(jìn)程的主執(zhí)行線程。也就是說(shuō),對(duì)線程來(lái)說(shuō),進(jìn)程相當(dāng)于一個(gè)容器,可以有許多線程同時(shí)在一個(gè)進(jìn)程里執(zhí)行。默認(rèn)情況下,同一應(yīng)用程序下的所有組件都運(yùn)行在相同的進(jìn)程和線程一般稱為程序的主線程中。

一 前言

異步編程是android初學(xué)者的一個(gè)難點(diǎn),卻也是始終不能繞過(guò)的一個(gè)坎??梢哉f(shuō)幾乎每個(gè)app都逃不了網(wǎng)絡(luò)編程,而網(wǎng)絡(luò)編程又往往建立在異步的機(jī)制之上(你不應(yīng)該也無(wú)法在UI線程里執(zhí)行網(wǎng)絡(luò)請(qǐng)求,你也不應(yīng)該在UI線程中頻繁的進(jìn)行IO操作)。等等,你不知道什么是線程?那就對(duì)了,我們一起來(lái)回憶一下大學(xué)課本的知識(shí),一切從進(jìn)程講起。

二 進(jìn)程和線程

我曾經(jīng)在知乎上聽(tīng)一個(gè)朋友說(shuō)一個(gè)優(yōu)秀的程序員一定會(huì)有著極強(qiáng)的對(duì)抽象的理解能力,我很贊同這句話,我心里一直鼓勵(lì)自己:當(dāng)你對(duì)抽象不再懼怕的時(shí)候,可能你正在成為一名真正的coder。

1.進(jìn)程(process)
A process is the operating system’s abstraction for a running program
這是csapp中的原話,我覺(jué)得兩個(gè)詞特別重要,一個(gè)是abstraction,說(shuō)明進(jìn)程是一種抽象,是人為的一種定義,另一個(gè)是running,說(shuō)明進(jìn)程是正在執(zhí)行的程序,而不是保存在磁盤(pán)上的一個(gè)程序文件。不管你現(xiàn)在怎么理解進(jìn)程,你都得看下面一段代碼:

#include 
int main()
{
printf("hello, world
");
return 0;
}

這可能是我們?nèi)松鷮?xiě)得第一行代碼,讓我們?cè)诮K端里gcc得到可執(zhí)行文件a.out,然后執(zhí)行它,好,在你按下return鍵的那一瞬間到終端里打印出hello,world(好吧我承認(rèn)我詞窮了,其實(shí)就是a.out被執(zhí)行時(shí)),進(jìn)程動(dòng)態(tài)產(chǎn)生,動(dòng)態(tài)消亡。怎么直觀的感受它呢,來(lái)改一下代碼:

#include 
#include 

int main(){
        printf("Hello World from process ID %ld 
",(long)getpid());
        return 0;
}

編譯,運(yùn)行得到:

Hello World from process ID 20289

在這里我們得到了這個(gè)進(jìn)程的ID(UNIX系統(tǒng)確保每個(gè)進(jìn)程都有一個(gè)唯一的數(shù)字標(biāo)識(shí)符,稱為進(jìn)程ID,進(jìn)程ID總是一個(gè)非負(fù)整數(shù)。),這也算進(jìn)程存在的一點(diǎn)痕跡吧。我們?cè)俑膭?dòng)一下代碼:

#include 
#include 

int doSomething();

int main(){
        printf("Hello World from process ID %ld 
",(long)getpid());
        doSomething();
        return 0;
}

int doSomething(){
        printf("let us doing something from ID %ld
",(long)getpid());
        return 0;

編譯,執(zhí)行:

Hello World from process ID 20777 
let us doing something from ID 20777

可以看到這兩個(gè)函數(shù)的進(jìn)程ID是一樣的,其實(shí)你進(jìn)一步調(diào)用getppid()函數(shù)得到父進(jìn)程的函數(shù)其實(shí)也是一樣的。細(xì)心的朋友就會(huì)發(fā)現(xiàn),上一次執(zhí)行后得到的ID是20289,這次執(zhí)行得到的ID卻是20777,同樣的文件為什么每次執(zhí)行得到的ID卻是不同的呢?這就需要我們好好體會(huì)進(jìn)程是動(dòng)態(tài)產(chǎn)生動(dòng)態(tài)消亡的了,抽象嗎?

2.線程(Thread)
可以這么說(shuō),一切的抽象都是為了解放生產(chǎn)力。系統(tǒng)為什么要抽象出進(jìn)程的概念?一個(gè)直觀的解釋就是它可以讓每個(gè)進(jìn)程獨(dú)立的擁有虛擬地址空間、代碼、數(shù)據(jù)和其它各種系統(tǒng)資源,它還可以讓多個(gè)進(jìn)程同時(shí)執(zhí)行,讓你在寫(xiě)代碼的同時(shí)還能掛著微信,放著音樂(lè)??墒沁@還不夠,因?yàn)橐粋€(gè)進(jìn)程在某一時(shí)刻只能做一件事情,為了進(jìn)一步提高效率,又抽象出進(jìn)程的概念,來(lái)看下面這段話:

 線程是進(jìn)程內(nèi)部的一個(gè)執(zhí)行單元。系統(tǒng)創(chuàng)建好進(jìn)程后,實(shí)際上就啟動(dòng)執(zhí)行了該進(jìn)程的主執(zhí)行線程。主執(zhí)行線程終止了,進(jìn)程也就隨之終止。

也就是說(shuō),對(duì)線程來(lái)說(shuō),進(jìn)程相當(dāng)于一個(gè)容器,可以有許多線程同時(shí)在一個(gè)進(jìn)程里執(zhí)行。

3.安卓中的進(jìn)程與線程
這里引用官方文檔的解釋,也不知是誰(shuí)翻譯的,總之獻(xiàn)上膝蓋看原網(wǎng)頁(yè)點(diǎn)這里為了閱讀方便把原文貼出來(lái)了并改正了一些錯(cuò)別字

當(dāng)一個(gè)Android應(yīng)用程序組件啟動(dòng)時(shí)候,如果此時(shí)這個(gè)程序的其他組件沒(méi)有正在運(yùn)行,那么系統(tǒng)會(huì)為這個(gè)程序以單一線程的形式啟動(dòng)一個(gè)新的Linux 進(jìn)程。 默認(rèn)情況下,同一應(yīng)用程序下的所有組件都運(yùn)行在相同的進(jìn)程和線程(一般稱為程序的“主”線程)中。如果一個(gè)應(yīng)用組件啟動(dòng)但這個(gè)應(yīng)用的進(jìn)程已經(jīng)存在了(因?yàn)檫@個(gè)應(yīng)用的其他組件已經(jīng)在之前啟動(dòng)了),那么這個(gè)組件將會(huì)在這個(gè)進(jìn)程中啟動(dòng),同時(shí)在這個(gè)應(yīng)用的主線程里面執(zhí)行。然而,你也可以讓你的應(yīng)用里面的組件運(yùn)行在 不同的進(jìn)程里面,也可以為任何進(jìn)程添加額外的線程。

這片文章討論了Android程序里面的進(jìn)程和線程如何運(yùn)作的。

進(jìn)程

默認(rèn)情況下,同一程序的所有組件都運(yùn)行在相同的進(jìn)程里面,大多數(shù)的應(yīng)用都是這樣的。然而,如果你發(fā)現(xiàn)你需要讓你的程序里面的某個(gè)組件運(yùn)行在特定的進(jìn)程里面,你可以在manifest 文件里面設(shè)置。

manifest 文件里面為每一個(gè)組件元素—, , , 和—提供了 android:process 屬 性。通過(guò)設(shè)置這個(gè)屬性你可以讓組件運(yùn)行在特定的進(jìn)程中。你可以設(shè)置成每個(gè)組件運(yùn)行在自己的進(jìn)程中,也可以讓一些組件共享一個(gè)進(jìn)程而其他的不這樣。你還可以 設(shè)置成不同應(yīng)用的組件運(yùn)行在同一個(gè)進(jìn)程里面—這樣可以讓這些應(yīng)用共享相同的Linux user ID同時(shí)被相同的證書(shū)所認(rèn)證。

元素也支持 android:process 屬性,設(shè)置這個(gè)屬性可以讓這個(gè)應(yīng)用里面的所有組件都默認(rèn)繼承這個(gè)屬性。

Android 可能在系統(tǒng)剩余內(nèi)存較少,而其他直接服務(wù)用戶的進(jìn)程又要申請(qǐng)內(nèi)存的時(shí)候shut down 一個(gè)進(jìn)程, 這時(shí)這個(gè)進(jìn)程里面的組件也會(huì)依次被kill掉。當(dāng)這些組件有新的任務(wù)到達(dá)時(shí),他們對(duì)應(yīng)的進(jìn)程又會(huì)被啟動(dòng)。

在決定哪些進(jìn)程需要被kill的時(shí)候,Android系統(tǒng)會(huì)權(quán)衡這些進(jìn)程跟用戶相關(guān)的重要性。比如,相對(duì)于那些承載這可見(jiàn)的activities的 進(jìn)程,系統(tǒng)會(huì)更容易的kill掉那些承載不再可見(jiàn)activities的進(jìn)程。決定是否終結(jié)一個(gè)進(jìn)程取決于這個(gè)進(jìn)程里面的組件運(yùn)行的狀態(tài)。下面我們會(huì)討論 kill進(jìn)程時(shí)所用到的一些規(guī)則。

進(jìn)程的生命周期

作為一個(gè)多任務(wù)的系統(tǒng),Android 當(dāng)然系統(tǒng)能夠盡可能長(zhǎng)的保留一個(gè)應(yīng)用進(jìn)程。但是由于新的或者更重要的進(jìn)程需要更多的內(nèi)存,系統(tǒng)不得不逐漸終結(jié)老的進(jìn)程來(lái)獲取內(nèi)存。為了聲明哪些進(jìn)程需要保 留,哪些需要kill,系統(tǒng)根據(jù)這些進(jìn)程里面的組件以及這些組件的狀態(tài)為每個(gè)進(jìn)程生成了一個(gè)“重要性層級(jí)” 。處于最低重要性層級(jí)的進(jìn)程將會(huì)第一時(shí)間被清除,接著是重要性高一點(diǎn),然后依此類推,根據(jù)系統(tǒng)需要來(lái)終結(jié)進(jìn)程。

在這個(gè)重要性層級(jí)里面有5個(gè)等級(jí)。下面的列表按照重要性排序展示了不同類型的進(jìn)程(第一種進(jìn)程是最重要的,因此將會(huì)在最后被kill):

Foreground進(jìn)程 一個(gè)正在和用戶進(jìn)行交互的進(jìn)程,如果一個(gè)進(jìn)程處于下面的狀態(tài)之一,那么我們可以把這個(gè)進(jìn)程稱為 foreground 進(jìn)程:
進(jìn)程包含了一個(gè)與用戶交互的 Activity (這個(gè) Activity的 onResume() 方法被調(diào)用)。
進(jìn)程包含了一個(gè)綁定了與用戶交互的activity的 Service 。
進(jìn)程包含了一個(gè)運(yùn)行在”in the foreground”狀態(tài)的 Service —這個(gè) service 調(diào)用了 startForeground()方法。
進(jìn)程包含了一個(gè)正在運(yùn)行的它的生命周期回調(diào)函數(shù) (onCreate(), onStart(), oronDestroy())的 Service 。
進(jìn)程包含了一個(gè)正在運(yùn)行 onReceive() 方法的 BroadcastReceiver 。
一般說(shuō)來(lái),任何時(shí)候,系統(tǒng)中只存在少數(shù)的 foreground 進(jìn)程。 只有在系統(tǒng)內(nèi)存特別緊張以至于都無(wú)法繼續(xù)運(yùn)行下去的時(shí)候,系統(tǒng)才會(huì)通過(guò)kill這些進(jìn)程來(lái)緩解內(nèi)存壓力。在這樣的時(shí)候系統(tǒng)必須kill一些 (Generally, at that point, the device has reached a memory paging state,這句如何翻譯較好呢)foreground 進(jìn)程來(lái)保證 用戶的交互有響應(yīng)。

Visible進(jìn)程 一個(gè)進(jìn)程沒(méi)有任何 foreground 組件, 但是它還能影響屏幕上的顯示。 如果一個(gè)進(jìn)程處于下面的狀態(tài)之一,那么我們可以把這個(gè)進(jìn)程稱為 visible 進(jìn)程:
進(jìn)程包含了一個(gè)沒(méi)有在foreground 狀態(tài)的 Activity ,但是它仍然被用戶可見(jiàn) (它的 onPause() 方法已經(jīng)被調(diào)用)。這種情況是有可能出現(xiàn)的,比如,一個(gè) foreground activity 啟動(dòng)了一個(gè) dialog,這樣就會(huì)讓之前的 activity 在dialog的后面部分可見(jiàn)。
進(jìn)程包含了一個(gè)綁定在一個(gè)visible(或者foreground)activity的 Service 。
一個(gè) visible 進(jìn)程在系統(tǒng)中是相當(dāng)重要的,只有在為了讓所有的foreground 進(jìn)程正常運(yùn)行時(shí)才會(huì)考慮去kill visible 進(jìn)程。

Service進(jìn)程 一個(gè)包含著已經(jīng)以 startService() 方法啟動(dòng)的 Service 的 進(jìn)程,同時(shí)還沒(méi)有進(jìn)入上面兩種更高級(jí)別的種類。盡管 service 進(jìn)程沒(méi)有與任何用戶所看到的直接關(guān)聯(lián),但是它們經(jīng)常被用來(lái)做用戶在意的事情(比如在后臺(tái)播放音樂(lè)或者下載網(wǎng)絡(luò)數(shù)據(jù)),所以系統(tǒng)也只會(huì)在為了保證所有的 foreground and visible 進(jìn)程正常運(yùn)行時(shí)kill掉 service 進(jìn)程。

Background進(jìn)程 一個(gè)包含了已不可見(jiàn)的activity的 進(jìn)程 (這個(gè) activity 的 onStop() 已 經(jīng)被調(diào)用)。這樣的進(jìn)程不會(huì)直接影響用戶的體驗(yàn),系統(tǒng)也可以為了foreground 、visible 或者 service 進(jìn)程隨時(shí)kill掉它們。一般說(shuō)來(lái),系統(tǒng)中有許多的 background 進(jìn)程在運(yùn)行,所以將它們保持在一個(gè)LRU (least recently used)列表中可以確保用戶最近看到的activity 所屬的進(jìn)程將會(huì)在最后被kill。如果一個(gè) activity 正確的實(shí)現(xiàn)了它的生命周期回調(diào)函數(shù),保存了自己的當(dāng)前狀態(tài),那么kill這個(gè)activity所在的進(jìn)程是不會(huì)對(duì)用戶在視覺(jué)上的體驗(yàn)有影響的,因?yàn)楫?dāng)用戶 回退到這個(gè) activity時(shí),它的所有的可視狀態(tài)將會(huì)被恢復(fù)。查看 Activities 可以獲取更多如果保存和恢復(fù)狀態(tài)的文檔。
Empty 進(jìn)程 一個(gè)不包含任何活動(dòng)的應(yīng)用組件的進(jìn)程。 這種進(jìn)程存在的唯一理由就是緩存。為了提高一個(gè)組件的啟動(dòng)的時(shí)間需要讓組件在這種進(jìn)程里運(yùn)行。為了平衡進(jìn)程緩存和相關(guān)內(nèi)核緩存的系統(tǒng)資源,系統(tǒng)需要kill這些進(jìn)程。
Android是根據(jù)進(jìn)程中組件的重要性盡可能高的來(lái)評(píng)級(jí)的。比如,如果一個(gè)進(jìn)程包含來(lái)一個(gè) service 和一個(gè)可見(jiàn) activity,那么這個(gè)進(jìn)程將會(huì)被評(píng)為 visible 進(jìn)程,而不是 service 進(jìn)程。

另外,一個(gè)進(jìn)程的評(píng)級(jí)可能會(huì)因?yàn)槠渌栏皆谒厦娴倪M(jìn)程而被提升—一個(gè)服務(wù)其他進(jìn)程的進(jìn)程永遠(yuǎn)不會(huì)比它正在服務(wù)的進(jìn)程評(píng)級(jí)低的。比如,如果進(jìn)程A中 的一個(gè) content provider 正在為進(jìn)程B中的客戶端服務(wù),或者如果進(jìn)程A中的一個(gè) service 綁定到進(jìn)程B中的一個(gè)組件,進(jìn)程A的評(píng)級(jí)會(huì)被系統(tǒng)認(rèn)為至少比進(jìn)程B要高。

因?yàn)檫M(jìn)程里面運(yùn)行著一個(gè) service 的評(píng)級(jí)要比一個(gè)包含background activities的進(jìn)程要高,所以當(dāng)一個(gè) activity 啟動(dòng)長(zhǎng)時(shí)操作時(shí),最好啟動(dòng)一個(gè) service 來(lái) 做這個(gè)操作,而不是簡(jiǎn)單的創(chuàng)建一個(gè)worker線程—特別是當(dāng)這個(gè)長(zhǎng)時(shí)操作可能會(huì)拖垮這個(gè)activity。比如,一個(gè)需要上傳圖片到一個(gè)網(wǎng)站的 activity 應(yīng)當(dāng)開(kāi)啟一個(gè)來(lái)執(zhí)行這個(gè)上傳操作。這樣的話,即使用戶離開(kāi)來(lái)這個(gè)activity也能保證上傳動(dòng)作在后臺(tái)繼續(xù)。使用 service 可以保證操作至少處于”service process” 這個(gè)優(yōu)先級(jí),無(wú)論這個(gè)activity發(fā)生了什么。這也是為什么 broadcast receivers 應(yīng)該使用 services 而不是簡(jiǎn)單的將耗時(shí)的操作放到線程里面。

線程

當(dāng)一個(gè)應(yīng)用啟動(dòng)的時(shí)候,系統(tǒng)會(huì)為它創(chuàng)建一個(gè)線程,稱為“主線程”。這個(gè)線程很重要因?yàn)樗?fù)責(zé)處理調(diào)度事件到相關(guān)的 user interface widgets,包括繪制事件。你的應(yīng)用也是在這個(gè)線程里面與來(lái)自Android UI toolkit (包括來(lái)自 android.widget 和 android.view 包的組件)的組件進(jìn)行交互。因此,這個(gè)主線程有時(shí)候也被稱為 UI 線程。

系統(tǒng)沒(méi)有為每個(gè)組件創(chuàng)建一個(gè)多帶帶的線程。同一進(jìn)程里面的所有組件都是在UI 線程里面被實(shí)例化的,系統(tǒng)對(duì)每個(gè)組件的調(diào)用都是用過(guò)這個(gè)線程進(jìn)行調(diào)度的。所以,響應(yīng)系統(tǒng)調(diào)用的方法(比如 onKeyDown() 方法是用來(lái)捕捉用戶動(dòng)作或者一個(gè)生命周期回調(diào)函數(shù))都運(yùn)行在進(jìn)程的UI 線程里面。

比如,當(dāng)用戶點(diǎn)擊屏幕上的按鈕,你的應(yīng)用的UI 線程會(huì)將這個(gè)點(diǎn)擊事件傳給 widget,接著這個(gè)widget設(shè)置它的按壓狀態(tài),然后發(fā)送一個(gè)失效的請(qǐng)求到事件隊(duì)列。這個(gè)UI 線程對(duì)請(qǐng)求進(jìn)行出隊(duì)操作,然后處理(通知這個(gè)widget重新繪制自己)。

當(dāng)你的應(yīng)用與用戶交互對(duì)響應(yīng)速度的要求比較高時(shí),這個(gè)單線程模型可能會(huì)產(chǎn)生糟糕的效果(除非你很好的實(shí)現(xiàn)了你的應(yīng)用)。特別是,當(dāng)應(yīng)用中所有的事情 都發(fā)生在UI 線程里面,那些訪問(wèn)網(wǎng)絡(luò)數(shù)據(jù)和數(shù)據(jù)庫(kù)查詢等長(zhǎng)時(shí)操作都會(huì)阻塞整個(gè)UI線程。當(dāng)整個(gè)線程被阻塞時(shí),所有事件都不能被傳遞,包括繪制事件。這在用戶看來(lái),這個(gè) 應(yīng)用假死了。甚至更糟糕的是,如果UI 線程被阻塞幾秒(當(dāng)前是5秒)以上,系統(tǒng)將會(huì)彈出臭名昭著的 “application not responding” (ANR) 對(duì)話框。這時(shí)用戶可能選擇退出你的應(yīng)用甚至卸載。

另外,Android的UI 線程不是線程安全的。所以你不能在一個(gè)worker 線程操作你的UI—你必須在UI線程上對(duì)你的UI進(jìn)行操作。這有兩條簡(jiǎn)單的關(guān)于Android單線程模型的規(guī)則:

不要阻塞 UI 線程
不要在非UI線程里訪問(wèn) Android UI toolkit
Worker 線程

由于上面對(duì)單一線程模型的描述,保證應(yīng)用界面的及時(shí)響應(yīng)同時(shí)UI線程不被阻塞變得很重要。如果你不能讓?xiě)?yīng)用里面的操作短時(shí)被執(zhí)行玩,那么你應(yīng)該確保把這些操作放到獨(dú)立的線程里(“background” or “worker” 線程)。

比如,下面這段代碼在一個(gè)額外的線程里面下載圖片并在一個(gè) ImageView顯示:

new Thread(new Runnable(){ 
    public void run(){ 
        Bitmap b = loadImageFromNetwork("http://example.com/image.png"); 
        mImageView.setImageBitmap(b); 
    } 
}).start();}

起先這段代碼看起來(lái)不錯(cuò),因?yàn)樗鼊?chuàng)建一個(gè)新的線程來(lái)處理網(wǎng)絡(luò)操作。然而,它違反來(lái)單一線程模型的第二條規(guī)則: 不在非UI線程里訪問(wèn) Android UI toolkit—這個(gè)例子在一個(gè)worker線程修改了 ImageView 。這會(huì)導(dǎo)致不可預(yù)期的結(jié)果,而且還難以調(diào)試。

為了修復(fù)這個(gè)問(wèn)題,Android提供了幾個(gè)方法從非UI線程訪問(wèn)Android UI toolkit 。詳見(jiàn)下面的這個(gè)列表:

Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
那么,你可以使用 View.post(Runnable) 方法來(lái)修改之前的代碼:

public void onClick(View v){ 
    new Thread(new Runnable(){ 
        public void run(){ 
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); 
            mImageView.post(new Runnable(){ 
                public void run(){ 
                    mImageView.setImageBitmap(bitmap); 
                } 
            }); 
        } 
    }).start();} 

現(xiàn)在這個(gè)方案的線程安全的:這個(gè)網(wǎng)絡(luò)操作在獨(dú)立線程中完成后,UI線程便會(huì)對(duì)ImageView 進(jìn)行操作。

然而,隨著操作復(fù)雜性的增長(zhǎng),代碼會(huì)變得越來(lái)越復(fù)雜,越來(lái)越難維護(hù)。為了用worker 線程處理更加復(fù)雜的交互,你可以考慮在worker線程中使用Handler ,用它來(lái)處理UI線程中的消息。也許最好的方案就是繼承 AsyncTask 類,這個(gè)類簡(jiǎn)化了需要同UI進(jìn)行交互的worker線程任務(wù)的執(zhí)行。

使用 AsyncTask

AsyncTask 能讓你在UI上進(jìn)行異步操作。它在一個(gè)worker線程里進(jìn)行一些阻塞操作然后把結(jié)果交給UI主線程,在這個(gè)過(guò)程中不需要你對(duì)線程或者h(yuǎn)andler進(jìn)行處理。

使用它,你必須繼承 AsyncTask 并實(shí)現(xiàn) doInBackground() 回調(diào)方法,這個(gè)方法運(yùn)行在一個(gè)后臺(tái)線程池里面。如果你需要更新UI,那么你應(yīng)該實(shí)現(xiàn)onPostExecute(),這個(gè)方法從 doInBackground() 取出結(jié)果,然后在 UI 線程里面運(yùn)行,所以你可以安全的更新你的UI。你可以通過(guò)在UI線程調(diào)用 execute()方法來(lái)運(yùn)行這個(gè)任務(wù)。

比如,你可以通過(guò)使用 AsyncTask來(lái)實(shí)現(xiàn)之前的例子:

public void onClick(View v){ 
    new DownloadImageTask().execute("http://example.com/image.png"); 
} 
private class DownloadImageTask extends AsyncTask{ 

protected Bitmap doInBackground(String... urls){ 
    return loadImageFromNetwork(urls[0]); 
} 

protected void onPostExecute(Bitmap result){ 
    mImageView.setImageBitmap(result); 
}} 

現(xiàn)在UI是安全的了,代碼也更加簡(jiǎn)單了,因?yàn)锳syncTask把worker線程里做的事和UI線程里要做的事分開(kāi)了。

你應(yīng)該閱讀一下 AsyncTask 的參考文檔以便更好的使用它。下面就是一個(gè)對(duì) AsyncTask 如何作用的快速的總覽:

你可以具體設(shè)置參數(shù)的類型,進(jìn)度值,任務(wù)的終值,使用的范型
doInBackground() 方法自動(dòng)在 worker 線程執(zhí)行
onPreExecute(), onPostExecute(), 和 onProgressUpdate() 方法都是在UI線程被調(diào)用
doInBackground() 的返回值會(huì)被送往 onPostExecute()方法
你可以隨時(shí)在 doInBackground()方法里面調(diào)用 publishProgress() 方法來(lái)執(zhí)行UI 線程里面的onProgressUpdate() 方法
你可以從任何線程取消這個(gè)任務(wù)
注意: 你在使用worker線程的時(shí)候可能會(huì)碰到的另一個(gè)問(wèn)題就是因?yàn)閞untime configuration change (比如用戶改變了屏幕的方向)導(dǎo)致你的activity不可預(yù)期的重啟,這可能會(huì)kill掉你的worker線程。為了解決這個(gè)問(wèn)題你可以參考 Shelves 這個(gè)項(xiàng)目。

線程安全的方法

在某些情況下,你實(shí)現(xiàn)的方法可能會(huì)被多個(gè)線程所調(diào)用,因此你必須把它寫(xiě)出線程安全的。

大家先不要困在上面這篇文章中的具體代碼實(shí)現(xiàn)上,把關(guān)注點(diǎn)放在Android中進(jìn)程,線程和android基本組件之間的關(guān)系上。我們看完了如何在java中進(jìn)行線程操作之后再去學(xué)習(xí)Android相關(guān)機(jī)制就會(huì)相對(duì)容易一些。

三 java并發(fā)編程

java并發(fā)編程是一個(gè)很龐大的話題,我不會(huì)也沒(méi)有能力講得過(guò)于深入,我沒(méi)辦法告訴你淘寶網(wǎng)是怎么處理每秒成千上萬(wàn)次的點(diǎn)擊而屹立不倒,我只會(huì)講一下為什么我們可以利用java并發(fā)編程讓?xiě)?yīng)用在下載文件的同時(shí)UI不會(huì)卡頓。java并發(fā)操作可以讓我們把一個(gè)程序分成幾部分,各自獨(dú)立的去完成任務(wù)。好首先我們來(lái)定義一下這里的任務(wù)(tasks)。

1.定義tasks
一個(gè)線程承載著一個(gè)任務(wù),如何描述它呢?java中提供Runnable這個(gè)接口,來(lái),上代碼:

public class ExampleTask implements Runnable {
    private static int taskCount = 0;
    private final int id = taskCount++;
    protected int count = 10;

    private String status(){
        return "#"+id+": "+"count is "+count;
    }

    @Override
    public void run() {
        while (count -- > 0){
            System.out.println(status());
            Thread.yield();//the part of the Java threading mechanism that moves the CPU from one thread to the next
        }
    }
}

注意靜態(tài)變量taskCount和final變量int,是為了該類每次被實(shí)例化時(shí)能有一個(gè)獨(dú)一無(wú)二的id。
在覆寫(xiě)的run方法中我們通常放入一個(gè)循環(huán),先不用理會(huì)yield方法。
然后我們?cè)谝粋€(gè)線程中將它實(shí)例化并調(diào)用run方法:

public class MainThread {
    public static void main(String args[]){
        ExampleTask exampleTask = new ExampleTask();
        exampleTask.run();
    }
}

結(jié)果如下:

#0: count is 9
#0: count is 8
#0: count is 7
#0: count is 6
#0: count is 5
#0: count is 4
#0: count is 3
#0: count is 2
#0: count is 1
#0: count is 0

這里并沒(méi)有什么特別之處,只是被main方法調(diào)用而已(也就是存在于系統(tǒng)分配給main的線程中)。

2 Thread類
Thread類被實(shí)例化時(shí),即在當(dāng)前進(jìn)程中創(chuàng)建一個(gè)新的線程,來(lái)看代碼:

public class BasicThread {
    public static void main(String[] args){
        Thread t = new Thread(new ExampleTask());
        t.start();
        System.out.println("ExampleTask任務(wù)即將開(kāi)始");
    }
}

可以看出我們需要將ExampleTask傳給Thread的構(gòu)造方法,上面說(shuō)過(guò)任務(wù)是對(duì)線程的描述,這里也就不難理解了。我們先看一下執(zhí)行結(jié)果:

結(jié)果一

#0: count is 9
ExampleTask任務(wù)即將開(kāi)始
#0: count is 8
#0: count is 7
#0: count is 6
#0: count is 5
#0: count is 4
#0: count is 3
#0: count is 2
#0: count is 1
#0: count is 0

結(jié)果二

ExampleTask任務(wù)即將開(kāi)始
#0: count is 9
#0: count is 8
#0: count is 7
#0: count is 6
#0: count is 5
#0: count is 4
#0: count is 3
#0: count is 2
#0: count is 1
#0: count is 0

不用奇怪我為什么給出這兩種結(jié)果(尤其是第一種),因?yàn)樵诙啻芜\(yùn)行試驗(yàn)中確確實(shí)實(shí)出現(xiàn)了這兩種結(jié)果。我們來(lái)分析一下,當(dāng)我們實(shí)例化Thread并將Task傳遞給它時(shí),當(dāng)前進(jìn)程將在main()線程之外重新創(chuàng)建一個(gè)t線程,然后我們執(zhí)行t.start(),這個(gè)方法會(huì)做一些必要的線程初始化的工作然后就通知t線程里的ExampleTask任務(wù)需要執(zhí)行run方法了,然后start會(huì)迅速return到main()線程,所以我們不必等到ExampleTask里的run方法里面的循環(huán)執(zhí)行完就可以看見(jiàn)

 ExampleTask任務(wù)即將開(kāi)始

至于為什么會(huì)發(fā)現(xiàn)第一種情況,我猜測(cè)是由于start返回的不夠快,讓t線程搶先了(對(duì),就這么生動(dòng)的理解線程你就不會(huì)怕了,雖然解釋的很糟糕)
再看看下面這代碼:

public class MoreBasicThread {
    public static void main(String args[]){
        for (int i=0;i<5;i++){
            Thread t = new Thread(new ExampleTask());
            t.start();
        }
        System.out.println("前方高能!多個(gè)線程即將開(kāi)始打架!");
    }
}


現(xiàn)在你可以回過(guò)頭去看一下這段代碼了:

 public void onClick(View v){ 
        new Thread(new Runnable(){ 
            public void run(){ 
                final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); 
                mImageView.post(new Runnable(){ 
                    public void run(){ 
                        mImageView.setImageBitmap(bitmap); 
                    } 
                }); 
            } 
        }).start();} 
四 AsyncTask

有了上面這些知識(shí)的鋪墊,我們回到Android中。我們?cè)O(shè)想一個(gè)場(chǎng)景,當(dāng)用戶點(diǎn)擊某個(gè)Button時(shí),我們想從網(wǎng)絡(luò)上加載一些文本到當(dāng)前UI,前面說(shuō)了我們沒(méi)辦法在UI線程中直接進(jìn)行網(wǎng)絡(luò)請(qǐng)求(因?yàn)榭赡軙?huì)有阻塞UI線程的風(fēng)險(xiǎn)),現(xiàn)在我們很容易想到在當(dāng)前進(jìn)程中再創(chuàng)建一個(gè)線程,讓其執(zhí)行網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求完成后再來(lái)更新UI,比如上面的方案,我們還可以用安卓給我們提供的AsyncTask,使用起來(lái)更加方便,也更容易維護(hù),操作起來(lái):

1.準(zhǔn)備工作

public class Loader {
    public byte[] getUrlBytes(String urlSpecfic)throws IOException{
        URL url = new URL(urlSpecfic);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        try {
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK){
                throw new IOException(connection.getResponseMessage()+"with"+urlSpecfic);
            }
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            InputStream in = connection.getInputStream();
            byte[] buffer = new byte[1024];
            int byteRead = 0;
            while ((byteRead = in.read(buffer))>0){
                out.write(buffer,0,byteRead);
            }
            out.close();
            return out.toByteArray();
        }finally {
            connection.disconnect();
        }
    }

    public String getUrlString(String urlSpecific) throws IOException{
        return new String(getUrlBytes(urlSpecific));
    }
}

這個(gè)類的主要作用是請(qǐng)求特定url的網(wǎng)絡(luò)資源,不理解的話要么跳過(guò),要么去找一本java書(shū)回顧一下java網(wǎng)絡(luò)編程。
接下來(lái)是布局文件:很簡(jiǎn)單,一個(gè)TextView,一個(gè)Button




    

    

2.使用AsyncTask

public class MainActivity extends AppCompatActivity {
    private TextView urlText;
    private Button urlButton;
    private Loader loader = new Loader();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        urlText = (TextView) findViewById(R.id.url_text);
        urlButton = (Button) findViewById(R.id.url_button);
        urlButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new DownLoader().execute("https://segmentfault.com/");
            }
        });


    }

 private class DownLoader extends AsyncTask{

        @Override
        protected String doInBackground(String... params) {
            try {
                return loader.getUrlString(params[0]);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String s) {
            urlText.setText(s);
        }
    }
}

這里覆寫(xiě)了兩個(gè)方法,doInBackground會(huì)在一個(gè)新的線程里執(zhí)行,參數(shù)類型由AsyncTask的第一個(gè)泛型參數(shù)決定,返回參數(shù)由AsyncTask的第三個(gè)泛型參數(shù)決定,其返回值會(huì)傳遞給onPostExecute方法。而onPostExecute方法是可以操作UI線程的,故用其為urlText賦值。好,編譯,運(yùn)行,點(diǎn)擊按鈕,幾秒鐘后urlText里的內(nèi)容便被請(qǐng)求回來(lái)的segmentfault的首頁(yè)html所替換。

五 后記

設(shè)想如果我們需要請(qǐng)求的內(nèi)容遠(yuǎn)不止一個(gè)html文件,可能是一個(gè)非常龐大的json數(shù)據(jù)或者是無(wú)窮無(wú)盡的圖片資源,如果還用上面的方法,恐怕用戶會(huì)在urlText前等到終老,別擔(dān)心,安卓提供了非常令人頭痛但是也同樣非常高效的異步機(jī)制HandlerThread,Looper,Handler以及Message。別怕,別虛。下次我們一起征服。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65602.html

相關(guān)文章

  • 串行還是并行?——記一次 AsyncTask 問(wèn)題排查

    摘要:當(dāng)然,如果你的核心數(shù)夠多,到個(gè)線程的并行度不滿足的話,也可以自定義一個(gè)線程池來(lái)執(zhí)行,不過(guò)這樣的話,要注意自己維護(hù)這個(gè)線程池的初始化,釋放等等操作了。 事情起源于一個(gè)bug排查,一個(gè)AsyncTask的子類,執(zhí)行的時(shí)候發(fā)現(xiàn)onPreExecute方法執(zhí)行了,doInBackground卻遲遲沒(méi)有被調(diào)用。懂AsyncTask一些表面原理的都知道,onPreExecute方法是在主線程執(zhí)行,...

    mo0n1andin 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<