android進程、線程的優先級

有一個面試問題:IntentService的優先級比單純的線程高,它不容易被系統殺死,可以執行高優先級的後臺任務。爲什麼?它的背後有那些邏輯呢?

IntentService的具體內容可以參照我的另外一篇文章:IntentService的源碼分析,IntentService也是Service,後續就把IntentService改爲Service了,更有代表性。表面看是涉及到兩個知識點的問題,一是android進程優先級的問題,與gc有關,二是線程的優先級問題。其實弄清楚了android進程優先級的問題就可以回答上面的問題了,但是出於怕有人把Service的優先級與線程優先級問題混爲一談,這裏也單獨寫一下線程優先級的問題。首先要說一點,Service的優先級與單純的線程比較問題,其實是指(至少是服務進程)服務進程和空進程的優先級問題,單獨的Service與線程優先級沒有可比性。

1.進程優先級

android中進程的優先級與gc垃圾回收機制有關,當內存不足的時候,優先級越低的進程越容易被系統殺死,然後回收。

默認情況下, 同一個application中的所有組件運行在同一個linux進程下. 啓動一個組件 A時, 如果已存在處於運行狀態中的組件 B, 且A和B屬於同一個application, 那麼組件 A將在組件 B所在的進程下運行. 否則將爲A創建一個新的linux進程.

開發者也可以爲application中的組件指定不同的運行進程. manifest.xml文件中的<activity>, <service>, <receiver>, <provider>標籤都支持android:process屬性, 通過這個屬性, 可以爲組件指定運行的進程. <application>標籤也支持設定android:process屬性, 用於爲application下的所有組件指定默認的運行進程.

進程優先級從高到低分別爲5類,更具體的進程與優先級信息可以查看文章:Android平臺App進程優先級


1. 前臺進程

a.   進程擁有一個正在與用戶交互的Activity(這個Activity的onResume()方法被調用)。

b.   進程擁有一個綁定到正在與用戶交互的activity上的Service。

c.   進程擁有一個前臺運行的Service — service調用了方法startForeground()。

d.   進程擁有一個正在執行其任何一個生命週期回調方法(onCreate(),onStart(),或onDestroy())的Service。

e.   程擁有正在執行其onReceive()方法的BroadcastReceiver。

系統中前臺進程的數量很少, 前臺進程幾乎不會被殺死. 只有當內存低到無法保證所有的前臺進程同時運行時纔會選擇殺死某個前臺進程.

2.可視進程

a. 進程中包含未處於前臺但仍然可見的activity(調用了activity的onPause()方法, 但沒有調用onStop()方法). 典型的情況是運行activity時彈出對話框, 此時的activity雖然不是前臺activity, 但其仍然可見.

可視進程不會被系統殺死, 除非爲了保證前臺進程的運行而不得已爲之.

3. 服務進程. 

a. 進程中包含已啓動的service,如運行着一個被startService()所啓動的service。

儘管一個服務進程不直接影響用戶所見,但是它們通常做一些用戶關心的事情(比如播放音樂或下載數據),所以系統不到前臺進程和可見進程活不下去時不會殺它。

4.後臺進程

a.進程中包含不可見的activity(onStop()方法調用後的activity)。

這樣的進程們不會直接影響到用戶體驗,所以系統可以在任意時刻殺了它們從而爲前臺、可見、以及服務進程們提供存儲空間。通常有很多後臺進程在運行。它們被保存在一個LRU(最近最少使用)列表中來確保擁有最近剛被看到的activity的進程最後被殺。如果一個activity正確的實現了它的生命週期方法,並保存了它的當前狀態,那麼殺死它的進程將不會對用戶的可視化體驗造成影響。因爲當用戶返回到這個activity時,這個activity會恢復它所有的可見狀態。

5.空進程

a.一個進程不擁有入何active組件或者說不包含任何處於活動狀態的進程。

