Android 進程和線程
默認情況下,Android 系統中同一應用的所有組件均運行在相同的進程和線程(稱為主線程)中,新啟動的應用組件會創建進程或者在已存在的進程中啟動並使用相同的執行線程。 但是,也可以安排應用中的組件在單獨的進程中運行,並為任何進程創建額外的線程
##一、進程
如果需要控制某個組件所屬的進程,則可在清單文件中執行以下操作
各類組件標籤: activity 、 service 、 receiver 和 provider 等均支持 android:process 屬性,此屬性可以指定該組件應在哪個進程運行,通過此屬性可以使每個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其他組件則不共享。 此外,還可以通過該屬性使不同應用的組件在相同的進程中運行,但前提是這些應用共享相同的 Linux 用戶 ID 並使用相同的證書進行簽署
此外, application 元素也支持 android:process 屬性,以設置適用於所有組件的默認值
如果內存不足而系統又需要內存時,系統可能會在某一時刻關閉某一進程,Android 系統將權衡所有進程對用戶的相對重要程度來決定終止哪個進程,被終止的進程中運行的應用組件也會隨之銷毀, 當這些組件需要再次運行時,系統將為它們重啟進程
##二、進程的重要性
Android 系統會盡量長時間地保持應用進程,但為了新建進程或運行更重要的進程,有時候就需要移除舊進程以回收內存。 為了確定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入「重要性層次結構」中。 必要時,系統會首先消除重要性最低的進程,然後是重要性略遜的進程,依此類推
重要性層次結構一共分為五級,第一級最為重要
前台進程
即用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即視為前台進程:
託管用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法)
託管某個 Service,該Service綁定到用戶正在交互的 Activity
託管正在前台運行的 Service(服務已調用 startForeground())
託管正在執行生命周期回調的 Service(onCreate()、onStart() 或 onDestroy())
託管正執行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前台進程都為數不多,只有在內存不足以支持它們同時繼續運行這一情況下系統才會終止它們。 此時,設備往往已達到內存分頁狀態,因此需要終止一些前台進程來確保用戶界面正常響應
可見進程
沒有任何前台組件,但仍會影響用戶在屏幕上所見內容的進程。 如果一個進程滿足以下任一條件,即視為可見進程:
託管不在前台、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,如果前台 Activity 啟動了一個覆蓋一部分屏幕的對話框時,就會出現這種情況
託管綁定到可見或前台Activity 的 Service
可見進程被視為是極其重要的進程,除非為了維持所有前台進程同時運行而必須終止,否則系統不會終止這些進程
服務進程
正在運行已使用 startService() 方法啟動的服務且不屬於上述兩個更高類別的進程。儘管服務進程與用戶所見內容沒有直接關聯,但是它們通常在執行一些用戶關心的操作(例如,在後台播放音樂或從網路下載數據)。因此,除非內存不足以維持所有前台進程和可見進程同時運行,否則系統會讓服務進程保持運行狀態
後台進程
包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。這些進程對用戶體驗沒有直接影響,系統可能會隨時終止它們以回收內存供前台進程、可見進程或服務進程使用。 通常會有很多後台進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。如果某個 Activity 正確實現了生命周期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,因為當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態
空進程
不含任何活動應用組件的進程。保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間。 為使總體系統資源在進程緩存和底層內核緩存之間保持平衡,系統往往會終止這些進程
根據進程中當前活動組件的重要程度,Android 會將進程評定為它可以達到的最高級別。例如,如果某進程託管著服務和可見 Activity,則會將此進程評定為可見進程。此外,一個進程的級別可能會因其他進程對它的依賴而有所提高,即服務於另一進程的進程其級別永遠不會低於其所服務的進程。 例如,如果進程 A 中的內容提供程序為進程 B 中的客戶端提供服務,或者如果進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視為至少與進程 B 同樣重要。
由於運行Service的進程其級別高於託管後台 Activity 的進程,因此啟動長時間運行操作的 Activity 最好為該操作啟動Service,而不是簡單地創建工作線程,當操作有可能比 Activity 更加持久時尤要如此。例如,正在將圖片上傳到網站的 Activity 應該啟動Service來執行上傳操作,這樣一來,即使用戶退出 Activity,仍可在後台繼續執行上傳操作。使用Service可以保證,無論 Activity 發生什麼情況,該操作至少具備「服務進程」優先順序。 同理,廣播接收器也應使用Service,而不是簡單地將耗時冗長的操作放入線程中
##三、線程
應用啟動時,系統會為應用創建一個名為「主線程」的執行線程。 此線程非常重要,因為它負責將事件分派給相應的用戶界面小部件,其中包括繪圖事件。此外,它也是應用與 Android UI 工具包組件(來自 android.widget 和 android.view 軟體包的組件)進行交互的線程。因此,主線程也稱為 UI 線程
系統不會為每個組件實例創建單獨的線程。運行於同一進程的所有組件均在 UI 線程中實例化,並且對每個組件的系統調用均由該線程進行分派。 因此,響應系統回調的方法(比如報告用戶操作的 onKeyDown() 或生命周期回調方法)始終在進程的 UI 線程中運行
例如,當用戶觸摸屏幕上的按鈕時,應用的 UI 線程會將觸摸事件分派給小部件,而小部件反過來又設置其按下狀態,並將失效請求發布到事件隊列中。 UI 線程從隊列中取消該請求並通知小部件應該重繪自身
如果 UI 線程需要處理所有任務,則執行耗時很長的操作將會阻塞整個 UI。 一旦線程被阻塞,將無法分派任何事件,包括繪圖事件。如果 UI 線程被阻塞超過特定時間(目前大約是 5 秒鐘),用戶就會看到一個顯示「應用無響應」(ANR) 文本的對話框
此外,Android UI 工具包並非線程安全工具包。因此不能通過工作線程操縱 UI,而只能通過 UI 線程操縱用戶界面。 因此,Android 的單線程模式必須遵守兩條規則:
不要阻塞 UI 線程
不要在 UI 線程之外訪問 Android UI 工具包
##四、工作線程
根據上述單線程模式,要保證應用 UI 的響應能力,關鍵是不能阻塞 UI 線程。如果執行的操作不能很快完成,則應確保它們在單獨的線程中運行
例如,以下代碼演示了一個點擊偵聽器從單獨的線程下載圖像並將其顯示在 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();
}
可以看出它創建了一個新線程來處理網路操作,但是它違反了單線程模式的第二條規則:不要在 UI 線程之外訪問 Android UI 工具包。在工作線程中修改了 ImageView, 這將使應用運行時出現錯誤
為解決此問題,Android 提供了幾種途徑來從其他線程訪問 UI 線程:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
可以將以上代碼修改為
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();
}
現在,上述實現屬於線程安全型:在單獨的線程中完成網路操作,而在 UI 線程中操縱 ImageView
此外,也可以使用AsyncTask來完成任務。AsyncTask 允許對用戶界面執行非同步操作。它會先阻塞工作線程中的操作,然後在 UI 線程中發布結果
要使用AsyncTask ,必須創建 AsyncTask 的子類並實現 doInBackground() 回調方法,該方法將在後台線程池中運行。 要更新 UI,應該實現 onPostExecute() 方法以傳遞 doInBackground() 返回的結果並在 UI 線程中運行,以便安全地更新 UI
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);
}
}
AsyncTask 工作方法如下所示:
使用泛型指定參數類型、進度值和任務最終值
doInBackground() 方法會在工作線程上自動執行
onPreExecute()、onPostExecute() 和 onProgressUpdate() 均在 UI 線程中調用
doInBackground() 返回的值將發送到 onPostExecute()
可以隨時在 doInBackground() 中調用publishProgress()方法,以便在 UI 線程中執行 onProgressUpdate()
可以隨時取消任何線程中的任務


※MQ 消息中間件的使用和重構系統之間的耦合
※面試官:「談談Spring中都用到了那些設計模式?」
TAG:千鋒JAVA開發學院 |