進(jìn)程和線程 來源官方文檔?
當(dāng)某個(gè)應(yīng)用組件啟動(dòng)且該應(yīng)用沒有運(yùn)行其他任何組件時(shí),Android 系統(tǒng)會(huì)使用單個(gè)執(zhí)行線程為應(yīng)用啟動(dòng)新的 Linux 進(jìn)程。默認(rèn)情況下于置,同一應(yīng)用的所有組件在相同的進(jìn)程和線程(稱為“主”線程)中運(yùn)行靴拱。 如果某個(gè)應(yīng)用組件啟動(dòng)且該應(yīng)用已存在進(jìn)程(因?yàn)榇嬖谠搼?yīng)用的其他組件),則該組件會(huì)在此進(jìn)程內(nèi)啟動(dòng)并使用相同的執(zhí)行線程吧恃。 但是,您可以安排應(yīng)用中的其他組件在單獨(dú)的進(jìn)程中運(yùn)行麻诀,并為任何進(jìn)程創(chuàng)建額外的線程痕寓。
本文檔介紹進(jìn)程和線程在 Android 應(yīng)用中的工作方式。
進(jìn)程
默認(rèn)情況下蝇闭,同一應(yīng)用的所有組件均在相同的進(jìn)程中運(yùn)行呻率,且大多數(shù)應(yīng)用都不會(huì)改變這一點(diǎn)。 但是呻引,如果您發(fā)現(xiàn)需要控制某個(gè)組件所屬的進(jìn)程礼仗,則可在清單文件中執(zhí)行此操作。
各類組件元素的清單文件條目—<activity>、<service>藐守、<receiver>和<provider>—均支持android:process屬性挪丢,此屬性可以指定該組件應(yīng)在哪個(gè)進(jìn)程運(yùn)行。您可以設(shè)置此屬性卢厂,使每個(gè)組件均在各自的進(jìn)程中運(yùn)行乾蓬,或者使一些組件共享一個(gè)進(jìn)程,而其他組件則不共享慎恒。 此外任内,您還可以設(shè)置android:process,使不同應(yīng)用的組件在相同的進(jìn)程中運(yùn)行融柬,但前提是這些應(yīng)用共享相同的 Linux 用戶 ID 并使用相同的證書進(jìn)行簽署死嗦。
此外,<application>元素還支持android:process屬性粒氧,以設(shè)置適用于所有組件的默認(rèn)值越除。
如果內(nèi)存不足,而其他為用戶提供更緊急服務(wù)的進(jìn)程又需要內(nèi)存時(shí)外盯,Android 可能會(huì)決定在某一時(shí)刻關(guān)閉某一進(jìn)程摘盆。在被終止進(jìn)程中運(yùn)行的應(yīng)用組件也會(huì)隨之銷毀。 當(dāng)這些組件需要再次運(yùn)行時(shí)饱苟,系統(tǒng)將為它們重啟進(jìn)程孩擂。
決定終止哪個(gè)進(jìn)程時(shí),Android 系統(tǒng)將權(quán)衡它們對(duì)用戶的相對(duì)重要程度箱熬。例如类垦,相對(duì)于托管可見 Activity 的進(jìn)程而言,它更有可能關(guān)閉托管屏幕上不再可見的 Activity 進(jìn)程城须。 因此蚤认,是否終止某個(gè)進(jìn)程的決定取決于該進(jìn)程中所運(yùn)行組件的狀態(tài)。 下面糕伐,我們介紹決定終止進(jìn)程所用的規(guī)則砰琢。
進(jìn)程生命周期
Android 系統(tǒng)將盡量長時(shí)間地保持應(yīng)用進(jìn)程,但為了新建進(jìn)程或運(yùn)行更重要的進(jìn)程赤炒,最終需要清除舊進(jìn)程來回收內(nèi)存氯析。 為了確定保留或終止哪些進(jìn)程亏较,系統(tǒng)會(huì)根據(jù)進(jìn)程中正在運(yùn)行的組件以及這些組件的狀態(tài)莺褒,將每個(gè)進(jìn)程放入“重要性層次結(jié)構(gòu)”中。 必要時(shí)雪情,系統(tǒng)會(huì)首先消除重要性最低的進(jìn)程遵岩,然后是重要性略遜的進(jìn)程,依此類推,以回收系統(tǒng)資源尘执。
重要性層次結(jié)構(gòu)一共有 5 級(jí)舍哄。以下列表按照重要程度列出了各類進(jìn)程(第一個(gè)進(jìn)程最重要,將是最后一個(gè)被終止的進(jìn)程):
前臺(tái)進(jìn)程
用戶當(dāng)前操作所必需的進(jìn)程誊锭。如果一個(gè)進(jìn)程滿足以下任一條件表悬,即視為前臺(tái)進(jìn)程:
????? 1.托管用戶正在交互的Activity(已調(diào)用Activity的onResume()方法)
????? 2.托管某個(gè)Service,后者綁定到用戶正在交互的 Activity
????? 3.托管正在“前臺(tái)”運(yùn)行的Service(服務(wù)已調(diào)用startForeground())
????? 4.托管正執(zhí)行一個(gè)生命周期回調(diào)的Service(onCreate()丧靡、onStart()或onDestroy())
????? 5.托管正執(zhí)行其onReceive()方法的BroadcastReceiver
通常蟆沫,在任意給定時(shí)間前臺(tái)進(jìn)程都為數(shù)不多。只有在內(nèi)在不足以支持它們同時(shí)繼續(xù)運(yùn)行這一萬不得已的情況下温治,系統(tǒng)才會(huì)終止它們饭庞。 此時(shí),設(shè)備往往已達(dá)到內(nèi)存分頁狀態(tài)熬荆,因此需要終止一些前臺(tái)進(jìn)程來確保用戶界面正常響應(yīng)。
可見進(jìn)程
沒有任何前臺(tái)組件、但仍會(huì)影響用戶在屏幕上所見內(nèi)容的進(jìn)程兽叮。 如果一個(gè)進(jìn)程滿足以下任一條件洞焙,即視為可見進(jìn)程:
托管不在前臺(tái)、但仍對(duì)用戶可見的Activity(已調(diào)用其onPause()方法)纬黎。例如幅骄,如果前臺(tái) Activity 啟動(dòng)了一個(gè)對(duì)話框,允許在其后顯示上一 Activity本今,則有可能會(huì)發(fā)生這種情況
托管綁定到可見(或前臺(tái))Activity 的Service
可見進(jìn)程被視為是極其重要的進(jìn)程拆座,除非為了維持所有前臺(tái)進(jìn)程同時(shí)運(yùn)行而必須終止,否則系統(tǒng)不會(huì)終止這些進(jìn)程冠息。
服務(wù)進(jìn)程
正在運(yùn)行已使用startService()方法啟動(dòng)的服務(wù)且不屬于上述兩個(gè)更高類別進(jìn)程的進(jìn)程挪凑。盡管服務(wù)進(jìn)程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),但是它們通常在執(zhí)行一些用戶關(guān)心的操作(例如逛艰,在后臺(tái)播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))躏碳。因此,除非內(nèi)存不足以維持所有前臺(tái)進(jìn)程和可見進(jìn)程同時(shí)運(yùn)行散怖,否則系統(tǒng)會(huì)讓服務(wù)進(jìn)程保持運(yùn)行狀態(tài)菇绵。
后臺(tái)進(jìn)程
包含目前對(duì)用戶不可見的 Activity 的進(jìn)程(已調(diào)用 Activity 的onStop()方法)。這些進(jìn)程對(duì)用戶體驗(yàn)沒有直接影響镇眷,系統(tǒng)可能隨時(shí)終止它們咬最,以回收內(nèi)存供前臺(tái)進(jìn)程、可見進(jìn)程或服務(wù)進(jìn)程使用欠动。 通常會(huì)有很多后臺(tái)進(jìn)程在運(yùn)行永乌,因此它們會(huì)保存在 LRU (最近最少使用)列表中惑申,以確保包含用戶最近查看的 Activity 的進(jìn)程最后一個(gè)被終止。如果某個(gè) Activity 正確實(shí)現(xiàn)了生命周期方法翅雏,并保存了其當(dāng)前狀態(tài)圈驼,則終止其進(jìn)程不會(huì)對(duì)用戶體驗(yàn)產(chǎn)生明顯影響,因?yàn)楫?dāng)用戶導(dǎo)航回該 Activity 時(shí)望几,Activity 會(huì)恢復(fù)其所有可見狀態(tài)绩脆。 有關(guān)保存和恢復(fù)狀態(tài)的信息,請(qǐng)參閱Activity文檔橄抹。
空進(jìn)程
不含任何活動(dòng)應(yīng)用組件的進(jìn)程衙伶。保留這種進(jìn)程的的唯一目的是用作緩存,以縮短下次在其中運(yùn)行組件所需的啟動(dòng)時(shí)間害碾。 為使總體系統(tǒng)資源在進(jìn)程緩存和底層內(nèi)核緩存之間保持平衡矢劲,系統(tǒng)往往會(huì)終止這些進(jìn)程慌随。
根據(jù)進(jìn)程中當(dāng)前活動(dòng)組件的重要程度芬沉,Android 會(huì)將進(jìn)程評(píng)定為它可能達(dá)到的最高級(jí)別。例如阁猜,如果某進(jìn)程托管著服務(wù)和可見 Activity丸逸,則會(huì)將此進(jìn)程評(píng)定為可見進(jìn)程,而不是服務(wù)進(jìn)程剃袍。
此外黄刚,一個(gè)進(jìn)程的級(jí)別可能會(huì)因其他進(jìn)程對(duì)它的依賴而有所提高,即服務(wù)于另一進(jìn)程的進(jìn)程其級(jí)別永遠(yuǎn)不會(huì)低于其所服務(wù)的進(jìn)程民效。 例如憔维,如果進(jìn)程 A 中的內(nèi)容提供程序?yàn)檫M(jìn)程 B 中的客戶端提供服務(wù),或者如果進(jìn)程 A 中的服務(wù)綁定到進(jìn)程 B 中的組件畏邢,則進(jìn)程 A 始終被視為至少與進(jìn)程 B 同樣重要业扒。
由于運(yùn)行服務(wù)的進(jìn)程其級(jí)別高于托管后臺(tái) Activity 的進(jìn)程,因此啟動(dòng)長時(shí)間運(yùn)行操作的 Activity 最好為該操作啟動(dòng)服務(wù)舒萎,而不是簡單地創(chuàng)建工作線程程储,當(dāng)操作有可能比 Activity 更加持久時(shí)尤要如此。例如臂寝,正在將圖片上傳到網(wǎng)站的 Activity 應(yīng)該啟動(dòng)服務(wù)來執(zhí)行上傳章鲤,這樣一來,即使用戶退出 Activity咆贬,仍可在后臺(tái)繼續(xù)執(zhí)行上傳操作败徊。使用服務(wù)可以保證,無論 Activity 發(fā)生什么情況素征,該操作至少具備“服務(wù)進(jìn)程”優(yōu)先級(jí)集嵌。 同理,廣播接收器也應(yīng)使用服務(wù)御毅,而不是簡單地將耗時(shí)冗長的操作放入線程中根欧。
線程
應(yīng)用啟動(dòng)時(shí),系統(tǒng)會(huì)為應(yīng)用創(chuàng)建一個(gè)名為“主線程”的執(zhí)行線程端蛆。 此線程非常重要凤粗,因?yàn)樗?fù)責(zé)將事件分派給相應(yīng)的用戶界面小工具,其中包括繪圖事件今豆。 此外嫌拣,它也是應(yīng)用與 Android UI 工具包組件(來自android.widget和android.view軟件包的組件)進(jìn)行交互的線程。因此呆躲,主線程有時(shí)也稱為 UI 線程异逐。
系統(tǒng)絕對(duì)不會(huì)為每個(gè)組件實(shí)例創(chuàng)建單獨(dú)的線程。運(yùn)行于同一進(jìn)程的所有組件均在 UI 線程中實(shí)例化插掂,并且對(duì)每個(gè)組件的系統(tǒng)調(diào)用均由該線程進(jìn)行分派灰瞻。因此,響應(yīng)系統(tǒng)回調(diào)的方法(例如辅甥,報(bào)告用戶操作的onKeyDown()或生命周期回調(diào)方法)始終在進(jìn)程的 UI 線程中運(yùn)行酝润。
例如,當(dāng)用戶觸摸屏幕上的按鈕時(shí)璃弄,應(yīng)用的 UI 線程會(huì)將觸摸事件分派給小工具要销,而小工具反過來又設(shè)置其按下狀態(tài),并將無效請(qǐng)求發(fā)布到事件隊(duì)列中夏块。UI 線程從隊(duì)列中取消該請(qǐng)求并通知小工具應(yīng)該重繪自身疏咐。
在應(yīng)用執(zhí)行繁重的任務(wù)以響應(yīng)用戶交互時(shí),除非正確實(shí)施應(yīng)用脐供,否則這種單線程模式可能會(huì)導(dǎo)致性能低下凳鬓。 特別地,如果 UI 線程需要處理所有任務(wù)患民,則執(zhí)行耗時(shí)很長的操作(例如缩举,網(wǎng)絡(luò)訪問或數(shù)據(jù)庫查詢)將會(huì)阻塞整個(gè) UI。一旦線程被阻塞匹颤,將無法分派任何事件仅孩,包括繪圖事件。從用戶的角度來看印蓖,應(yīng)用顯示為掛起辽慕。 更糟糕的是,如果 UI 線程被阻塞超過幾秒鐘時(shí)間(目前大約是 5 秒鐘)赦肃,用戶就會(huì)看到一個(gè)讓人厭煩的“應(yīng)用無響應(yīng)”(ANR) 對(duì)話框溅蛉。如果引起用戶不滿公浪,他們可能就會(huì)決定退出并卸載此應(yīng)用。
此外船侧,Android UI 工具包并非線程安全工具包欠气。因此,您不得通過工作線程操縱 UI镜撩,而只能通過 UI 線程操縱用戶界面预柒。因此,Android 的單線程模式必須遵守兩條規(guī)則:
不要阻塞 UI 線程
不要在 UI 線程之外訪問 Android UI 工具包
工作線程
根據(jù)上述單線程模式袁梗,要保證應(yīng)用 UI 的響應(yīng)能力宜鸯,關(guān)鍵是不能阻塞 UI 線程。如果執(zhí)行的操作不能很快完成遮怜,則應(yīng)確保它們?cè)趩为?dú)的線程(“后臺(tái)”或“工作”線程)中運(yùn)行淋袖。
例如,以下代碼演示了一個(gè)點(diǎn)擊偵聽器從單獨(dú)的線程下載圖像并將其顯示在ImageView中:
public? void? onClick(View? v){
new? Thread(new? Runnable(){
public? void? run(){
Bitmap? b=loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}`
乍看起來锯梁,這段代碼似乎運(yùn)行良好适贸,因?yàn)樗鼊?chuàng)建了一個(gè)新線程來處理網(wǎng)絡(luò)操作。 但是涝桅,它違反了單線程模式的第二條規(guī)則:不要在 UI 線程之外訪問 Android UI 工具包—此示例從工作線程(而不是 UI 線程)修改了ImageView拜姿。這可能導(dǎo)致出現(xiàn)不明確、不可預(yù)見的行為冯遂,但要跟蹤此行為困難而又費(fèi)時(shí)蕊肥。
為解決此問題,Android 提供了幾種途徑來從其他線程訪問 UI 線程蛤肌。以下列出了幾種有用的方法:
Activity.runOnUiThread(Runnable)
View.postDelayed(Runnable, long)
例如壁却,您可以通過使用View.post(Runnable)方法修復(fù)上述代碼:
public?? void?? onClick(Viewv){
new?? Thread(new?? Runnable(){
public? void?? run(){
final?? Bitmapbitmap=loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new?? Runnable(){
public?? void? run(){
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
現(xiàn)在,上述實(shí)現(xiàn)屬于線程安全型:在單獨(dú)的線程中完成網(wǎng)絡(luò)操作裸准,而在 UI 線程中操縱ImageView展东。
但是,隨著操作日趨復(fù)雜炒俱,這類代碼也會(huì)變得復(fù)雜且難以維護(hù)盐肃。 要通過工作線程處理更復(fù)雜的交互,可以考慮在工作線程中使用Handler處理來自 UI 線程的消息权悟。當(dāng)然砸王,最好的解決方案或許是擴(kuò)展AsyncTask類,此類簡化了與 UI 進(jìn)行交互所需執(zhí)行的工作線程任務(wù)峦阁。
使用 AsyncTask
AsyncTask允許對(duì)用戶界面執(zhí)行異步操作谦铃。它會(huì)先阻塞工作線程中的操作,然后在 UI 線程中發(fā)布結(jié)果榔昔,而無需您親自處理線程和/或處理程序驹闰。
要使用它瘪菌,必須創(chuàng)建AsyncTask子類并實(shí)現(xiàn)doInBackground()回調(diào)方法,該方法將在后臺(tái)線程池中運(yùn)行嘹朗。要更新 UI师妙,必須實(shí)現(xiàn)onPostExecute()以傳遞doInBackground()返回的結(jié)果并在 UI 線程中運(yùn)行,這樣骡显,您即可安全更新 UI。稍后曾掂,您可以通過從 UI 線程調(diào)用execute()來運(yùn)行任務(wù)惫谤。
例如,您可以通過以下方式使用AsyncTask來實(shí)現(xiàn)上述示例:
public?? void?? onClick(View? v){
new? DownloadImageTask().execute("http://example.com/image.png");
}
private?? class?? DownloadImageTask?? extends?? AsyncTask{
/** 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)槿蝿?wù)分解成了兩部分:一部分應(yīng)在工作線程內(nèi)完成,另一部分應(yīng)在 UI 線程內(nèi)完成许蓖。
下面簡要概述了 AsyncTask 的工作方法蝴猪,但要全面了解如何使用此類,您應(yīng)閱讀AsyncTask參考文檔:
可以使用泛型指定參數(shù)類型膊爪、進(jìn)度值和任務(wù)最終值
方法doInBackground()會(huì)在工作線程上自動(dòng)執(zhí)行
onPreExecute()自阱、onPostExecute()和onProgressUpdate()均在 UI 線程中調(diào)用
doInBackground()返回的值將發(fā)送到onPostExecute()
您可以隨時(shí)在doInBackground()中調(diào)用publishProgress(),以在 UI 線程中執(zhí)行onProgressUpdate()
您可以隨時(shí)取消任何線程中的任務(wù)
注意:使用工作線程時(shí)可能會(huì)遇到另一個(gè)問題米酬,即:運(yùn)行時(shí)配置變更(例如沛豌,用戶更改了屏幕方向)導(dǎo)致 Activity 意外重啟,這可能會(huì)銷毀工作線程赃额。 要了解如何在這種重啟情況下堅(jiān)持執(zhí)行任務(wù)加派,以及如何在 Activity 被銷毀時(shí)正確地取消任務(wù),請(qǐng)參閱書架示例應(yīng)用的源代碼跳芳。
線程安全方法
在某些情況下芍锦,您實(shí)現(xiàn)的方法可能會(huì)從多個(gè)線程調(diào)用,因此編寫這些方法時(shí)必須確保其滿足線程安全的要求飞盆。
這一點(diǎn)主要適用于可以遠(yuǎn)程調(diào)用的方法娄琉,如綁定服務(wù)中的方法。如果對(duì)IBinder中所實(shí)現(xiàn)方法的調(diào)用源自運(yùn)行IBinder的同一進(jìn)程吓歇,則該方法在調(diào)用方的線程中執(zhí)行车胡。但是,如果調(diào)用源自其他進(jìn)程照瘾,則該方法將在從線程池選擇的某個(gè)線程中執(zhí)行(而不是在進(jìn)程的 UI 線程中執(zhí)行)匈棘,線程池由系統(tǒng)在與IBinder相同的進(jìn)程中維護(hù)。例如析命,即使服務(wù)的onBind()方法將從服務(wù)進(jìn)程的 UI 線程調(diào)用主卫,在onBind()返回的對(duì)象中實(shí)現(xiàn)的方法(例如逃默,實(shí)現(xiàn) RPC 方法的子類)仍會(huì)從線程池中的線程調(diào)用。由于一個(gè)服務(wù)可以有多個(gè)客戶端簇搅,因此可能會(huì)有多個(gè)池線程在同一時(shí)間使用同一IBinder方法完域。因此,IBinder方法必須實(shí)現(xiàn)為線程安全方法瘩将。
同樣吟税,內(nèi)容提供程序也可接收來自其他進(jìn)程的數(shù)據(jù)請(qǐng)求。盡管ContentResolver和ContentProvider類隱藏了如何管理進(jìn)程間通信的細(xì)節(jié)姿现,但響應(yīng)這些請(qǐng)求的ContentProvider方法(query()肠仪、insert()、delete()备典、update()和getType()方法)將從內(nèi)容提供程序所在進(jìn)程的線程池中調(diào)用异旧,而不是從進(jìn)程的 UI 線程調(diào)用。由于這些方法可能會(huì)同時(shí)從任意數(shù)量的線程調(diào)用提佣,因此它們也必須實(shí)現(xiàn)為線程安全方法吮蛹。
進(jìn)程間通信
Android 利用遠(yuǎn)程過程調(diào)用 (RPC) 提供了一種進(jìn)程間通信 (IPC) 機(jī)制,通過這種機(jī)制拌屏,由 Activity 或其他應(yīng)用組件調(diào)用的方法將(在其他進(jìn)程中)遠(yuǎn)程執(zhí)行潮针,而所有結(jié)果將返回給調(diào)用方。這就要求把方法調(diào)用及其數(shù)據(jù)分解至操作系統(tǒng)可以識(shí)別的程度倚喂,并將其從本地進(jìn)程和地址空間傳輸至遠(yuǎn)程進(jìn)程和地址空間然低,然后在遠(yuǎn)程進(jìn)程中重新組裝并執(zhí)行該調(diào)用。 然后务唐,返回值將沿相反方向傳輸回來雳攘。 Android 提供了執(zhí)行這些 IPC 事務(wù)所需的全部代碼,因此您只需集中精力定義和實(shí)現(xiàn) RPC 編程接口即可枫笛。
要執(zhí)行 IPC吨灭,必須使用bindService()將應(yīng)用綁定到服務(wù)上。如需了解詳細(xì)信息刑巧,請(qǐng)參閱服務(wù)開發(fā)者指南喧兄。