Android 進(jìn)程和線程詳解
當(dāng)啟動一個應(yīng)用程序組件時,如果該應(yīng)用沒有正在運(yùn)行的其它程序組件,那么Android系統(tǒng)將為這個應(yīng)用創(chuàng)建一個新進(jìn)程(包含一個線程)用于運(yùn)行應(yīng)用。缺省情況下,一個應(yīng)用的所有組件(Activity,Service等)運(yùn)行在同一個進(jìn)程和線程中(稱為“主”線程)。如果在啟動一個應(yīng)用程序組件時,這個應(yīng)用已經(jīng)有進(jìn)程在運(yùn)行(因?yàn)橛袘?yīng)用的其它組件存在),那么這個應(yīng)用程序組件將使用同一進(jìn)程和線程運(yùn)行。當(dāng)然你可以使用不同進(jìn)程來運(yùn)行不同的組件,或者在進(jìn)程中創(chuàng)建新的線程。
進(jìn)程
缺省情況,應(yīng)用的所有組件都運(yùn)行在同一個進(jìn)程,而且應(yīng)用不應(yīng)該改變這個傳統(tǒng)。然而,如果你發(fā)現(xiàn)你需要控制某個組件運(yùn)行在那個進(jìn)程中,你可以通過應(yīng)用程序清單來配置。
在應(yīng)用程序清單文件中,每個類型的應(yīng)用程序組件-<activity>,<service>,<receiver>和<provider>都支持 android:process 屬性,這個屬性用來指明該程序組件運(yùn)行的進(jìn)程。你可以為應(yīng)用程序組件設(shè)置這個屬性以使每個組件運(yùn)行在不同的進(jìn)程中或者某幾個組件使用同一進(jìn)程。你也可以通過設(shè)置android:process 使得不同應(yīng)用中的組件運(yùn)行在同一個進(jìn)程中-前提是這些應(yīng)用使用同一個Linux用戶名并且使用同一個證書簽名。
<Application>元素也支持 android: process 屬性,用來為應(yīng)用程序的所有組件設(shè)置缺省的進(jìn)程。
Android系統(tǒng)中系統(tǒng)資源過低而且有需要為用戶立即提供服務(wù)的進(jìn)程需要啟動時可能會終止某些進(jìn)程的運(yùn)行。運(yùn)行在這些被終止的進(jìn)程中的程序組件將逐個被銷毀。此后如果還有工作需要這些應(yīng)用程序組件時將啟動新的進(jìn)程。
系統(tǒng)中決定哪些進(jìn)程可以殺死時,系統(tǒng)將權(quán)衡這些進(jìn)程對用戶的重要性。比如,對于那些運(yùn)行不可見的Activity的進(jìn)程比運(yùn)行屏幕上可見的Activity的進(jìn)程更容易被殺死。
進(jìn)程生命周期
Android系統(tǒng)會盡可能長的保持應(yīng)用程序進(jìn)程的運(yùn)行,但總會有需要清除舊的進(jìn)程來釋放資源以滿足新或是重要的進(jìn)程的運(yùn)行。為了決定哪些進(jìn)程可以殺死,哪些進(jìn)程需要保留,系統(tǒng)根據(jù)運(yùn)行在其中的應(yīng)用程序組件和這些組件的狀態(tài),將這些進(jìn)程分配到“重要性層次表”中。具有最低重要性的進(jìn)程首先被殺死,次重要性的進(jìn)程為其次等等直到系統(tǒng)恢復(fù)所需的資源。
“重要性層次表”可以分為五個層次,下面列表給出了不同類型的進(jìn)程的重要性等級(最重要的排在前面):
1.前臺進(jìn)程
這種進(jìn)程是當(dāng)前用戶所需要的。一個進(jìn)程被認(rèn)為是前臺進(jìn)程需滿足下面條件之一:
· 本進(jìn)程中有Activity是當(dāng)前和用戶有交互的Activity(該Activity的onResume()已調(diào)用)。
· 本進(jìn)程中有Service和當(dāng)前用戶有交互Activity的綁定。
· 本進(jìn)程中有在前臺運(yùn)行的Service—該Service調(diào)用過startForeground()。
· 本進(jìn)程中有Service正在執(zhí)行某個生命周期回調(diào)函數(shù)(onCreate(),onStart()或onDestroy())。
· 本進(jìn)程中的某個BroadcastReceiver正在執(zhí)行onReceive()方法。
2.可視進(jìn)程
這種進(jìn)程雖然不含有任何在前臺運(yùn)行的組件,但會影響當(dāng)前顯示給用戶屏幕上的內(nèi)容,一個進(jìn)程中滿足下面兩個條件之一時被認(rèn)為是個可見進(jìn)程:
· 本進(jìn)程含有一個雖然不在前臺但卻部分可見的Activity(該Activity的onPause()被調(diào)用)??赡馨l(fā)生的情形是前臺Activity顯示一對話框,此時之前的Activity變?yōu)椴糠挚梢姟?br />
· 本進(jìn)程含有綁定到可見Activity的Service。
3. 服務(wù)進(jìn)程
該進(jìn)程運(yùn)行了某個使用startService()啟動的Service,但不屬于以上兩種情況。盡管此服務(wù)進(jìn)程不直接和用戶可以看到的任何部分有關(guān)聯(lián),但它會運(yùn)行一些用戶關(guān)心的事情(比如在后臺播放音樂或者通過網(wǎng)絡(luò)下載文件)。因此Android系統(tǒng)會盡量讓它們運(yùn)行直到系統(tǒng)資源低到無法滿足前臺和可見進(jìn)程的運(yùn)行。
4.后臺進(jìn)程
該進(jìn)程運(yùn)行一些目前用戶不可見的Activity(該Activity的onStop()已被調(diào)用),該進(jìn)程對用戶體驗(yàn)無直接的影響,系統(tǒng)中資源低時為保證前臺,可見或服務(wù)進(jìn)程運(yùn)行時可以隨時殺死該進(jìn)程。通常系統(tǒng)中有很多進(jìn)程在后臺運(yùn)行,這些進(jìn)程保存在LRU(最近使用過)列表中以保證用戶最后看到的進(jìn)程最后被殺死。如果一個Activity正確實(shí)現(xiàn)了它的生命周期函數(shù),并保存了它的狀態(tài)。殺死運(yùn)行該Activity的進(jìn)程對用戶來說在視覺上不會有什么影響,這是因?yàn)橹笥脩艋氐皆揂ctivity時,該Activity能夠正確恢復(fù)之前屏幕上的狀態(tài)。
5.空進(jìn)程
該進(jìn)程不運(yùn)行任何活動的應(yīng)用程序組件。保持這種進(jìn)程運(yùn)行的唯一原因是由于緩存,以縮短下次運(yùn)行某個程序組件時的啟動時間。系統(tǒng)會為了進(jìn)程緩存和內(nèi)核緩存之間的平衡經(jīng)常會清除空進(jìn)程。
Android系統(tǒng)會根據(jù)進(jìn)程中當(dāng)前活動的程序組件的重要性,近可能高的給該進(jìn)程評級。比如,如果一個進(jìn)程中同時有一個Service和一個可見的Activity在運(yùn)行,該進(jìn)程將被定級為可見進(jìn)程而不是服務(wù)進(jìn)程(可見進(jìn)程的優(yōu)先級高于服務(wù)進(jìn)程)。
此外,一個進(jìn)程的級別可能有對其有依賴的其它進(jìn)程提升—一個給其它進(jìn)程提供服務(wù)的進(jìn)程的級別不會低于它所服務(wù)的進(jìn)程的級別。比如,進(jìn)程A中的Content Provider 給進(jìn)程B中某客戶端提供數(shù)據(jù)服務(wù)或者進(jìn)程A中某個服務(wù)被進(jìn)程B某組件所綁定。那么進(jìn)程A重要性程度不會低于進(jìn)程B。
由于運(yùn)行Service的進(jìn)程的級別高于運(yùn)行后臺Activity的進(jìn)程的級別,一個需要較長時間運(yùn)行操作的Activity 啟動能夠完成該操作的Service可能也能很好的完成任務(wù)而無需簡單創(chuàng)建一個新工作線程—尤其是該操作運(yùn)行時間比該Activity還要長。比如,如果一個Activity需要完成向服務(wù)器上傳圖片任務(wù)時應(yīng)該使用一個服務(wù)來完成上載任務(wù),這些即使用戶離開該Activity,Service依然可以在后臺完成上載任務(wù)。使用Service可以保證某個操作至少具有“服務(wù)進(jìn)程”的優(yōu)先級而無需關(guān)心該Activity發(fā)生了什么變化。這也是一個Broadcast Receiver應(yīng)該使用一個Service而非一線程來完成某個耗時的任務(wù)。
線程
Android系統(tǒng)啟動某個應(yīng)用后,將會創(chuàng)建一個線程來運(yùn)行該應(yīng)用,這個線程成為“主”線程。主線程非常重要,這是因?yàn)樗?fù)責(zé)消息的分發(fā),給界面上相應(yīng)的UI組件分發(fā)事件,包括繪圖事件。這也是應(yīng)用可以和UI組件(為android.widget和android.view中定義的組件)發(fā)生直接交互的線程。因此主線程也通常稱為用戶界面線程(UI線程)。
Android系統(tǒng)不會主動為應(yīng)用程序的組件創(chuàng)建額外的線程。運(yùn)用在同一進(jìn)程中所有程序組件都在UI線程中初始化,并使用UI線程來分發(fā)對這些程序組件的系統(tǒng)調(diào)用。由此可見,響應(yīng)系統(tǒng)回調(diào)函數(shù)(比如onKeyDown() 響應(yīng)用戶按鍵或者某個生命周期回調(diào)函數(shù))的方法總是使用UI線程來運(yùn)行。
比如,當(dāng)用戶觸摸屏幕上某個按鈕時,你的應(yīng)用中的UI線程將把這個觸摸事件發(fā)送到對應(yīng)的UI小組件,然后該UI小組件設(shè)置其按下的狀態(tài)并給事件隊(duì)列發(fā)送一個刷新的請求,之后UI線程處理事件隊(duì)列并通知該UI小組件重新繪制自身。
當(dāng)你的應(yīng)用中響應(yīng)用戶事件時需要完成一些費(fèi)事的工作時,這種單線程工作模式可能會導(dǎo)致非常差的用戶響應(yīng)性能。尤其是如果所有的工作都在UI線程中完成,比如訪問網(wǎng)絡(luò),數(shù)據(jù)庫查詢等費(fèi)時的工作將會阻塞UI線程。當(dāng)UI線程被阻塞時,就無法分發(fā)事件,包括繪圖事件。此時從用戶的角度來看,該應(yīng)用看起來不再有響應(yīng)。更為糟糕的是,如果UI線程阻塞超過幾秒鐘(目前為五秒),系統(tǒng)將給用戶顯示著名的“應(yīng)用程序無響應(yīng)”(ANR)對話框。用戶可能會選擇退出你的應(yīng)用,更為甚者如果他們感覺很不滿意也會選擇卸載你的應(yīng)用。
此外,Android的UI組件包不是“線程安全”的,因此你不能走工作線程中調(diào)用UI組件的方法,所有有關(guān)UI的操作必須在UI線程中完成,因此下面為使用UI單線程工作線程的兩個規(guī)則:
1. 永遠(yuǎn)不要阻塞UI線程。
2. 不要在非UI線程中操作UI組件。
工作線程
由于Android使用單線程工作模式,因此不阻塞UI線程對于應(yīng)用程序的響應(yīng)性能至關(guān)重要。如果在你的應(yīng)用中包含一些不是一瞬間就能完成的操作的話,你應(yīng)用使用額外的線程(工作線程或是后臺線程)來執(zhí)行這些操作。
比如下面示例,在用戶點(diǎn)擊某個按鈕后,就啟動一個新線程來下載某個圖像然后在ImageView中顯示:
[java] view plaincopyprint?
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
private Bitmap loadImageFromNetwork(String string) {
// TODO Auto-generated method stub
return null;
}
}).start();
}
乍一看,這段代碼應(yīng)該很好的完成工作,因?yàn)樗鼊?chuàng)建了一個新線程來完成網(wǎng)絡(luò)操作。然而它違法了上面說的第二個規(guī)則:不要在非UI線程中操作UI組件。在這段代碼中的工作線程中而不是在UI線程中,直接修改ImageView,這將導(dǎo)致一些不可以預(yù)見的后果,常常導(dǎo)致發(fā)現(xiàn)此類錯誤捕捉異常困難和費(fèi)時。
為了更正此類錯誤,Android提供了多種方法使得在非UI線程中訪問UI組件,下面給出了其中的幾種方法:
· Activity.runOnUiThread (Runnable) 方法
· View.post (Runnable) 方法
· View.postDelayed (Runnable) 方法
比如,使用View.post(Runnable)修改上面的代碼:
[java] view plaincopyprint?
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();
}
這樣的實(shí)現(xiàn)是符合“線程安全”原則的:在額外的線程中完成網(wǎng)絡(luò)操作并且在UI線程中完成對ImageView的操作。
然而,隨著操作復(fù)雜性的增加,上述代碼可能會變得非常復(fù)雜導(dǎo)致維護(hù)困難。為了解決工作線程中處理此類復(fù)雜操作,你可能會考慮在工作線程中使用Handler類來處理由UI線程發(fā)送過來的消息。但可能使用AsyncTask是此類問題的最好解決方案,它很好的簡化了工作線程需要和UI組件發(fā)生交互的問題。
使用AsyncTask
AsyncTask允許你完成一些和用戶界面相關(guān)的異步工作。它在一個工作線程中完成一些阻塞工作任務(wù)然后在任務(wù)完成后通知UI線程,這些都不需要你自己來管理工作線程。
你必須從AsyncTask派生一個子類并實(shí)現(xiàn)doInBackground()回調(diào)函數(shù)來使用AsyncTask,AsyncTask將使用后臺進(jìn)程池來執(zhí)行異步任務(wù)。為了能夠更新用戶界面,你必須實(shí)現(xiàn)onPostExecute()方法,該方法將傳遞doInBackground()的返回結(jié)果,并且運(yùn)行在UI線程中。然后你可以在UI線程中調(diào)用execute() 方法來執(zhí)行該任務(wù)。
比如,使用AsyncTask來完成之前的例子:
[java] view plaincopyprint?
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
現(xiàn)在UI是安全的而且代碼變的更簡單,因?yàn)樗言诠ぷ骶€程中的工作和在UI線程的工作很好的分隔開。
你應(yīng)該參考AsyncTask的詳細(xì)文檔以便更好的理解它的工作原理,這里給出它的基本步驟:
· 你可以使用generics為Task指定參數(shù)類型,返回值類型等
· 方法doInBackground()將自動在一個工作線程中執(zhí)行。
· 方法onPreExecute(),onPostExecute()和onProgressUpdate都在UI線程中調(diào)用。
· 方法doInBackground()的返回值將傳遞給onPostExecute()方法。
· 你可以在doInBackground()中任意調(diào)用publishProgress()方法,該方法將會調(diào)用UI線程中的onProgressUpdate()方法,你可以用它來報告任務(wù)完成的進(jìn)度。
· 你可以在任意線程中任意時刻終止任務(wù)的執(zhí)行。
要注意的是,由于系統(tǒng)配置的變化(比如屏幕的方向轉(zhuǎn)動)你的工作線程可能會碰到意外的重新啟動,這種情況下,你的工作線程可能被銷毀,你可以參考Android開發(fā)包中Shelves示例來處理線程重新的問題。
編寫“線程安全”方法
在某些情況下,你編寫的方法可能會被多個線程調(diào)用,此時你實(shí)現(xiàn)方法時要保證它是“線程安全”的。
“線程安全”是可以被遠(yuǎn)程調(diào)用方法實(shí)現(xiàn)的基本規(guī)則—比如支持“綁定”的Service中的方法。當(dāng)在實(shí)現(xiàn)IBinder接口同一進(jìn)程中調(diào)用IBinder對象的方法時,該方法運(yùn)行在調(diào)用者運(yùn)行的同一線程中。然而,如果調(diào)用來自不同進(jìn)程,系統(tǒng)將使用和實(shí)現(xiàn)IBinder接口的進(jìn)程關(guān)聯(lián)的線程池中的某個線程(非該進(jìn)程中的UI線程)來執(zhí)行IBinder的方法。比如,一個Service的onBind()方法會在某個Service進(jìn)程的UI線程中調(diào)用,而由onBind()返回的對象(比如實(shí)現(xiàn)遠(yuǎn)程調(diào)用RPC方法的子類)的方法會在線程池的某個線程中執(zhí)行。由于Service可能服務(wù)于多個客戶端,那么可能有線程池中的多個線程同時執(zhí)行IBinder對象的某個方法,因此IBinder對象的方法必須保證是線程安全的。
同樣的,一個Content Provider可以接受來自其它多個進(jìn)程的數(shù)據(jù)請求。盡管ContentResolver和ContentProvider類隱藏了處理這些數(shù)據(jù)請求時進(jìn)程間通信的詳細(xì)機(jī)制,這些請求方法有query(), insert (), delete (), update () 及getType() 等。這些方法會在Content Provider的進(jìn)程的線程池的某個線程中執(zhí)行。由于這些方法同時有不定數(shù)量的線程同時調(diào)用,因此這些方法也必須是線程安全的。
進(jìn)程間通信
Android系統(tǒng)支持使用遠(yuǎn)程調(diào)用(RPC)來實(shí)現(xiàn)進(jìn)程間通信(IPC)的機(jī)制。此時在一個Activity或其它程序組件調(diào)用某個方法,而該方法的實(shí)現(xiàn)執(zhí)行是在另外的進(jìn)程中(遠(yuǎn)程進(jìn)程)。遠(yuǎn)程調(diào)用可能給調(diào)用者返回結(jié)果。這就要求將方法調(diào)用和相關(guān)數(shù)據(jù)分離到某個層次,以便能讓操作系統(tǒng)理解,能從本地進(jìn)程傳送數(shù)據(jù)到遠(yuǎn)程進(jìn)程地址空間,在遠(yuǎn)程能夠重新構(gòu)造數(shù)據(jù)以執(zhí)行方法,返回數(shù)據(jù)也能夠反向返回。Android支持能夠完成這些進(jìn)程間通信事務(wù)的所有代碼,從而使你可以只關(guān)注于定義和實(shí)現(xiàn)遠(yuǎn)程調(diào)用的接口。
為了使用進(jìn)程間通信(IPC),你的應(yīng)用需要使用bindService()綁定到某個Service。
本文版權(quán)歸黑馬程序員Android+物聯(lián)網(wǎng)培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處。謝謝!
作者:黑馬程序員Android+物聯(lián)網(wǎng)培訓(xùn)學(xué)院
首發(fā):http://android.itheima.com