轉(zhuǎn)載一篇不錯的文章,解決了我一下關(guān)于如何保留android進(jìn)程的技術(shù)。
Android 進(jìn)程保活
目前市面上的應(yīng)用,貌似除了微信和手Q都會比較擔(dān)心被用戶或者系統(tǒng)(廠商)殺死問題。本文對 Android 進(jìn)程拉活進(jìn)行一個總結(jié)。
Android 進(jìn)程拉活包括兩個層面:
A. 提供進(jìn)程優(yōu)先級,降低進(jìn)程被殺死的概率
B. 在進(jìn)程被殺死后,進(jìn)行拉活
本文下面就從這兩個方面做一下總結(jié)。
1. 進(jìn)程的優(yōu)先級
Android 系統(tǒng)將盡量長時間地保持應(yīng)用進(jìn)程,但為了新建進(jìn)程或運(yùn)行更重要的進(jìn)程,最終需要清除舊進(jìn)程來回收內(nèi)存。 為了確定保留或終止哪些進(jìn)程,系統(tǒng)會根據(jù)進(jìn)程中正在運(yùn)行的組件以及這些組件的狀態(tài),將每個進(jìn)程放入“重要性層次結(jié)構(gòu)”中。 必要時,系統(tǒng)會首先消除重要性最低的進(jìn)程,然后是清除重要性稍低一級的進(jìn)程,依此類推,以回收系統(tǒng)資源。
進(jìn)程的重要性,劃分5級:
前臺進(jìn)程(Foreground process)
可見進(jìn)程(Visible process)
服務(wù)進(jìn)程(Service process)
后臺進(jìn)程(Background process)
空進(jìn)程(Empty process)
前臺進(jìn)程的重要性最高,依次遞減,空進(jìn)程的重要性最低,下面分別來闡述每種級別的進(jìn)程
1.1. 前臺進(jìn)程 —— Foreground process
用戶當(dāng)前操作所必需的進(jìn)程。通常在任意給定時間前臺進(jìn)程都為數(shù)不多。只有在內(nèi)存不足以支持它們同時繼續(xù)運(yùn)行這一萬不得已的情況下,系統(tǒng)才會終止它們。
A. 擁有用戶正在交互的 Activity(已調(diào)用 onResume())
B. 擁有某個 Service,后者綁定到用戶正在交互的 Activity
C. 擁有正在“前臺”運(yùn)行的 Service(服務(wù)已調(diào)用 startForeground())
D. 擁有正執(zhí)行一個生命周期回調(diào)的 Service(onCreate()、onStart() 或 onDestroy())
E. 擁有正執(zhí)行其 onReceive() 方法的 BroadcastReceiver
1.2. 可見進(jìn)程 —— Visible process
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內(nèi)容的進(jìn)程。可見進(jìn)程被視為是極其重要的進(jìn)程,除非為了維持所有前臺進(jìn)程同時運(yùn)行而必須終止,否則系統(tǒng)不會終止這些進(jìn)程。
A. 擁有不在前臺、但仍對用戶可見的 Activity(已調(diào)用 onPause())。
B. 擁有綁定到可見(或前臺)Activity 的 Service
1.3. 服務(wù)進(jìn)程 —— Service process
盡管服務(wù)進(jìn)程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),但是它們通常在執(zhí)行一些用戶關(guān)心的操作(例如,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此,除非內(nèi)存不足以維持所有前臺進(jìn)程和可見進(jìn)程同時運(yùn)行,否則系統(tǒng)會讓服務(wù)進(jìn)程保持運(yùn)行狀態(tài)。
A. 正在運(yùn)行 startService() 方法啟動的服務(wù),且不屬于上述兩個更高類別進(jìn)程的進(jìn)程。
1.4. 后臺進(jìn)程 —— Background process
后臺進(jìn)程對用戶體驗沒有直接影響,系統(tǒng)可能隨時終止它們,以回收內(nèi)存供前臺進(jìn)程、可見進(jìn)程或服務(wù)進(jìn)程使用。 通常會有很多后臺進(jìn)程在運(yùn)行,因此它們會保存在 LRU 列表中,以確保包含用戶最近查看的 Activity 的進(jìn)程最后一個被終止。如果某個 Activity 正確實現(xiàn)了生命周期方法,并保存了其當(dāng)前狀態(tài),則終止其進(jìn)程不會對用戶體驗產(chǎn)生明顯影響,因為當(dāng)用戶導(dǎo)航回該 Activity 時,Activity 會恢復(fù)其所有可見狀態(tài)。
A. 對用戶不可見的 Activity 的進(jìn)程(已調(diào)用 Activity的onStop() 方法)
1.5. 空進(jìn)程 —— Empty process
保留這種進(jìn)程的的唯一目的是用作緩存,以縮短下次在其中運(yùn)行組件所需的啟動時間。 為使總體系統(tǒng)資源在進(jìn)程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會終止這些進(jìn)程。
2. Android 進(jìn)程回收策略
Android 中對于內(nèi)存的回收,主要依靠 Lowmemorykiller 來完成,是一種根據(jù) OOM_ADJ 閾值級別觸發(fā)相應(yīng)力度的內(nèi)存回收的機(jī)制。
關(guān)于 OOM_ADJ 的說明如下:
其中紅色部分代表比較容易被殺死的 Android 進(jìn)程(OOM_ADJ>=4),綠色部分表示不容易被殺死的 Android 進(jìn)程,其他表示非 Android 進(jìn)程(純 Linux 進(jìn)程)。在 Lowmemorykiller 回收內(nèi)存時會根據(jù)進(jìn)程的級別優(yōu)先殺死 OOM_ADJ 比較大的進(jìn)程,對于優(yōu)先級相同的進(jìn)程則進(jìn)一步受到進(jìn)程所占內(nèi)存和進(jìn)程存活時間的影響。
Android 手機(jī)中進(jìn)程被殺死可能有如下情況:
綜上,可以得出減少進(jìn)程被殺死概率無非就是想辦法提高進(jìn)程優(yōu)先級,減少進(jìn)程在內(nèi)存不足等情況下被殺死的概率。
3. 提升進(jìn)程優(yōu)先級的方案
3.1. 利用 Activity 提升權(quán)限
3.1.1. 方案設(shè)計思想
監(jiān)控手機(jī)鎖屏解鎖事件,在屏幕鎖屏?xí)r啟動1個像素的 Activity,在用戶解鎖時將 Activity 銷毀掉。注意該 Activity 需設(shè)計成用戶無感知。
通過該方案,可以使進(jìn)程的優(yōu)先級在屏幕鎖屏?xí)r間由4提升為最高優(yōu)先級1。
3.1.2. 方案適用范圍
適用場景: 本方案主要解決第三方應(yīng)用及系統(tǒng)管理工具在檢測到鎖屏事件后一段時間(一般為5分鐘以內(nèi))內(nèi)會殺死后臺進(jìn)程,已達(dá)到省電的目的問題。
適用版本: 適用于所有的 Android 版本。
3.1.3. 方案具體實現(xiàn)
首先定義 Activity,并設(shè)置 Activity 的大小為1像素:
其次,從 AndroidManifest 中通過如下屬性,排除 Activity 在 RecentTask 中的顯示:
最后,控制 Activity 為透明:
Activity 啟動與銷毀時機(jī)的控制:
3.2. 利用 Notification 提升權(quán)限
3.2.1. 方案設(shè)計思想
Android 中 Service 的優(yōu)先級為4,通過 setForeground 接口可以將后臺 Service 設(shè)置為前臺 Service,使進(jìn)程的優(yōu)先級由4提升為2,從而使進(jìn)程的優(yōu)先級僅僅低于用戶當(dāng)前正在交互的進(jìn)程,與可見進(jìn)程優(yōu)先級一致,使進(jìn)程被殺死的概率大大降低。
3.2.2. 方案實現(xiàn)挑戰(zhàn)
從 Android2.3 開始調(diào)用 setForeground 將后臺 Service 設(shè)置為前臺 Service 時,必須在系統(tǒng)的通知欄發(fā)送一條通知,也就是前臺 Service 與一條可見的通知時綁定在一起的。
對于不需要常駐通知欄的應(yīng)用來說,該方案雖好,但卻是用戶感知的,無法直接使用。
3.2.3. 方案挑戰(zhàn)應(yīng)對措施
通過實現(xiàn)一個內(nèi)部 Service,在 LiveService 和其內(nèi)部 Service 中同時發(fā)送具有相同 ID 的 Notification,然后將內(nèi)部 Service 結(jié)束掉。隨著內(nèi)部 Service 的結(jié)束,Notification 將會消失,但系統(tǒng)優(yōu)先級依然保持為2。
3.2.4. 方案適用范圍
適用于目前已知所有版本。
3.2.5. 方案具體實現(xiàn)
4. 進(jìn)程死后拉活的方案
本人覺得比較通用的解決方案
4.1. 利用系統(tǒng)廣播拉活
4.1.1. 方案設(shè)計思想
在發(fā)生特定系統(tǒng)事件時,系統(tǒng)會發(fā)出響應(yīng)的廣播,通過在 AndroidManifest 中“靜態(tài)”注冊對應(yīng)的廣播監(jiān)聽器,即可在發(fā)生響應(yīng)事件時拉活。
常用的用于拉活的廣播事件包括:
4.1.2. 方案適用范圍
適用于全部 Android 平臺。但存在如下幾個缺點(diǎn):
1) 廣播接收器被管理軟件、系統(tǒng)軟件通過“自啟管理”等功能禁用的場景無法接收到廣播,從而無法自啟。
2) 系統(tǒng)廣播事件不可控,只能保證發(fā)生事件時拉活進(jìn)程,但無法保證進(jìn)程掛掉后立即拉活。
因此,該方案主要作為備用手段。
4.2. 利用第三方應(yīng)用廣播拉活
4.2.1. 方案設(shè)計思想
該方案總的設(shè)計思想與接收系統(tǒng)廣播類似,不同的是該方案為接收第三方 Top 應(yīng)用廣播。
通過反編譯第三方 Top 應(yīng)用,如:手機(jī)QQ、微信、支付寶、UC瀏覽器等,以及友盟、信鴿、個推等 SDK,找出它們外發(fā)的廣播,在應(yīng)用中進(jìn)行監(jiān)聽,這樣當(dāng)這些應(yīng)用發(fā)出廣播時,就會將我們的應(yīng)用拉活。
4.2.2. 方案適用范圍
該方案的有效程度除與系統(tǒng)廣播一樣的因素外,主要受如下因素限制:
1) 反編譯分析過的第三方應(yīng)用的多少
2) 第三方應(yīng)用的廣播屬于應(yīng)用私有,當(dāng)前版本中有效的廣播,在后續(xù)版本隨時就可能被移除或被改為不外發(fā)。
這些因素都影響了拉活的效果。
4.3. 利用系統(tǒng)Service機(jī)制拉活
4.3.1. 方案設(shè)計思想
將 Service 設(shè)置為 START_STICKY,利用系統(tǒng)機(jī)制在 Service 掛掉后自動拉活:
4.3.2. 方案適用范圍
如下兩種情況無法拉活:
Service 第一次被異常殺死后會在5秒內(nèi)重啟,第二次被殺死會在10秒內(nèi)重啟,第三次會在20秒內(nèi)重啟,一旦在短時間內(nèi) Service 被殺死達(dá)到5次,則系統(tǒng)不再拉起。
進(jìn)程被取得 Root 權(quán)限的管理工具或系統(tǒng)工具通過 forestop 停止掉,無法重啟。
4.4. 利用Native進(jìn)程拉活
4.4.1. 方案設(shè)計思想
主要思想:利用 Linux 中的 fork 機(jī)制創(chuàng)建 Native 進(jìn)程,在 Native 進(jìn)程中監(jiān)控主進(jìn)程的存活,當(dāng)主進(jìn)程掛掉后,在 Native 進(jìn)程中立即對主進(jìn)程進(jìn)行拉活。
主要原理:在 Android 中所有進(jìn)程和系統(tǒng)組件的生命周期受 ActivityManagerService 的統(tǒng)一管理。而且,通過 Linux 的 fork 機(jī)制創(chuàng)建的進(jìn)程為純 Linux 進(jìn)程,其生命周期不受 Android 的管理。
4.4.2. 方案實現(xiàn)挑戰(zhàn)
挑戰(zhàn)一:在 Native 進(jìn)程中如何感知主進(jìn)程死亡。
要在 Native 進(jìn)程中感知主進(jìn)程是否存活有兩種實現(xiàn)方式:
在 Native 進(jìn)程中通過死循環(huán)或定時器,輪訓(xùn)判斷主進(jìn)程是否存活,檔主進(jìn)程不存活時進(jìn)行拉活。該方案的很大缺點(diǎn)是不停的輪詢執(zhí)行判斷邏輯,非常耗電。
在主進(jìn)程中創(chuàng)建一個監(jiān)控文件,并且在主進(jìn)程中持有文件鎖。在拉活進(jìn)程啟動后申請文件鎖將會被堵塞,一旦可以成功獲取到鎖,說明主進(jìn)程掛掉,即可進(jìn)行拉活。由于 Android 中的應(yīng)用都運(yùn)行于虛擬機(jī)之上,Java 層的文件鎖與 Linux 層的文件鎖是不同的,要實現(xiàn)該功能需要封裝 Linux 層的文件鎖供上層調(diào)用。
封裝 Linux 文件鎖的代碼如下:
Native 層中堵塞申請文件鎖的部分代碼:
挑戰(zhàn)二:在 Native 進(jìn)程中如何拉活主進(jìn)程。
通過 Native 進(jìn)程拉活主進(jìn)程的部分代碼如下,即通過 am 命令進(jìn)行拉活。通過指定“—include-stopped-packages”參數(shù)來拉活主進(jìn)程處于 forestop 狀態(tài)的情況。
挑戰(zhàn)三:如何保證 Native 進(jìn)程的唯一。
從可擴(kuò)展性和進(jìn)程唯一等多方面考慮,將 Native 進(jìn)程設(shè)計層 C/S 結(jié)構(gòu)模式,主進(jìn)程與 Native 進(jìn)程通過 Localsocket 進(jìn)行通信。在Native進(jìn)程中利用 Localsocket 保證 Native 進(jìn)程的唯一性,不至于出現(xiàn)創(chuàng)建多個 Native 進(jìn)程以及 Native 進(jìn)程變成僵尸進(jìn)程等問題。
4.4.3. 方案適用范圍
該方案主要適用于 Android5.0 以下版本手機(jī)。
該方案不受 forcestop 影響,被強(qiáng)制停止的應(yīng)用依然可以被拉活,在 Android5.0 以下版本拉活效果非常好。
對于 Android5.0 以上手機(jī),系統(tǒng)雖然會將native進(jìn)程內(nèi)的所有進(jìn)程都?xì)⑺溃@里其實就是系統(tǒng)“依次”殺死進(jìn)程時間與拉活邏輯執(zhí)行時間賽跑的問題,如果可以跑的比系統(tǒng)邏輯快,依然可以有效拉起。記得網(wǎng)上有人做過實驗,該結(jié)論是成立的,在某些 Android 5.0 以上機(jī)型有效。
4.5. 利用 JobScheduler 機(jī)制拉活
4.5.1. 方案設(shè)計思想
Android5.0 以后系統(tǒng)對 Native 進(jìn)程等加強(qiáng)了管理,Native 拉活方式失效。系統(tǒng)在 Android5.0 以上版本提供了 JobScheduler 接口,系統(tǒng)會定時調(diào)用該進(jìn)程以使應(yīng)用進(jìn)行一些邏輯操作。
在本項目中,我對 JobScheduler 進(jìn)行了進(jìn)一步封裝,兼容 Android5.0 以下版本。封裝后 JobScheduler 接口的使用如下:
4.5.2. 方案適用范圍
該方案主要適用于 Android5.0 以上版本手機(jī)。
該方案在 Android5.0 以上版本中不受 forcestop 影響,被強(qiáng)制停止的應(yīng)用依然可以被拉活,在 Android5.0 以上版本拉活效果非常好。
僅在小米手機(jī)可能會出現(xiàn)有時無法拉活的問題。
4.6. 利用賬號同步機(jī)制拉活
4.6.1. 方案設(shè)計思想
Android 系統(tǒng)的賬號同步機(jī)制會定期同步賬號進(jìn)行,該方案目的在于利用同步機(jī)制進(jìn)行進(jìn)程的拉活。添加賬號和設(shè)置同步周期的代碼如下:
該方案需要在 AndroidManifest 中定義賬號授權(quán)與同步服務(wù)。
4.6.2. 方案適用范圍
該方案適用于所有的 Android 版本,包括被 forestop 掉的進(jìn)程也可以進(jìn)行拉活。
最新 Android 版本(Android N)中系統(tǒng)好像對賬戶同步這里做了變動,該方法不再有效。
5. 其他有效拉活方案
經(jīng)研究發(fā)現(xiàn)還有其他一些系統(tǒng)拉活措施可以使用,但在使用時需要用戶授權(quán),用戶感知比較強(qiáng)烈。
這些方案包括:
利用系統(tǒng)通知管理權(quán)限進(jìn)行拉活
利用輔助功能拉活,將應(yīng)用加入廠商或管理軟件白名單。
這些方案需要結(jié)合具體產(chǎn)品特性來搞。
上面所有解釋這些方案都是考慮的無 Root 的情況。
其他還有一些技術(shù)之外的措施,比如說應(yīng)用內(nèi) Push 通道的選擇:
國外版應(yīng)用:接入 Google 的 GCM。
國內(nèi)版應(yīng)用:根據(jù)終端不同,在小米手機(jī)(包括 MIUI)接入小米推送、華為手機(jī)接入華為推送;其他手機(jī)可以考慮接入騰訊信鴿或極光推送與小米推送做 A/B Test。