前言
按照操作系統(tǒng)中的描述。線程是 CPU 調(diào)度的最小單元,同時線程也是一種有限的資源。而進程一般指一個執(zhí)行單元,在 PC 和移動設(shè)備上指一個程序或者一個應(yīng)用。一個進程可以包含多個線程。對于 Android 來說,它是一種基于 Linux 內(nèi)核的移動操作系統(tǒng),它的進程和線程有著其特有的性質(zhì)。我們這篇文章就來聊聊關(guān)于 Android 中的進程和線程,我們需要了解的知識。
進程
當一個程序第一次啟動的時候,Android 會啟動一個 Linux 進程和一個主線程。默認情況下,同一應(yīng)用的所有組件均在相同的進程中運行,且大多數(shù)應(yīng)用都不會改變這一點。如果我們發(fā)現(xiàn)需要控制某個組件所屬的進程,則可在清單文件中執(zhí)行此操作。
組件運行在哪個進程中,是在 AndroidManifest 文件中進行設(shè)置的,<activity>、<service>、<receiver> 和 <provider> 均支持 android:process 屬性,此屬性可以指定該組件應(yīng)在哪個進程運行。我們可以設(shè)置此屬性,使每個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其他組件則不共享。此外,<application> 元素還支持 android:process 屬性,用來設(shè)置適用于所有組件的默認值。
進程的優(yōu)先級
Android 系統(tǒng)將盡量長時間地保持應(yīng)用進程,但為了新建進程或運行更重要的進程,最終需要移除舊進程來回收內(nèi)存。為了確定保留或終止哪些進程,系統(tǒng)會根據(jù)進程中正在運行的組件以及這些組件的狀態(tài),將每個進程放入 “重要性層次結(jié)構(gòu)” 中。必要時,系統(tǒng)會首先消除重要性最低的進程,然后是重要性相對較高的進程,以此類推,以回收進程。
重要性層次結(jié)構(gòu)一共有 5 級
1、前臺進程 — Foreground process
用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即是為前臺進程:
- 托管用戶正在交互的 Activity(已調(diào)用 Activity 的 onResume() 方法)
- 托管某個 Service,后者綁定到用戶正在交互的 Activity
- 托管正在 “前臺” 運行的 Service(服務(wù)已調(diào)用 startForeground())
- 托管正執(zhí)行一個生命周期回調(diào)的 Service(onCreate()、onStart() 或 onDestroy())
- 托管正執(zhí)行其 onReceive() 方法的 BroadcastRecevier
通常,在任意給定時間前臺進程都為數(shù)不多。只有在內(nèi)存不足以支撐他們同時運行這一萬不得已的情況下,系統(tǒng)才會終止它們。此時,設(shè)備往往已達到內(nèi)存分頁狀態(tài),因此需要終止一些前臺進程來確保用戶界面正常響應(yīng)。
2、可見進程 — Visible process
??沒有任何前臺組件、但仍會影響用戶在屏幕上所見內(nèi)存的進程
- 托管不在前臺、但仍對用戶可見的 Activity(已調(diào)用其 onPause() 方法)
- 托管綁定到可見(或前臺)Activity 的 Service
可見進程被視為極其重要的進程,除非為了維持所有前臺進程同時運行而必須終止,否則系統(tǒng)不會終止這些進程。
3、服務(wù)進程 — Service process
正在運行已使用 startService() 方法啟動的服務(wù)且不屬于和上述兩個更高類別進程的進程。盡管服務(wù)進程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),但它們通常在「執(zhí)行一些用戶關(guān)心的操作」(例如,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此,除非內(nèi)存不足以維護所有前臺進程和可見進程同時運行,否則會讓服務(wù)進程保持運行狀態(tài)。
4、后臺進程 — Background process
包含目前對用戶不可見的 Activity 的進程(已調(diào)用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統(tǒng)可能隨時終止它們,以回收內(nèi)存供前臺進程、可見進程或服務(wù)進程使用。
5、空進程 — Empty process
不含任意活動應(yīng)用組件的進程。保留這種進程的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間。為使總體系統(tǒng)資源在進程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會終止這些進程。
比較常見的使用場景
由于運行服務(wù)的進程級別高于托管后臺 Activity 的進程,因此啟動長時間運行操作的 Activity 最好為此操作啟動服務(wù),而不是簡單地創(chuàng)建工作線程,當操作有可能比 Activity 更加持久時尤要如此。
例如,正在將圖片上傳到網(wǎng)站的 Activity 應(yīng)該啟動服務(wù)來執(zhí)行上傳,這樣一來,即使用戶退出 Activity,仍可在后臺繼續(xù)執(zhí)行上傳操作。使用服務(wù)可以保證,無論 Activity 發(fā)生什么情況,該操作至少具備 “服務(wù)進程” 優(yōu)先級。同理,廣播接收器也應(yīng)使用服務(wù),而不是簡單地將耗時冗長的操作放入線程中。
線程
線程在 Android 中是一個很重要的概念,從用途上來說,線程分為主線程和子線程,主線程的作用是「運行四大組件以及處理它們和用戶的交互」,而子線程的作用則是「執(zhí)行耗時任務(wù),比如網(wǎng)絡(luò)請求、I/O 操作等」,由于 Android 的特性,如果在主線程中執(zhí)行耗時操作那么就會導(dǎo)致程序無法及時地響應(yīng)。因此耗時操作必須放在子線程中執(zhí)行。
Android 中的線程形態(tài)
除了 Thread 本身以外,在 Android 中可以扮演線程角色的還有很多,比如 AsyncTask 和 IntentService,同時 HandlerThread 也是一種特殊的線程。盡管 AsyncTask、IntentService 以及 HandlerThread 的「表現(xiàn)形式」都有別于傳統(tǒng)的線程,但是它們的本質(zhì)仍然是傳統(tǒng)的線程。對于 AsyncTask 來說,它的底層用到了線程池,對于 IntentService 和 HandlerThread 來說,他們的底層則直接使用了線程。
不同形式的線程雖然都是線程,但是它們?nèi)匀挥胁煌奶匦院褪褂脠鼍啊?strong>AsyncTask 封裝了線程池和 Handler,它主要是為了方便開發(fā)者在子線程中更新 UI。HandlerThread 是一種具有消息循環(huán)的線程,在它的內(nèi)部可以使用 Handler。IntentService 內(nèi)部采用 HandlerThread 來執(zhí)行任務(wù),當任務(wù)執(zhí)行完畢后 IntentService 會自動退出。
從任務(wù)執(zhí)行的角度來看,IntentService 的作用很像一個后臺線程,但是 IntentService 是一種服務(wù),它不容易被系統(tǒng)殺死從而可以盡量保證任務(wù)的執(zhí)行,而如果是一個后臺線程的話,由于這個時候進程中沒有活動的四大組件,那么這個進程的優(yōu)先級就會非常低,會很容易被系統(tǒng)殺死,這就是 IntentService 的優(yōu)點。
主線程的一些事
從 Android 3.0 開始,系統(tǒng)要求網(wǎng)絡(luò)訪問必須在子線程中進行,否則網(wǎng)絡(luò)訪問將會失敗并拋出 NetworkOnMainThreadException 這個異常,這樣做是為了避免主線程由于被耗時操作阻塞從而出現(xiàn) ANR 現(xiàn)象。
而 Android 規(guī)定訪問 UI 只能在主線程中進行,如果在子線程中訪問 UI,那么程序就回拋出異常。ViewRootImpl 對 UI 操作做了驗證,這個驗證工作是由 ViewRootImpl 的 checkThread() 方法來完成的。
void checkThread(){
if(mThread != Thread.currentThread()){
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarch can
ouch its views.");
}
}
Android 系統(tǒng)為什么不允許在子線程中訪問 UI 呢?這是因為 Android 的 UI 控件不是線程安全的,如果在多線程中并發(fā)訪問可能會導(dǎo)致 UI 控件處于不可預(yù)期的狀態(tài),那為什么系統(tǒng)不對 UI 控件的訪問加上鎖機制呢?
缺點有兩個
加上鎖機制會讓 UI 訪問的邏輯變得復(fù)雜
鎖機制會降低 UI 訪問的效率
鑒于這兩個缺點,最簡單且高效的方法就是采用單線程模型來處理 UI 操作,對于開發(fā)者來說也不是很麻煩,只是需要通過 Handler 切換一下 UI 的訪問執(zhí)行線程即可。
參考資料
- 《Android 開發(fā)藝術(shù)探索》
- ??進程和線程