轉(zhuǎn)發(fā)請(qǐng)注明出處:http://www.reibang.com/p/bf0c82be9b25
請(qǐng)尊重原創(chuàng)作者
有一段時(shí)間都在說服務(wù)怎么蓖凭瑁活,怎么才能不被殺死侧啼,各種花里胡哨的迸F猓活方案抄來抄去,到頭來 能打的 一個(gè)都沒有痊乾。
【硬核場(chǎng)景】
既然這么多可操作性低的“保命方案”皮壁,那么需要知道些什么知識(shí)點(diǎn)才能脫穎而出,好吧我們提供不了蹦纳螅活的黑科技蛾魄,如果對(duì)方按套路出牌的話,至少會(huì)讓你解釋下為什么保不活协饲。
【GC】
就是因?yàn)镚C機(jī)制畏腕,所以我們的服務(wù)才保不活,但是這個(gè)過程我們不可控茉稠,很難讓運(yùn)行中的應(yīng)用感知什么時(shí)候GC觸發(fā)了描馅,從而做點(diǎn)什么來補(bǔ)救。所以才有了 輪詢機(jī)制 來喚醒而线,一段時(shí)間檢查下服務(wù)有沒有在跑铭污,沒有就再扶起來繼續(xù)送。
通常情況下膀篮,觸發(fā)GC的條件有兩個(gè):
1.當(dāng)應(yīng)用程序空閑時(shí),即沒有應(yīng)用線程在運(yùn)行時(shí),GC會(huì)被調(diào)用(隨心隨意收垃圾)
2.Java堆內(nèi)存不足時(shí),GC會(huì)被調(diào)用(被強(qiáng)制叫去收垃圾)
D/dalvikvm: GC_CONCURRENT freed 2012K, 63% free 3213K/9291K, external 4501K/5161K, paused 2ms+2ms
各參數(shù)對(duì)照含義
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
相信大家都很熟悉這log嘹狞,但對(duì)它表達(dá)的意義估計(jì)就看不太懂,為了方便解釋誓竿,把各參數(shù)對(duì)照在上面列出磅网,簡(jiǎn)單說個(gè)參數(shù),其它大家再去詳細(xì)了解GC的參數(shù)解讀吧筷屡。
GC Reason 就是指引起GC原因涧偷,有以下幾種:
- GC_CONCURRENT:當(dāng)堆開始填充時(shí)簸喂,并發(fā)GC可以釋放內(nèi)存。
- GC_FOR_MALLOC:當(dāng)堆內(nèi)存已滿時(shí)燎潮,app嘗試分配內(nèi)存而引起的GC喻鳄,系統(tǒng)必須停止app并回收內(nèi)存。
- GC_HPROF_DUMP_HEAP:當(dāng)你請(qǐng)求創(chuàng)建 HPROF 文件來分析堆內(nèi)存時(shí)出現(xiàn)- 的GC确封。
- GC_EXPLICIT:顯示的GC除呵,例如調(diào)用System.gc()(應(yīng)該避免調(diào)用顯示的GC,信任GC會(huì)在需要時(shí)運(yùn)行)爪喘。
- GC_EXTERNAL_ALLOC:僅適用于 API 級(jí)別小于等于10 颜曾,用于外部分配內(nèi)存的GC。
回到正題腥放,GC為什么導(dǎo)致服務(wù)保不活泛啸?那是因?yàn)樵诋?dāng)各種情況下觸發(fā)了垃圾回收,它就會(huì)去找能宰的都宰掉秃症,從而擠出內(nèi)存給那些優(yōu)先給用戶接觸到的應(yīng)用或服務(wù)。所以才會(huì)有 Notification的前臺(tái)服務(wù) 這樣的做法讓服務(wù)不輕易被殺死吕粹,盡可能把服務(wù)提升靠近用戶种柑,但是其核心思想都是為了改變 oom_adj 的等級(jí),等級(jí)越低越重要越不容易被殺死匹耕。在解釋改變等級(jí)之前聚请,有個(gè)等級(jí)的概念需要先了解的。
【進(jìn)程等級(jí)】
還是大家熟知的知識(shí)點(diǎn)稳其,進(jìn)程等級(jí)可以劃分為這五大類:
1.前臺(tái)進(jìn)程
2.可見進(jìn)程
3.服務(wù)進(jìn)程
4.后臺(tái)進(jìn)程
5.空進(jìn)程
他們?cè)诓煌瑘?chǎng)景下驶赏,會(huì)對(duì)應(yīng)到某一個(gè)值,具體查看方法可以用 adb 的命令查看既鞠。但這還只是等級(jí)概念煤傍,能大概地估算被殺死的可能性。而最終決定會(huì)不會(huì)被殺的是一個(gè)叫做 閥值 概念的值嘱蛋,這就涉及到 linux 的知識(shí)點(diǎn)蚯姆,我們只需要知道 linux 會(huì)通過某個(gè)計(jì)算方式得出一個(gè)叫做 oom_score 的值,而這個(gè)就是和GC有直接關(guān)系了洒敏,因?yàn)楦鶕?jù)不同情況龄恋,GC回收的程度不一樣,那么每個(gè)等級(jí)都有對(duì)應(yīng)閥值凶伙,只要沒超過這個(gè)閥值的進(jìn)程郭毕,就不會(huì)被回收。
比如說函荣,這次GC只宰到 “后臺(tái)進(jìn)程” 的最低閥值显押,那么 “空進(jìn)程”扳肛、“后臺(tái)進(jìn)程” 都會(huì)被宰,而 [服務(wù)進(jìn)程]煮落、[可見進(jìn)程]敞峭、[前臺(tái)進(jìn)程] 這些進(jìn)程安然無事,但某一時(shí)機(jī)這個(gè)閥值被系統(tǒng)調(diào)得更嚴(yán)格蝉仇,需要更多內(nèi)存了旋讹,調(diào)到 “服務(wù)進(jìn)程” 的最低閥值,那么按照這個(gè)道理轿衔, “空進(jìn)程”沉迹、“后臺(tái)進(jìn)程” 、“服務(wù)進(jìn)程” 都會(huì)被宰掉害驹。好鞭呕,那么我們平時(shí)怎么知道自己的服務(wù)在什么等級(jí)呢?
【oom_adj】
adj級(jí)別 | 值 | 解釋 |
---|---|---|
UNKNOWN_ADJ | 16 | 預(yù)留的最低級(jí)別宛官,一般對(duì)于緩存的進(jìn)程才有可能設(shè)置成這個(gè)級(jí)別 |
CACHED_APP_MAX_ADJ | 15 | 緩存進(jìn)程葫松,空進(jìn)程,在內(nèi)存不足的情況下就會(huì)優(yōu)先被kill |
CACHED_APP_MIN_ADJ | 9 | 緩存進(jìn)程底洗,也就是空進(jìn)程 |
SERVICE_B_ADJ | 8 | 不活躍的進(jìn)程 |
PREVIOUS_APP_ADJ | 7 | 切換進(jìn)程 |
HOME_APP_ADJ | 6 | 與Home交互的進(jìn)程 |
SERVICE_ADJ | 5 | 有Service的進(jìn)程 |
HEAVY_WEIGHT_APP_ADJ | 4 | 高權(quán)重進(jìn)程 |
BACKUP_APP_ADJ | 3 | 正在備份的進(jìn)程 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知的進(jìn)程腋么,比如那種播放音樂 |
VISIBLE_APP_ADJ | 1 | 可見進(jìn)程 |
FOREGROUND_APP_ADJ | 0 | 前臺(tái)進(jìn)程 |
PERSISTENT_SERVICE_ADJ | -11 | 重要進(jìn)程 |
PERSISTENT_PROC_ADJ | -12 | 核心進(jìn)程 |
SYSTEM_ADJ | -16 | 系統(tǒng)進(jìn)程 |
NATIVE_ADJ | -17 | 系統(tǒng)起的Native進(jìn)程 |
在終端輸入命令(過程就大概是 先連接設(shè)備;進(jìn)入shell亥揖;獲得進(jìn)程pid珊擂;查看值)
adb shell
cat /proc/{pid}/oom_adj
如圖所示了,某一個(gè)進(jìn)程等級(jí)在 -13 那么對(duì)照到上面的表费变,可以知道起碼是 系統(tǒng)進(jìn)程 級(jí)別摧扇,那么普通的GC基本是宰不到它的。所以敝科纾活的核心扛稽,就是為了提升這個(gè)等級(jí),類似的思想就有在AndroidManifest.xml的服務(wù)增加 優(yōu)先級(jí)priority 昼激,就盡量在這個(gè)閥值的范圍往上靠庇绽,希望別宰到我。
【不過】
也不是沒有辦法背壤В活瞧掺,你需要知道一下這幾個(gè)屬性的作用,但有個(gè)前提咯凡傅!要提升到系統(tǒng)級(jí)別辟狈,唯一有效而且安全的做法,就是有 系統(tǒng)簽名 。類似的哼转,每個(gè)品牌每個(gè)主板每個(gè)型號(hào)都有會(huì)它的簽名文件明未,就如平時(shí)我們自己生成的那種 jks、keystore 文件(只不過它們由 platform.x509.pem壹蔓、platform.pk8 這些來生成)趟妥,然后下面這些屬性就能起作用
android:sharedUserId="android.uid.system"
這個(gè)屬性使用應(yīng)該會(huì)比較少,官方對(duì)它的解釋:與其他應(yīng)用程序共享的Linux用戶ID的名稱佣蓉。默認(rèn)情況下披摄,Android為每個(gè)應(yīng)用程序分配了自己唯一的用戶ID。但是勇凭,如果將該屬性設(shè)置為兩個(gè)或多個(gè)應(yīng)用程序相同的值疚膊,它們將共享相同的ID——前提是它們的證書集相同。具有相同用戶ID的應(yīng)用程序可以訪問彼此的數(shù)據(jù)虾标,如果需要寓盗,還可以在相同的進(jìn)程中運(yùn)行。
換言之璧函,你得有系統(tǒng)簽名才能和他們平起平坐傀蚌。
persistent=true
這個(gè)在保活文章出現(xiàn)的頻率就比較高了蘸吓,但是卻不怎么起作用喳张,再來看看官方解釋:應(yīng)用程序是否應(yīng)該一直運(yùn)行—如果應(yīng)該,則為“true”;如果不應(yīng)該美澳,則為“false”。默認(rèn)值為“false”摸航。應(yīng)用程序通常不應(yīng)設(shè)置此標(biāo)志;持久性模式僅適用于某些系統(tǒng)應(yīng)用程序制跟。
通常情況下,我們的包都會(huì)安裝到 data/data/ 目錄下酱虎,而 persistent 需要 system/app 配套使用才會(huì)起效果的雨膨,也就是說應(yīng)用本身就已經(jīng)是系統(tǒng)應(yīng)用,那么這個(gè)屬性才生效读串,才會(huì)在系統(tǒng)啟動(dòng)的時(shí)候拉起有該屬性的應(yīng)用聊记,并且被殺死后能夠重啟應(yīng)用。
那我們?cè)趺打?yàn)證自己的屬性有沒有生效呢恢暖?
adb shell dumpsys meminfo
看一下自己的應(yīng)用包名有沒有出現(xiàn)在 Persistent 列表就行了,按照網(wǎng)上那些苯芪妫活方式舆床,通常只會(huì)出現(xiàn)在 A Services 、Visible、Foreground 這些列表內(nèi)挨队。
【總結(jié)】
大家對(duì)服務(wù)為啥保不活谷暮,有了個(gè)整體概念,那么概括成一句話應(yīng)該就是:GC可能會(huì)殺死等級(jí) oom_adj 不太重要的服務(wù)盛垦,而我們所做的一切都是為了提升這個(gè)等級(jí)的值湿弦,讓它不那么輕易被殺死。