Android 服務為啥保不活
轉發請註明出處: https://www.jianshu.com/p/bf0c82be9b25
請尊重原創作者
有一段時間都在說服務怎麼保活,怎麼才能不被殺死,各種花裡胡哨的保活方案抄來抄去,到頭來 能打的 一個都沒有。
【 硬核場景 】
既然這麼多可操作性低的“保命方案”,那麼需要知道些什麼知識點才能脫穎而出,好吧我們提供不了保活的黑科技,如果對方按套路出牌的話,至少會讓你解釋下為什麼保不活。
【 GC 】
就是因為GC機制,所以我們的服務才保不活,但是這個過程我們不可控,很難讓執行中的應用感知什麼時候GC觸發了,從而做點什麼來補救。所以才有了 輪詢機制 來喚醒,一段時間檢查下服務有沒有在跑,沒有就再扶起來繼續送。
通常情況下,觸發GC的條件有兩個:
1.當應用程式空閒時,即沒有應用執行緒在執行時,GC會被呼叫(隨心隨意收垃圾)
2.Java堆記憶體不足時,GC會被呼叫(被強制叫去收垃圾)
D/dalvikvm: GC_CONCURRENT freed 2012K, 63% free 3213K/9291K, external 4501K/5161K, paused 2ms+2ms 各引數對照含義 D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
相信大家都很熟悉這log,但對它表達的意義估計就看不太懂,為了方便解釋,把各引數對照在上面列出,簡單說個引數,其它大家再去詳細瞭解GC的引數解讀吧。
GC Reason就是指引起GC原因,有以下幾種:
- GC_CONCURRENT:當堆開始填充時,併發GC可以釋放記憶體。
- GC_FOR_MALLOC:當堆記憶體已滿時,app嘗試分配記憶體而引起的GC,系統必須停止app並回收記憶體。
- GC_HPROF_DUMP_HEAP:當你請求建立 HPROF 檔案來分析堆記憶體時出現- 的GC。
- GC_EXPLICIT:顯示的GC,例如呼叫System.gc()(應該避免呼叫顯示的GC,信任GC會在需要時執行)。
- GC_EXTERNAL_ALLOC:僅適用於 API 級別小於等於10 ,用於外部分配記憶體的GC。
回到正題,GC為什麼導致服務保不活?那是因為在當各種情況下觸發了垃圾回收,它就會去找能宰的都宰掉,從而擠出記憶體給那些優先給使用者接觸到的應用或服務。所以才會有 Notification的前臺服務 這樣的做法讓服務不輕易被殺死,儘可能把服務提升靠近使用者,但是其核心思想都是為了改變 oom_adj 的等級,等級越低越重要越不容易被殺死。在解釋改變等級之前,有個等級的概念需要先了解的。
【 程序等級 】
還是大家熟知的知識點,程序等級可以劃分為這五大類:
1.前臺程序
2.可見程序
3.服務程序
4.後臺程序
5.空程序
他們在不同場景下,會對應到某一個值,具體檢視方法可以用 adb 的命令檢視。但這還只是等級概念,能大概地估算被殺死的可能性。而最終決定會不會被殺的是一個叫做 閥值 概念的值,這就涉及到 linux 的知識點,我們只需要知道 linux 會通過某個計算方式得出一個叫做 oom_score 的值,而這個就是和GC有直接關係了,因為根據不同情況,GC回收的程度不一樣,那麼每個等級都有對應閥值,只要沒超過這個閥值的程序,就不會被回收。
比如說,這次GC只宰到 “後臺程序” 的最低閥值,那麼 “空程序”、“後臺程序” 都會被宰,而 [服務程序]、[可見程序]、[前臺程序] 這些程序安然無事,但某一時機這個閥值被系統調得更嚴格,需要更多記憶體了,調到 “服務程序” 的最低閥值,那麼按照這個道理, “空程序”、“後臺程序” 、“服務程序” 都會被宰掉。好,那麼我們平時怎麼知道自己的服務在什麼等級呢?
【oom_adj】
adj級別 | 值 | 解釋 |
---|---|---|
UNKNOWN_ADJ | 16 | 預留的最低級別,一般對於快取的程序才有可能設定成這個級別 |
CACHED_APP_MAX_ADJ | 15 | 快取程序,空程序,在記憶體不足的情況下就會優先被kill |
CACHED_APP_MIN_ADJ | 9 | 快取程序,也就是空程序 |
SERVICE_B_ADJ | 8 | 不活躍的程序 |
PREVIOUS_APP_ADJ | 7 | 切換程序 |
HOME_APP_ADJ | 6 | 與Home互動的程序 |
SERVICE_ADJ | 5 | 有Service的程序 |
HEAVY_WEIGHT_APP_ADJ | 4 | 高權重程序 |
BACKUP_APP_ADJ | 3 | 正在備份的程序 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知的程序,比如那種播放音樂 |
VISIBLE_APP_ADJ | 1 | 可見程序 |
FOREGROUND_APP_ADJ | 0 | 前臺程序 |
PERSISTENT_SERVICE_ADJ | -11 | 重要程序 |
PERSISTENT_PROC_ADJ | -12 | 核心程序 |
SYSTEM_ADJ | -16 | 系統程序 |
NATIVE_ADJ | -17 | 系統起的Native程序 |
在終端輸入命令(過程就大概是 先連線裝置;進入shell;獲得程序pid;檢視值)
adb shell
cat /proc/{pid}/oom_adj

