目前市面上的應用,貌似除了微信和手Q都會比較擔憂被用戶或者系統(廠商)殺死問題。本文對 Android 進程拉活進行一個總結。html
Android 進程拉活包括兩個層面:android
A. 提供進程優先級,下降進程被殺死的機率瀏覽器
B. 在進程被殺死後,進行拉活緩存
本文下面就從這兩個方面作一下總結。微信
Android 系統將盡可能長時間地保持應用進程,但爲了新建進程或運行更重要的進程,最終須要清除舊進程來回收內存。 爲了肯定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每一個進程放入「重要性層次結構」中。 必要時,系統會首先消除重要性最低的進程,而後是清除重要性稍低一級的進程,依此類推,以回收系統資源。網絡
進程的重要性,劃分5級:socket
1. 前臺進程(Foreground process)ide
2. 可見進程(Visible process)工具
3. 服務進程(Service process)ui
4. 後臺進程(Background process)
5. 空進程(Empty process)
前臺進程的重要性最高,依次遞減,空進程的重要性最低,下面分別來闡述每種級別的進程
用戶當前操做所必需的進程。一般在任意給定時間前臺進程都爲數很少。只有在內存不足以支持它們同時繼續運行這一萬不得已的狀況下,系統纔會終止它們。
A. 擁有用戶正在交互的 Activity(已調用 onResume())
B. 擁有某個 Service,後者綁定到用戶正在交互的 Activity
C. 擁有正在「前臺」運行的 Service(服務已調用 startForeground())
D. 擁有正執行一個生命週期回調的 Service(onCreate()、onStart() 或 onDestroy())
E. 擁有正執行其 onReceive() 方法的 BroadcastReceiver
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。可見進程被視爲是極其重要的進程,除非爲了維持全部前臺進程同時運行而必須終止,不然系統不會終止這些進程。
A. 擁有不在前臺、但仍對用戶可見的 Activity(已調用 onPause())。
B. 擁有綁定到可見(或前臺)Activity 的 Service
儘管服務進程與用戶所見內容沒有直接關聯,可是它們一般在執行一些用戶關心的操做(例如,在後臺播放音樂或從網絡下載數據)。所以,除非內存不足以維持全部前臺進程和可見進程同時運行,不然系統會讓服務進程保持運行狀態。
A. 正在運行 startService() 方法啓動的服務,且不屬於上述兩個更高類別進程的進程。
後臺進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前臺進程、可見進程或服務進程使用。 一般會有不少後臺進程在運行,所以它們會保存在 LRU 列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。若是某個 Activity 正確實現了生命週期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,由於當用戶導航回該 Activity 時,Activity 會恢復其全部可見狀態。
A. 對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)
保留這種進程的的惟一目的是用做緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使整體系統資源在進程緩存和底層內核緩存之間保持平衡,系統每每會終止這些進程。
A. 不含任何活動應用組件的進程
詳情參見:http://developer.android.com/intl/zh-cn/guide/components/processes-and-threads.html
Android 中對於內存的回收,主要依靠 Lowmemorykiller 來完成,是一種根據 OOM_ADJ 閾值級別觸發相應力度的內存回收的機制。
關於 OOM_ADJ 的說明以下:
其中紅色部分表明比較容易被殺死的 Android 進程(OOM_ADJ>=4),綠色部分表示不容易被殺死的 Android 進程,其餘表示非 Android 進程(純 Linux 進程)。在 Lowmemorykiller 回收內存時會根據進程的級別優先殺死 OOM_ADJ 比較大的進程,對於優先級相同的進程則進一步受到進程所佔內存和進程存活時間的影響。
Android 手機中進程被殺死可能有以下狀況:
綜上,能夠得出減小進程被殺死機率無非就是想辦法提升進程優先級,減小進程在內存不足等狀況下被殺死的機率。
監控手機鎖屏解鎖事件,在屏幕鎖屏時啓動1個像素的 Activity,在用戶解鎖時將 Activity 銷燬掉。注意該 Activity 需設計成用戶無感知。
經過該方案,可使進程的優先級在屏幕鎖屏時間由4提高爲最高優先級1。
適用場景: 本方案主要解決第三方應用及系統管理工具在檢測到鎖屏事件後一段時間(通常爲5分鐘之內)內會殺死後臺進程,已達到省電的目的問題。
適用版本: 適用於全部的 Android 版本。
首先定義 Activity,並設置 Activity 的大小爲1像素:
其次,從 AndroidManifest 中經過以下屬性,排除 Activity 在 RecentTask 中的顯示:
最後,控制 Activity 爲透明:
Activity 啓動與銷燬時機的控制:
Android 中 Service 的優先級爲4,經過 setForeground 接口能夠將後臺 Service 設置爲前臺 Service,使進程的優先級由4提高爲2,從而使進程的優先級僅僅低於用戶當前正在交互的進程,與可見進程優先級一致,使進程被殺死的機率大大下降。
從 Android2.3 開始調用 setForeground 將後臺 Service 設置爲前臺 Service 時,必須在系統的通知欄發送一條通知,也就是前臺 Service 與一條可見的通知時綁定在一塊兒的。
對於不須要常駐通知欄的應用來講,該方案雖好,但倒是用戶感知的,沒法直接使用。
經過實現一個內部 Service,在 LiveService 和其內部 Service 中同時發送具備相同 ID 的 Notification,而後將內部 Service 結束掉。隨着內部 Service 的結束,Notification 將會消失,但系統優先級依然保持爲2。
適用於目前已知全部版本。
在發生特定系統事件時,系統會發出響應的廣播,經過在 AndroidManifest 中「靜態」註冊對應的廣播監聽器,便可在發生響應事件時拉活。
經常使用的用於拉活的廣播事件包括:
適用於所有 Android 平臺。但存在以下幾個缺點:
1) 廣播接收器被管理軟件、系統軟件經過「自啓管理」等功能禁用的場景沒法接收到廣播,從而沒法自啓。
2) 系統廣播事件不可控,只能保證發生事件時拉活進程,但沒法保證進程掛掉後當即拉活。
所以,該方案主要做爲備用手段。
該方案總的設計思想與接收系統廣播相似,不一樣的是該方案爲接收第三方 Top 應用廣播。
經過反編譯第三方 Top 應用,如:手機QQ、微信、支付寶、UC瀏覽器等,以及友盟、信鴿、個推等 SDK,找出它們外發的廣播,在應用中進行監聽,這樣當這些應用發出廣播時,就會將咱們的應用拉活。
該方案的有效程度除與系統廣播同樣的因素外,主要受以下因素限制:
1) 反編譯分析過的第三方應用的多少
2) 第三方應用的廣播屬於應用私有,當前版本中有效的廣播,在後續版本隨時就可能被移除或被改成不外發。
這些因素都影響了拉活的效果。
將 Service 設置爲 START_STICKY,利用系統機制在 Service 掛掉後自動拉活:
以下兩種狀況沒法拉活:
1. Service 第一次被異常殺死後會在5秒內重啓,第二次被殺死會在10秒內重啓,第三次會在20秒內重啓,一旦在短期內 Service 被殺死達到5次,則系統再也不拉起。
2. 進程被取得 Root 權限的管理工具或系統工具經過 forestop 中止掉,沒法重啓。
主要思想:利用 Linux 中的 fork 機制建立 Native 進程,在 Native 進程中監控主進程的存活,當主進程掛掉後,在 Native 進程中當即對主進程進行拉活。
主要原理:在 Android 中全部進程和系統組件的生命週期受 ActivityManagerService 的統一管理。並且,經過 Linux 的 fork 機制建立的進程爲純 Linux 進程,其生命週期不受 Android 的管理。
挑戰一:在 Native 進程中如何感知主進程死亡。
要在 Native 進程中感知主進程是否存活有兩種實現方式:
1. 在 Native 進程中經過死循環或定時器,輪訓判斷主進程是否存活,檔主進程不存活時進行拉活。該方案的很大缺點是不停的輪詢執行判斷邏輯,很是耗電。
2. 在主進程中建立一個監控文件,而且在主進程中持有文件鎖。在拉活進程啓動後申請文件鎖將會被堵塞,一旦能夠成功獲取到鎖,說明主進程掛掉,便可進行拉活。因爲 Android 中的應用都運行於虛擬機之上,Java 層的文件鎖與 Linux 層的文件鎖是不一樣的,要實現該功能須要封裝 Linux 層的文件鎖供上層調用。
封裝 Linux 文件鎖的代碼以下:
Native 層中堵塞申請文件鎖的部分代碼:
挑戰二:在 Native 進程中如何拉活主進程。
經過 Native 進程拉活主進程的部分代碼以下,即經過 am 命令進行拉活。經過指定「–include-stopped-packages」參數來拉活主進程處於 forestop 狀態的狀況。
挑戰三:如何保證 Native 進程的惟一。
從可擴展性和進程惟一等多方面考慮,將 Native 進程設計層 C/S 結構模式,主進程與 Native 進程經過 Localsocket 進行通訊。在Native進程中利用 Localsocket 保證 Native 進程的惟一性,不至於出現建立多個 Native 進程以及 Native 進程變成殭屍進程等問題。
該方案主要適用於 Android5.0 如下版本手機。
該方案不受 forcestop 影響,被強制中止的應用依然能夠被拉活,在 Android5.0 如下版本拉活效果很是好。
對於 Android 5.0 以上手機,系統雖然會將native進程內的全部進程都殺死,這裏其實就是系統「依次」殺死進程時間與拉活邏輯執行時間賽跑的問題,若是能夠跑的比系統邏輯快,依然能夠有效拉起。記得網上有人作過實驗,該結論是成立的,在某些 Android 5.0 以上機型有效。
Android5.0 之後系統對 Native 進程等增強了管理,Native 拉活方式失效。系統在 Android5.0 以上版本提供了 JobScheduler 接口,系統會定時調用該進程以使應用進行一些邏輯操做。
在本項目中,我對 JobScheduler 進行了進一步封裝,兼容 Android5.0 如下版本。封裝後 JobScheduler 接口的使用以下:
該方案主要適用於 Android5.0 以上版本手機。
該方案在 Android5.0 以上版本中不受 forcestop 影響,被強制中止的應用依然能夠被拉活,在 Android5.0 以上版本拉活效果很是好。
僅在小米手機可能會出現有時沒法拉活的問題。
Android 系統的帳號同步機制會按期同步帳號進行,該方案目的在於利用同步機制進行進程的拉活。添加帳號和設置同步週期的代碼以下:
該方案須要在 AndroidManifest 中定義帳號受權與同步服務。
該方案適用於全部的 Android 版本,包括被 forestop 掉的進程也能夠進行拉活。
最新 Android 版本(Android N)中系統好像對帳戶同步這裏作了變更,該方法再也不有效。
經研究發現還有其餘一些系統拉活措施可使用,但在使用時須要用戶受權,用戶感知比較強烈。
這些方案包括:
1. 利用系統通知管理權限進行拉活
2. 利用輔助功能拉活,將應用加入廠商或管理軟件白名單。
這些方案須要結合具體產品特性來搞。
上面全部解釋這些方案都是考慮的無 Root 的狀況。
其餘還有一些技術以外的措施,好比說應用內 Push 通道的選擇:
1. 國外版應用:接入 Google 的 GCM。
2. 國內版應用:根據終端不一樣,在小米手機(包括 MIUI)接入小米推送、華爲手機接入華爲推送;其餘手機能夠考慮接入騰訊信鴿或極光推送與小米推送作 A/B Test。