摘要:系統(tǒng)創(chuàng)建好進(jìn)程后,實際上就啟動執(zhí)行了該進(jìn)程的主執(zhí)行線程。也就是說,對線程來說,進(jìn)程相當(dāng)于一個容器,可以有許多線程同時在一個進(jìn)程里執(zhí)行。默認(rèn)情況下,同一應(yīng)用程序下的所有組件都運行在相同的進(jìn)程和線程一般稱為程序的主線程中。
一 前言
異步編程是android初學(xué)者的一個難點,卻也是始終不能繞過的一個坎??梢哉f幾乎每個app都逃不了網(wǎng)絡(luò)編程,而網(wǎng)絡(luò)編程又往往建立在異步的機制之上(你不應(yīng)該也無法在UI線程里執(zhí)行網(wǎng)絡(luò)請求,你也不應(yīng)該在UI線程中頻繁的進(jìn)行IO操作)。等等,你不知道什么是線程?那就對了,我們一起來回憶一下大學(xué)課本的知識,一切從進(jìn)程講起。
二 進(jìn)程和線程我曾經(jīng)在知乎上聽一個朋友說一個優(yōu)秀的程序員一定會有著極強的對抽象的理解能力,我很贊同這句話,我心里一直鼓勵自己:當(dāng)你對抽象不再懼怕的時候,可能你正在成為一名真正的coder。
1.進(jìn)程(process)
A process is the operating system’s abstraction for a running program
這是csapp中的原話,我覺得兩個詞特別重要,一個是abstraction,說明進(jìn)程是一種抽象,是人為的一種定義,另一個是running,說明進(jìn)程是正在執(zhí)行的程序,而不是保存在磁盤上的一個程序文件。不管你現(xiàn)在怎么理解進(jìn)程,你都得看下面一段代碼:
#includeint main() { printf("hello, world "); return 0; }
這可能是我們?nèi)松鷮懙玫谝恍写a,讓我們在終端里gcc得到可執(zhí)行文件a.out,然后執(zhí)行它,好,在你按下return鍵的那一瞬間到終端里打印出hello,world(好吧我承認(rèn)我詞窮了,其實就是a.out被執(zhí)行時),進(jìn)程動態(tài)產(chǎn)生,動態(tài)消亡。怎么直觀的感受它呢,來改一下代碼:
#include#include int main(){ printf("Hello World from process ID %ld ",(long)getpid()); return 0; }
編譯,運行得到:
Hello World from process ID 20289
在這里我們得到了這個進(jìn)程的ID(UNIX系統(tǒng)確保每個進(jìn)程都有一個唯一的數(shù)字標(biāo)識符,稱為進(jìn)程ID,進(jìn)程ID總是一個非負(fù)整數(shù)。),這也算進(jìn)程存在的一點痕跡吧。我們再改動一下代碼:
#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
可以看到這兩個函數(shù)的進(jìn)程ID是一樣的,其實你進(jìn)一步調(diào)用getppid()函數(shù)得到父進(jìn)程的函數(shù)其實也是一樣的。細(xì)心的朋友就會發(fā)現(xiàn),上一次執(zhí)行后得到的ID是20289,這次執(zhí)行得到的ID卻是20777,同樣的文件為什么每次執(zhí)行得到的ID卻是不同的呢?這就需要我們好好體會進(jìn)程是動態(tài)產(chǎn)生動態(tài)消亡的了,抽象嗎?
2.線程(Thread)
可以這么說,一切的抽象都是為了解放生產(chǎn)力。系統(tǒng)為什么要抽象出進(jìn)程的概念?一個直觀的解釋就是它可以讓每個進(jìn)程獨立的擁有虛擬地址空間、代碼、數(shù)據(jù)和其它各種系統(tǒng)資源,它還可以讓多個進(jìn)程同時執(zhí)行,讓你在寫代碼的同時還能掛著微信,放著音樂??墒沁@還不夠,因為一個進(jìn)程在某一時刻只能做一件事情,為了進(jìn)一步提高效率,又抽象出進(jìn)程的概念,來看下面這段話:
線程是進(jìn)程內(nèi)部的一個執(zhí)行單元。系統(tǒng)創(chuàng)建好進(jìn)程后,實際上就啟動執(zhí)行了該進(jìn)程的主執(zhí)行線程。主執(zhí)行線程終止了,進(jìn)程也就隨之終止。
也就是說,對線程來說,進(jìn)程相當(dāng)于一個容器,可以有許多線程同時在一個進(jìn)程里執(zhí)行。
3.安卓中的進(jìn)程與線程
這里引用官方文檔的解釋,也不知是誰翻譯的,總之獻(xiàn)上膝蓋看原網(wǎng)頁點這里為了閱讀方便把原文貼出來了并改正了一些錯別字
當(dāng)一個Android應(yīng)用程序組件啟動時候,如果此時這個程序的其他組件沒有正在運行,那么系統(tǒng)會為這個程序以單一線程的形式啟動一個新的Linux 進(jìn)程。 默認(rèn)情況下,同一應(yīng)用程序下的所有組件都運行在相同的進(jìn)程和線程(一般稱為程序的“主”線程)中。如果一個應(yīng)用組件啟動但這個應(yīng)用的進(jìn)程已經(jīng)存在了(因為這個應(yīng)用的其他組件已經(jīng)在之前啟動了),那么這個組件將會在這個進(jìn)程中啟動,同時在這個應(yīng)用的主線程里面執(zhí)行。然而,你也可以讓你的應(yīng)用里面的組件運行在 不同的進(jìn)程里面,也可以為任何進(jìn)程添加額外的線程。
這片文章討論了Android程序里面的進(jìn)程和線程如何運作的。
進(jìn)程
默認(rèn)情況下,同一程序的所有組件都運行在相同的進(jìn)程里面,大多數(shù)的應(yīng)用都是這樣的。然而,如果你發(fā)現(xiàn)你需要讓你的程序里面的某個組件運行在特定的進(jìn)程里面,你可以在manifest 文件里面設(shè)置。
manifest 文件里面為每一個組件元素—
Android 可能在系統(tǒng)剩余內(nèi)存較少,而其他直接服務(wù)用戶的進(jìn)程又要申請內(nèi)存的時候shut down 一個進(jìn)程, 這時這個進(jìn)程里面的組件也會依次被kill掉。當(dāng)這些組件有新的任務(wù)到達(dá)時,他們對應(yīng)的進(jìn)程又會被啟動。
在決定哪些進(jìn)程需要被kill的時候,Android系統(tǒng)會權(quán)衡這些進(jìn)程跟用戶相關(guān)的重要性。比如,相對于那些承載這可見的activities的 進(jìn)程,系統(tǒng)會更容易的kill掉那些承載不再可見activities的進(jìn)程。決定是否終結(jié)一個進(jìn)程取決于這個進(jìn)程里面的組件運行的狀態(tài)。下面我們會討論 kill進(jìn)程時所用到的一些規(guī)則。
進(jìn)程的生命周期
作為一個多任務(wù)的系統(tǒng),Android 當(dāng)然系統(tǒng)能夠盡可能長的保留一個應(yīng)用進(jìn)程。但是由于新的或者更重要的進(jìn)程需要更多的內(nèi)存,系統(tǒng)不得不逐漸終結(jié)老的進(jìn)程來獲取內(nèi)存。為了聲明哪些進(jìn)程需要保 留,哪些需要kill,系統(tǒng)根據(jù)這些進(jìn)程里面的組件以及這些組件的狀態(tài)為每個進(jìn)程生成了一個“重要性層級” 。處于最低重要性層級的進(jìn)程將會第一時間被清除,接著是重要性高一點,然后依此類推,根據(jù)系統(tǒng)需要來終結(jié)進(jìn)程。
在這個重要性層級里面有5個等級。下面的列表按照重要性排序展示了不同類型的進(jìn)程(第一種進(jìn)程是最重要的,因此將會在最后被kill):
Foreground進(jìn)程 一個正在和用戶進(jìn)行交互的進(jìn)程,如果一個進(jìn)程處于下面的狀態(tài)之一,那么我們可以把這個進(jìn)程稱為 foreground 進(jìn)程:
進(jìn)程包含了一個與用戶交互的 Activity (這個 Activity的 onResume() 方法被調(diào)用)。
進(jìn)程包含了一個綁定了與用戶交互的activity的 Service 。
進(jìn)程包含了一個運行在”in the foreground”狀態(tài)的 Service —這個 service 調(diào)用了 startForeground()方法。
進(jìn)程包含了一個正在運行的它的生命周期回調(diào)函數(shù) (onCreate(), onStart(), oronDestroy())的 Service 。
進(jìn)程包含了一個正在運行 onReceive() 方法的 BroadcastReceiver 。
一般說來,任何時候,系統(tǒng)中只存在少數(shù)的 foreground 進(jìn)程。 只有在系統(tǒng)內(nèi)存特別緊張以至于都無法繼續(xù)運行下去的時候,系統(tǒng)才會通過kill這些進(jìn)程來緩解內(nèi)存壓力。在這樣的時候系統(tǒng)必須kill一些 (Generally, at that point, the device has reached a memory paging state,這句如何翻譯較好呢)foreground 進(jìn)程來保證 用戶的交互有響應(yīng)。
Visible進(jìn)程 一個進(jìn)程沒有任何 foreground 組件, 但是它還能影響屏幕上的顯示。 如果一個進(jìn)程處于下面的狀態(tài)之一,那么我們可以把這個進(jìn)程稱為 visible 進(jìn)程:
進(jìn)程包含了一個沒有在foreground 狀態(tài)的 Activity ,但是它仍然被用戶可見 (它的 onPause() 方法已經(jīng)被調(diào)用)。這種情況是有可能出現(xiàn)的,比如,一個 foreground activity 啟動了一個 dialog,這樣就會讓之前的 activity 在dialog的后面部分可見。
進(jìn)程包含了一個綁定在一個visible(或者foreground)activity的 Service 。
一個 visible 進(jìn)程在系統(tǒng)中是相當(dāng)重要的,只有在為了讓所有的foreground 進(jìn)程正常運行時才會考慮去kill visible 進(jìn)程。
Service進(jìn)程 一個包含著已經(jīng)以 startService() 方法啟動的 Service 的 進(jìn)程,同時還沒有進(jìn)入上面兩種更高級別的種類。盡管 service 進(jìn)程沒有與任何用戶所看到的直接關(guān)聯(lián),但是它們經(jīng)常被用來做用戶在意的事情(比如在后臺播放音樂或者下載網(wǎng)絡(luò)數(shù)據(jù)),所以系統(tǒng)也只會在為了保證所有的 foreground and visible 進(jìn)程正常運行時kill掉 service 進(jìn)程。
Background進(jìn)程 一個包含了已不可見的activity的 進(jìn)程 (這個 activity 的 onStop() 已 經(jīng)被調(diào)用)。這樣的進(jìn)程不會直接影響用戶的體驗,系統(tǒng)也可以為了foreground 、visible 或者 service 進(jìn)程隨時kill掉它們。一般說來,系統(tǒng)中有許多的 background 進(jìn)程在運行,所以將它們保持在一個LRU (least recently used)列表中可以確保用戶最近看到的activity 所屬的進(jìn)程將會在最后被kill。如果一個 activity 正確的實現(xiàn)了它的生命周期回調(diào)函數(shù),保存了自己的當(dāng)前狀態(tài),那么kill這個activity所在的進(jìn)程是不會對用戶在視覺上的體驗有影響的,因為當(dāng)用戶 回退到這個 activity時,它的所有的可視狀態(tài)將會被恢復(fù)。查看 Activities 可以獲取更多如果保存和恢復(fù)狀態(tài)的文檔。
Empty 進(jìn)程 一個不包含任何活動的應(yīng)用組件的進(jìn)程。 這種進(jìn)程存在的唯一理由就是緩存。為了提高一個組件的啟動的時間需要讓組件在這種進(jìn)程里運行。為了平衡進(jìn)程緩存和相關(guān)內(nèi)核緩存的系統(tǒng)資源,系統(tǒng)需要kill這些進(jìn)程。
Android是根據(jù)進(jìn)程中組件的重要性盡可能高的來評級的。比如,如果一個進(jìn)程包含來一個 service 和一個可見 activity,那么這個進(jìn)程將會被評為 visible 進(jìn)程,而不是 service 進(jìn)程。
另外,一個進(jìn)程的評級可能會因為其他依附在它上面的進(jìn)程而被提升—一個服務(wù)其他進(jìn)程的進(jìn)程永遠(yuǎn)不會比它正在服務(wù)的進(jìn)程評級低的。比如,如果進(jìn)程A中 的一個 content provider 正在為進(jìn)程B中的客戶端服務(wù),或者如果進(jìn)程A中的一個 service 綁定到進(jìn)程B中的一個組件,進(jìn)程A的評級會被系統(tǒng)認(rèn)為至少比進(jìn)程B要高。
因為進(jìn)程里面運行著一個 service 的評級要比一個包含background activities的進(jìn)程要高,所以當(dāng)一個 activity 啟動長時操作時,最好啟動一個 service 來 做這個操作,而不是簡單的創(chuàng)建一個worker線程—特別是當(dāng)這個長時操作可能會拖垮這個activity。比如,一個需要上傳圖片到一個網(wǎng)站的 activity 應(yīng)當(dāng)開啟一個來執(zhí)行這個上傳操作。這樣的話,即使用戶離開來這個activity也能保證上傳動作在后臺繼續(xù)。使用 service 可以保證操作至少處于”service process” 這個優(yōu)先級,無論這個activity發(fā)生了什么。這也是為什么 broadcast receivers 應(yīng)該使用 services 而不是簡單的將耗時的操作放到線程里面。
線程
當(dāng)一個應(yīng)用啟動的時候,系統(tǒng)會為它創(chuàng)建一個線程,稱為“主線程”。這個線程很重要因為它負(fù)責(zé)處理調(diào)度事件到相關(guān)的 user interface widgets,包括繪制事件。你的應(yīng)用也是在這個線程里面與來自Android UI toolkit (包括來自 android.widget 和 android.view 包的組件)的組件進(jìn)行交互。因此,這個主線程有時候也被稱為 UI 線程。
系統(tǒng)沒有為每個組件創(chuàng)建一個多帶帶的線程。同一進(jìn)程里面的所有組件都是在UI 線程里面被實例化的,系統(tǒng)對每個組件的調(diào)用都是用過這個線程進(jìn)行調(diào)度的。所以,響應(yīng)系統(tǒng)調(diào)用的方法(比如 onKeyDown() 方法是用來捕捉用戶動作或者一個生命周期回調(diào)函數(shù))都運行在進(jìn)程的UI 線程里面。
比如,當(dāng)用戶點擊屏幕上的按鈕,你的應(yīng)用的UI 線程會將這個點擊事件傳給 widget,接著這個widget設(shè)置它的按壓狀態(tài),然后發(fā)送一個失效的請求到事件隊列。這個UI 線程對請求進(jìn)行出隊操作,然后處理(通知這個widget重新繪制自己)。
當(dāng)你的應(yīng)用與用戶交互對響應(yīng)速度的要求比較高時,這個單線程模型可能會產(chǎn)生糟糕的效果(除非你很好的實現(xiàn)了你的應(yīng)用)。特別是,當(dāng)應(yīng)用中所有的事情 都發(fā)生在UI 線程里面,那些訪問網(wǎng)絡(luò)數(shù)據(jù)和數(shù)據(jù)庫查詢等長時操作都會阻塞整個UI線程。當(dāng)整個線程被阻塞時,所有事件都不能被傳遞,包括繪制事件。這在用戶看來,這個 應(yīng)用假死了。甚至更糟糕的是,如果UI 線程被阻塞幾秒(當(dāng)前是5秒)以上,系統(tǒng)將會彈出臭名昭著的 “application not responding” (ANR) 對話框。這時用戶可能選擇退出你的應(yīng)用甚至卸載。
另外,Android的UI 線程不是線程安全的。所以你不能在一個worker 線程操作你的UI—你必須在UI線程上對你的UI進(jìn)行操作。這有兩條簡單的關(guān)于Android單線程模型的規(guī)則:
不要阻塞 UI 線程
不要在非UI線程里訪問 Android UI toolkit
Worker 線程
由于上面對單一線程模型的描述,保證應(yīng)用界面的及時響應(yīng)同時UI線程不被阻塞變得很重要。如果你不能讓應(yīng)用里面的操作短時被執(zhí)行玩,那么你應(yīng)該確保把這些操作放到獨立的線程里(“background” or “worker” 線程)。
比如,下面這段代碼在一個額外的線程里面下載圖片并在一個 ImageView顯示:
new Thread(new Runnable(){ public void run(){ Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start();}
起先這段代碼看起來不錯,因為它創(chuàng)建一個新的線程來處理網(wǎng)絡(luò)操作。然而,它違反來單一線程模型的第二條規(guī)則: 不在非UI線程里訪問 Android UI toolkit—這個例子在一個worker線程修改了 ImageView 。這會導(dǎo)致不可預(yù)期的結(jié)果,而且還難以調(diào)試。
為了修復(fù)這個問題,Android提供了幾個方法從非UI線程訪問Android UI toolkit 。詳見下面的這個列表:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
那么,你可以使用 View.post(Runnable) 方法來修改之前的代碼:
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)在這個方案的線程安全的:這個網(wǎng)絡(luò)操作在獨立線程中完成后,UI線程便會對ImageView 進(jìn)行操作。
然而,隨著操作復(fù)雜性的增長,代碼會變得越來越復(fù)雜,越來越難維護(hù)。為了用worker 線程處理更加復(fù)雜的交互,你可以考慮在worker線程中使用Handler ,用它來處理UI線程中的消息。也許最好的方案就是繼承 AsyncTask 類,這個類簡化了需要同UI進(jìn)行交互的worker線程任務(wù)的執(zhí)行。
使用 AsyncTask
AsyncTask 能讓你在UI上進(jìn)行異步操作。它在一個worker線程里進(jìn)行一些阻塞操作然后把結(jié)果交給UI主線程,在這個過程中不需要你對線程或者h(yuǎn)andler進(jìn)行處理。
使用它,你必須繼承 AsyncTask 并實現(xiàn) doInBackground() 回調(diào)方法,這個方法運行在一個后臺線程池里面。如果你需要更新UI,那么你應(yīng)該實現(xiàn)onPostExecute(),這個方法從 doInBackground() 取出結(jié)果,然后在 UI 線程里面運行,所以你可以安全的更新你的UI。你可以通過在UI線程調(diào)用 execute()方法來運行這個任務(wù)。
比如,你可以通過使用 AsyncTask來實現(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是安全的了,代碼也更加簡單了,因為AsyncTask把worker線程里做的事和UI線程里要做的事分開了。
你應(yīng)該閱讀一下 AsyncTask 的參考文檔以便更好的使用它。下面就是一個對 AsyncTask 如何作用的快速的總覽:
你可以具體設(shè)置參數(shù)的類型,進(jìn)度值,任務(wù)的終值,使用的范型
doInBackground() 方法自動在 worker 線程執(zhí)行
onPreExecute(), onPostExecute(), 和 onProgressUpdate() 方法都是在UI線程被調(diào)用
doInBackground() 的返回值會被送往 onPostExecute()方法
你可以隨時在 doInBackground()方法里面調(diào)用 publishProgress() 方法來執(zhí)行UI 線程里面的onProgressUpdate() 方法
你可以從任何線程取消這個任務(wù)
注意: 你在使用worker線程的時候可能會碰到的另一個問題就是因為runtime configuration change (比如用戶改變了屏幕的方向)導(dǎo)致你的activity不可預(yù)期的重啟,這可能會kill掉你的worker線程。為了解決這個問題你可以參考 Shelves 這個項目。
線程安全的方法
在某些情況下,你實現(xiàn)的方法可能會被多個線程所調(diào)用,因此你必須把它寫出線程安全的。
大家先不要困在上面這篇文章中的具體代碼實現(xiàn)上,把關(guān)注點放在Android中進(jìn)程,線程和android基本組件之間的關(guān)系上。我們看完了如何在java中進(jìn)行線程操作之后再去學(xué)習(xí)Android相關(guān)機制就會相對容易一些。
三 java并發(fā)編程java并發(fā)編程是一個很龐大的話題,我不會也沒有能力講得過于深入,我沒辦法告訴你淘寶網(wǎng)是怎么處理每秒成千上萬次的點擊而屹立不倒,我只會講一下為什么我們可以利用java并發(fā)編程讓應(yīng)用在下載文件的同時UI不會卡頓。java并發(fā)操作可以讓我們把一個程序分成幾部分,各自獨立的去完成任務(wù)。好首先我們來定義一下這里的任務(wù)(tasks)。
1.定義tasks
一個線程承載著一個任務(wù),如何描述它呢?java中提供Runnable這個接口,來,上代碼:
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,是為了該類每次被實例化時能有一個獨一無二的id。
在覆寫的run方法中我們通常放入一個循環(huán),先不用理會yield方法。
然后我們在一個線程中將它實例化并調(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
這里并沒有什么特別之處,只是被main方法調(diào)用而已(也就是存在于系統(tǒng)分配給main的線程中)。
2 Thread類
Thread類被實例化時,即在當(dāng)前進(jìn)程中創(chuàng)建一個新的線程,來看代碼:
public class BasicThread { public static void main(String[] args){ Thread t = new Thread(new ExampleTask()); t.start(); System.out.println("ExampleTask任務(wù)即將開始"); } }
可以看出我們需要將ExampleTask傳給Thread的構(gòu)造方法,上面說過任務(wù)是對線程的描述,這里也就不難理解了。我們先看一下執(zhí)行結(jié)果:
結(jié)果一
#0: count is 9 ExampleTask任務(wù)即將開始 #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ù)即將開始 #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é)果(尤其是第一種),因為在多次運行試驗中確確實實出現(xiàn)了這兩種結(jié)果。我們來分析一下,當(dāng)我們實例化Thread并將Task傳遞給它時,當(dāng)前進(jìn)程將在main()線程之外重新創(chuàng)建一個t線程,然后我們執(zhí)行t.start(),這個方法會做一些必要的線程初始化的工作然后就通知t線程里的ExampleTask任務(wù)需要執(zhí)行run方法了,然后start會迅速return到main()線程,所以我們不必等到ExampleTask里的run方法里面的循環(huán)執(zhí)行完就可以看見
ExampleTask任務(wù)即將開始
至于為什么會發(fā)現(xiàn)第一種情況,我猜測是由于start返回的不夠快,讓t線程搶先了(對,就這么生動的理解線程你就不會怕了,雖然解釋的很糟糕)
再看看下面這代碼:
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("前方高能!多個線程即將開始打架!"); } }
現(xiàn)在你可以回過頭去看一下這段代碼了:
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
有了上面這些知識的鋪墊,我們回到Android中。我們設(shè)想一個場景,當(dāng)用戶點擊某個Button時,我們想從網(wǎng)絡(luò)上加載一些文本到當(dāng)前UI,前面說了我們沒辦法在UI線程中直接進(jìn)行網(wǎng)絡(luò)請求(因為可能會有阻塞UI線程的風(fēng)險),現(xiàn)在我們很容易想到在當(dāng)前進(jìn)程中再創(chuàng)建一個線程,讓其執(zhí)行網(wǎng)絡(luò)請求,請求完成后再來更新UI,比如上面的方案,我們還可以用安卓給我們提供的AsyncTask,使用起來更加方便,也更容易維護(hù),操作起來:
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)); } }
這個類的主要作用是請求特定url的網(wǎng)絡(luò)資源,不理解的話要么跳過,要么去找一本java書回顧一下java網(wǎng)絡(luò)編程。
接下來是布局文件:很簡單,一個TextView,一個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); } } }
這里覆寫了兩個方法,doInBackground會在一個新的線程里執(zhí)行,參數(shù)類型由AsyncTask的第一個泛型參數(shù)決定,返回參數(shù)由AsyncTask的第三個泛型參數(shù)決定,其返回值會傳遞給onPostExecute方法。而onPostExecute方法是可以操作UI線程的,故用其為urlText賦值。好,編譯,運行,點擊按鈕,幾秒鐘后urlText里的內(nèi)容便被請求回來的segmentfault的首頁html所替換。
五 后記設(shè)想如果我們需要請求的內(nèi)容遠(yuǎn)不止一個html文件,可能是一個非常龐大的json數(shù)據(jù)或者是無窮無盡的圖片資源,如果還用上面的方法,恐怕用戶會在urlText前等到終老,別擔(dān)心,安卓提供了非常令人頭痛但是也同樣非常高效的異步機制HandlerThread,Looper,Handler以及Message。別怕,別虛。下次我們一起征服。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/65602.html
摘要:當(dāng)然,如果你的核心數(shù)夠多,到個線程的并行度不滿足的話,也可以自定義一個線程池來執(zhí)行,不過這樣的話,要注意自己維護(hù)這個線程池的初始化,釋放等等操作了。 事情起源于一個bug排查,一個AsyncTask的子類,執(zhí)行的時候發(fā)現(xiàn)onPreExecute方法執(zhí)行了,doInBackground卻遲遲沒有被調(diào)用。懂AsyncTask一些表面原理的都知道,onPreExecute方法是在主線程執(zhí)行,...
閱讀 2683·2023-04-25 17:33
閱讀 718·2021-11-23 09:51
閱讀 3040·2021-07-30 15:32
閱讀 1501·2019-08-29 18:40
閱讀 2030·2019-08-28 18:19
閱讀 1529·2019-08-26 13:48
閱讀 2314·2019-08-23 16:48
閱讀 2356·2019-08-23 15:56