檢視oom_adj的終端命令
如圖所示了,某一個程序等級在 -13 那麼對照到上面的表,可以知道起碼是 系統程序 級別,那麼普通的GC基本是宰不到它的。所以保活的核心,就是為了提升這個等級,類似的思想就有在AndroidManifest.xml的服務增加 優先順序priority ,就儘量在這個閥值的範圍往上靠,希望別宰到我。
【 不過 】
也不是沒有辦法保活,你需要知道一下這幾個屬性的作用,但有個前提咯!要提升到系統級別,唯一有效而且安全的做法,就是有 系統簽名 。類似的,每個品牌每個主機板每個型號都有會它的簽名檔案,就如平時我們自己生成的那種 jks、keystore 檔案(只不過它們由 platform.x509.pem、platform.pk8 這些來生成),然後下面這些屬性就能起作用
android:sharedUserId="android.uid.system"
這個屬性使用應該會比較少,官方對它的解釋:與其他應用程式共享的Linux使用者ID的名稱。預設情況下,Android為每個應用程式分配了自己唯一的使用者ID。但是,如果將該屬性設定為兩個或多個應用程式相同的值,它們將共享相同的ID——前提是它們的證書集相同。具有相同使用者ID的應用程式可以訪問彼此的資料,如果需要,還可以在相同的程序中執行。
換言之,你得有系統簽名才能和他們平起平坐。
persistent=true
這個在保活文章出現的頻率就比較高了,但是卻不怎麼起作用,再來看看官方解釋:應用程式是否應該一直執行—如果應該,則為“true”;如果不應該,則為“false”。預設值為“false”。應用程式通常不應設定此標誌;永續性模式僅適用於某些系統應用程式。
通常情況下,我們的包都會安裝到 data/data/ 目錄下,而 persistent 需要 system/app 配套使用才會起效果的,也就是說應用本身就已經是系統應用,那麼這個屬性才生效,才會在系統啟動的時候拉起有該屬性的應用,並且被殺死後能夠重啟應用。
那我們怎麼驗證自己的屬性有沒有生效呢?
adb shell dumpsys meminfo

專業素養,打碼了
看一下自己的應用包名有沒有出現在 Persistent 列表就行了,按照網上那些保活方式,通常只會出現在 A Services 、Visible、Foreground 這些列表內。
【 總結 】
大家對服務為啥保不活,有了個整體概念,那麼概括成一句話應該就是: GC可能會殺死等級 oom_adj 不太重要的服務,而我們所做的一切都是為了提升這個等級的值,讓它不那麼輕易被殺死。