系統經常殺死空進程, 這不會造成任何影響. 空進程存在的唯一理由是爲了緩存一些啓動數據, 以便下次可以更快的啓動.

補充說明:

1.跟據進程中當前活動的組件的重要性,Android會把進程按排在其可能的最高級別。

例如,如果一個進程擁有一個service和一個可見的activity,進程會被定爲可見進程,而不是服務進程。

2.由於組件之間的依賴性, 進程的優先級有可能被提高. 

假如進程A服務於進程B, 則進程A的優先級不能低於進程B. 比如, 進程A的ContentProvider組件正在服務於進程B的某個組件, 或者進程A的service組件和進程B的某個組件綁定等, 這些情況下, 進程A的優先級都不會低於進程B(如果按照優先級規則, 進程A的優先級確實低於進程B, 則系統會選擇提高進程A的優先級到和進程B相同).

3.服務進程的優先級高於後臺進程, 因此如果activity需要執行耗時操作, 最好還是啓動一個service來完成. 當然, 在activity中啓動子線程完成耗時操作也可以, 但是這樣做的缺點在於, 一旦activity不再可見, activity所在的進程成爲後臺進程, 而內存不足時後臺進程隨時都有可能被系統殺死(但是啓動service完成耗時操作會帶來數據交互的問題, 比如耗時操作需要實時更新UI控件的狀態的話, service就不是一個好的選擇). 基於同樣的考慮, 在BroadcastReceiver中也不應該執行耗時操作, 而應該啓動service來完成(當然, BroadcastReceiver的生命週期過於短暫, 也決定了不能在其中執行耗時操作).


2.線程的優先級

談到線程,要先說一下進程與線程的區別,可以參照文章:進程與線程

android中線程優先級與調用的順序有關,優先級越高被調用的可能性越高,也就是說即使線程A的優先級高於線程B,同等情況下A不一定優於線程B被調用。

Android系統基於精簡過後的linux內核,其線程的調度受時間片輪轉和優先級控制等諸多因素影響。淡出認爲某個線程分配到的time slice多少是按照其優先級與其它線程優先級對比所決定的,這並不完全正確。

Linux系統的調度器在分配time slice的時候,採用的CFS(completely fair scheduler)策略。這種策略不但會參考單個線程的優先級,還會追蹤每個線程已經獲取到的time slice數量,如果高優先級的線程已經執行了很長時間,但低優先級的線程一直在等待,後續系統會保證低優先級的線程也能獲取更多的CPU時間。顯然使用這種調度策略的話,優先級高的線程並不一定能在爭取time slice上有絕對的優勢,所以Android系統在線程調度上使用了cgroups的概念,cgroups能更好的凸顯某些線程的重要性,使得優先級更高的線程明確的獲取到更多的time slice。

Android將線程分爲多個group,其中兩類group尤其重要。一類是default group,UI線程屬於這一類。另一類是background group,工作線程應該歸屬到這一類。background group當中所有的線程加起來總共也只能分配到5~10%的time slice,剩下的全部分配給default group,這樣設計顯然能保證UI線程繪製UI的流暢性。Android線程需要顯示的指定線程的優先級,比如:

new Thread(new Runnable() {
  @Override
  public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  }
}).start();

決定新啓一個線程執行任務的時候,首先要問自己這個任務在完成時間上是否重要到要和UI線程爭奪CPU資源。如果不是,降低線程優先級將其歸於background group。雖說Android系統在任務調度上是以線程爲基礎單位,但是設置單個thread的優先級也可以改變其所屬的control groups,從而影響CPU time slice的分配。而且進程的屬性變化也會影響到線程的調度,當一個App進入後臺的時候,該App所屬的整個進程都將進入background group,以確保處於foreground(用戶可見)的新進程能獲取到儘可能多的CPU資源。

====================================

參考文章:1.http://mrpeak.cn/blog/android-threading/

2. http://www.2cto.com/kf/201205/132324.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章