日常開發(fā)中經(jīng)常會(huì)出現(xiàn)這樣的情景宵呛,測(cè)試跟開發(fā)唇槍舌戰(zhàn)“為什么從其他app返回,頁(yè)面內(nèi)容要重新加載呢夕凝,這肯定是個(gè)bug”宝穗,“Activity被系統(tǒng)回收了,不是bug”码秉;“為什么切換到后臺(tái)逮矛,這個(gè)app心跳不在線了呢,這肯定也是bug”转砖,“這是后臺(tái)進(jìn)程被系統(tǒng)回收了须鼎,算什么bug”。內(nèi)存回收是在Android開發(fā)中經(jīng)常接觸到的概念府蔗,但是你真的了解內(nèi)存回收嗎晋控?這篇我們就來簡(jiǎn)單探討下Android的內(nèi)存回收機(jī)制。
Android內(nèi)存回收機(jī)制
Addroid系統(tǒng)在設(shè)計(jì)時(shí)處于用戶體驗(yàn)和性能優(yōu)化的角度礁竞,設(shè)計(jì)了LMK機(jī)制:Low Memory Killer
簡(jiǎn)稱LMK,用于處理內(nèi)存回收調(diào)度糖荒。
簡(jiǎn)單來說就是,當(dāng)app切換到后臺(tái)時(shí)模捂,為了能在用戶再次打開app時(shí),及時(shí)進(jìn)行響應(yīng)加快app打開時(shí)間蜘矢,Android系統(tǒng)對(duì)于切換到后臺(tái)的app狂男,并不會(huì)立即殺死回收,而是在保證系統(tǒng)資源足夠的情況下品腹,盡可能的提升app進(jìn)程在系統(tǒng)內(nèi)存中的留存時(shí)間岖食,只有當(dāng)系統(tǒng)內(nèi)存資源不足時(shí),按照一定的優(yōu)先級(jí)將app進(jìn)程回收舞吭,這套機(jī)制就是LMK泡垃。
Android進(jìn)程分類
Android系統(tǒng)根據(jù)app當(dāng)前狀態(tài)和組件運(yùn)行聲明周期,按照進(jìn)程重要程度羡鸥,將app進(jìn)程進(jìn)行了分類蔑穴。
在Android 5.0之前,系統(tǒng)將app進(jìn)程分為了5類惧浴,分別是:
-
前臺(tái)進(jìn)程——
IMPORTANCE_FOREGROUND
- 簡(jiǎn)單來說:當(dāng)前用戶正在操作的進(jìn)程存和。
- 比如一下任意一個(gè),都會(huì)被認(rèn)為是前臺(tái)進(jìn)程:
- 它正在用戶的互動(dòng)屏幕上運(yùn)行一個(gè)
Activity
(其onResume()
方法已被調(diào)用)。 - 它有一個(gè)
BroadcastReceiver
目前正在運(yùn)行(其BroadcastReceiver.onReceive()
方法正在執(zhí)行)捐腿。 - 它有一個(gè)
Service
目前正在執(zhí)行其某個(gè)回調(diào)(Service.onCreate()
纵朋、Service.onStart()
或Service.onDestroy()
)中的代碼。
- 它正在用戶的互動(dòng)屏幕上運(yùn)行一個(gè)
-
可見進(jìn)程——
IMPORTANCE_VISIBLE
- 正在進(jìn)行用戶當(dāng)前知曉的任務(wù)茄袖,也就是說能夠被用戶感知到的進(jìn)程操软。
- 比如:
- 它正在運(yùn)行的
Activity
在屏幕上對(duì)用戶可見,但不在前臺(tái)(其onPause()
方法已被調(diào)用)宪祥。舉例來說寺鸥,如果前臺(tái) Activity 顯示為一個(gè)對(duì)話框,而這個(gè)對(duì)話框允許在其后面看到上一個(gè) Activity品山,則可能會(huì)出現(xiàn)這種情況胆建。 - 它有一個(gè)
Service
正在通過Service.startForeground()
(要求系統(tǒng)將該服務(wù)視為用戶知曉或基本上對(duì)用戶可見的服務(wù))作為前臺(tái)服務(wù)運(yùn)行。 - 系統(tǒng)正在使用其托管的服務(wù)實(shí)現(xiàn)用戶知曉的特定功能肘交,例如動(dòng)態(tài)壁紙笆载、輸入法服務(wù)等。
- 它正在運(yùn)行的
-
服務(wù)進(jìn)程——
IMPORTANCE_SERVICE
- 含一個(gè)已使用
startService()
方法啟動(dòng)的Service
涯呻。 -
注意:
- 這里說的服務(wù)進(jìn)程是指Service是單獨(dú)的進(jìn)程凉驻,此進(jìn)程中沒有啟動(dòng)過Activity。
- 并且在30分鐘內(nèi)活躍過的服務(wù)進(jìn)程复罐。
- 含一個(gè)已使用
-
后臺(tái)進(jìn)程——
IMPORTANCE_BACKGROUND
- app切換到后臺(tái)涝登,app通常包含用戶當(dāng)前不可見的一個(gè)或多個(gè)
Activity
實(shí)例(onStop()
方法已被調(diào)用并返回)。
- app切換到后臺(tái)涝登,app通常包含用戶當(dāng)前不可見的一個(gè)或多個(gè)
- 空白進(jìn)程——
IMPORTANCE_EMPTY
當(dāng)然在5.0之后的Android系統(tǒng)中效诅,對(duì)進(jìn)程分類再次進(jìn)行的細(xì)化胀滚,比如:
- Android 5.0,新增了
IMPORTANCE_PERCEPTIBLE
IMPORTANCE_CANT_SAVE_STATE
-
IMPORTANCE_CACHED
- 等同于
IMPORTANCE_BACKGROUND
- 等同于
- Android 11.0乱投,新增到了9種
-
IMPORTANCE_TOP_SLEEPING
- 前臺(tái)app咽笼,但是系統(tǒng)進(jìn)入的休眠,此app進(jìn)程戚炫。
-
IMPORTANCE_FOREGROUND_SERVICE
- 將
Service.startForeground()
前臺(tái)服務(wù)這種情況進(jìn)行了拆分剑刑,單列。
- 將
-
這里就不詳細(xì)介紹了双肤,可以通過源碼:android.app.ActivityManager.RunningAppProcessInfo
查看詳細(xì)的定義和注釋介紹施掏。
ADJ
剛開始提到了當(dāng)系統(tǒng)內(nèi)存資源不足時(shí),系統(tǒng)會(huì)按照一定的優(yōu)先級(jí)將app進(jìn)程回收茅糜,這個(gè)優(yōu)先級(jí)就是adj七芭。
App進(jìn)程啟動(dòng)時(shí),Android系統(tǒng)會(huì)為其分配一個(gè)單獨(dú)的adj限匣,adj的取值會(huì)隨著進(jìn)程的狀態(tài)和其組件的生命周期動(dòng)態(tài)變化抖苦。
查看系統(tǒng)adj
Android維護(hù)了一套adj值與可用內(nèi)存的對(duì)應(yīng)關(guān)系毁菱,他們是一一對(duì)應(yīng)的,當(dāng)可用內(nèi)存達(dá)到閾值時(shí)锌历,會(huì)將對(duì)應(yīng)adj的進(jìn)行進(jìn)行回收贮庞,釋放資源。
minfree和adj文件是描述系統(tǒng)可用內(nèi)存與對(duì)應(yīng)進(jìn)程優(yōu)先級(jí)的關(guān)系究西,分成多個(gè)等級(jí)兩個(gè)文件值是一一對(duì)應(yīng)的窗慎。
aosp:/sys/module/lowmemorykiller/parameters # cat minfree
18432,23040,27648,32256,36864,46080
aosp:/sys/module/lowmemorykiller/parameters # cat adj
0,100,200,300,900,906
通過查看sys/module/lowmemorykiller/parameters/
下的minfree
和adj
文件,可用獲得內(nèi)存閾值與adj對(duì)應(yīng)關(guān)系卤材。以上面數(shù)據(jù)為例:
注意:menfree值單位是page遮斥, 1page = 4K。
當(dāng)系統(tǒng)可用內(nèi)存小于46080 * 4 / 1024 = 180(M)
時(shí)扇丛,系統(tǒng)會(huì)將adj大于906的進(jìn)程回收术吗,從而釋放內(nèi)存,同理帆精,當(dāng)系統(tǒng)可用內(nèi)存小于18432 * 4 / 1024 = 72 M
時(shí)较屿,系統(tǒng)會(huì)將adj大于0的進(jìn)程回收。
應(yīng)用進(jìn)程adj
App進(jìn)程啟動(dòng)時(shí)卓练,Android系統(tǒng)會(huì)為其分配一個(gè)單獨(dú)的adj隘蝎,進(jìn)程adj的大小也可以通過adb命令進(jìn)行查看。
// 查看應(yīng)用進(jìn)程號(hào)
aosp:/ # ps |grep com.zhong.event
u0_a45 2104 1060 1153372 92444 0 c7f29c02 S com.zhong.event
// 查看進(jìn)程adj值
aosp:/ # cat /proc/2104/oom_score_adj
// com.zhong.event為前臺(tái)應(yīng)用襟企,adb = 0
0
進(jìn)程adj值嘱么,可以通過查看文件/proc/進(jìn)程號(hào)/oom_score_adj
獲得。
我們按Home鍵顽悼,將應(yīng)用切換到后臺(tái)曼振,再次查看其adj值:
aosp:/ # cat /proc/2104/oom_score_adj
700
手動(dòng)kill掉進(jìn)程后再次查看:
aosp:/ # cat /proc/2104/oom_score_adj
sh: cat: /proc/2104/oom_score_adj: No such file or directory
1|aosp:/ #
可以發(fā)現(xiàn),進(jìn)程的adj值是動(dòng)態(tài)變化的表蝙,且只有當(dāng)應(yīng)用進(jìn)程存活時(shí)拴测,系統(tǒng)才會(huì)為期分配adj。
注意:adj取值根據(jù)系統(tǒng)版本不同府蛇,硬件配置不同,會(huì)有差異屿愚,但是關(guān)系是一樣的汇跨。
adj級(jí)別
adj值是動(dòng)態(tài)變化的,系統(tǒng)會(huì)根據(jù)當(dāng)前app狀態(tài)及四大組件生命周期變化等因素妆距,動(dòng)態(tài)改變adj值穷遂,常見級(jí)別如下:
以下類型的adj取值,同樣會(huì)因?yàn)椴煌珹ndroid版本不同娱据,硬件配置不同蚪黑,甚至不同廠家等原因會(huì)有所差異。
ADJ級(jí)別 | 取值 | 含義 |
---|---|---|
NATIVE_ADJ | -1000 | native進(jìn)程 |
SYSTEM_ADJ | -900 | 僅指system_server進(jìn)程 |
PERSISTENT_PROC_ADJ | -800 | 系統(tǒng)persistent進(jìn)程 |
PERSISTENT_SERVICE_ADJ | -700 | 關(guān)聯(lián)著系統(tǒng)或persistent進(jìn)程 |
FOREGROUND_APP_ADJ | 0 | 前臺(tái)進(jìn)程,用戶目前執(zhí)行操作所需的進(jìn)程忌穿。 |
VISIBLE_APP_ADJ | 100 | 可見進(jìn)程抒寂,正在進(jìn)行用戶當(dāng)前知曉的任務(wù), |
PERCEPTIBLE_APP_ADJ | 200 | 可感知進(jìn)程掠剑,F(xiàn)oregroundService |
BACKUP_APP_ADJ | 300 | 備份進(jìn)程 |
HEAVY_WEIGHT_APP_ADJ | 400 | 重量級(jí)進(jìn)程 |
SERVICE_ADJ | 500 | 服務(wù)進(jìn)程屈芜,純Service進(jìn)程 |
HOME_APP_ADJ | 600 | Home進(jìn)程,launcher app |
PREVIOUS_APP_ADJ | 700 | 上一個(gè)進(jìn)程 |
SERVICE_B_ADJ | 800 | B List中的Service朴译,資源不足時(shí)Service進(jìn)程井佑,降級(jí) |
CACHED_APP_MIN_ADJ | 900 | 不可見進(jìn)程的adj最小值,activity finish |
CACHED_APP_MAX_ADJ | 906 | 不可見進(jìn)程的adj最大值 |
我們重點(diǎn)關(guān)注下表格中加粗部分就可以了眠寿,可以看到這其中好多類型與我們上文介紹的Android進(jìn)程分類有很明顯的對(duì)應(yīng)關(guān)系躬翁。
adj < 0
這些進(jìn)程基本都是系統(tǒng)處理的進(jìn)程,不用太多關(guān)注盯拱。唯一需要注意的是PERSISTENT_SERVICE_ADJ
這種情況盒发,當(dāng)app的AndroidManifest.xml中添加了android:persistent="true"
屬性,并且app必須是系統(tǒng)內(nèi)置app(必須內(nèi)置/system/app下)坟乾,滿足這個(gè)必要條件的基礎(chǔ)上迹辐,系統(tǒng)在開機(jī)時(shí)會(huì)自動(dòng)創(chuàng)建app進(jìn)程,并且進(jìn)程的優(yōu)先級(jí)是PERSISTENT_SERVICE_ADJ(-800)
甚侣。當(dāng)手動(dòng)殺死進(jìn)程時(shí)明吩,系統(tǒng)還會(huì)自動(dòng)重啟進(jìn)程,這在開發(fā)系統(tǒng)內(nèi)置應(yīng)用時(shí)殷费,處理進(jìn)程庇±螅活會(huì)很有效。
FOREGROUND_APP_ADJ详羡, adj = 0
對(duì)應(yīng)進(jìn)程類型的前臺(tái)進(jìn)程仍律,當(dāng)app滿足一下任意條件時(shí),就屬于adj = 0的情況:
- 它正在用戶的互動(dòng)屏幕上運(yùn)行一個(gè)
Activity
(其onResume()
方法已被調(diào)用)实柠。 - 它有一個(gè)
BroadcastReceiver
目前正在運(yùn)行(其BroadcastReceiver.onReceive()
方法正在執(zhí)行)水泉。 - 它有一個(gè)
Service
目前正在執(zhí)行其某個(gè)回調(diào)(Service.onCreate()
、Service.onStart()
或Service.onDestroy()
)中的代碼窒盐。
VISIBLE_APP_ADJ, adj = 100
對(duì)應(yīng)可見進(jìn)程草则,但是不包含Service.startForeground()
前臺(tái)服務(wù)情況:
- 它正在運(yùn)行的
Activity
在屏幕上對(duì)用戶可見,但不在前臺(tái)(其onPause()
方法已被調(diào)用)蟹漓。舉例來說炕横,如果前臺(tái) Activity 顯示為一個(gè)對(duì)話框,而這個(gè)對(duì)話框允許在其后面看到上一個(gè) Activity葡粒,則可能會(huì)出現(xiàn)這種情況份殿。
PERCEPTIBLE_APP_ADJ, adj = 200
使用Service.startForeground()
啟動(dòng)前臺(tái)服務(wù)進(jìn)程膜钓。
SERVICE_ADJ, adj = 500
對(duì)應(yīng)服務(wù)進(jìn)程:
- 這里說的服務(wù)進(jìn)程是指Service是單獨(dú)的進(jìn)程卿嘲,此進(jìn)程中沒有啟動(dòng)過Activity颂斜。
- 并且在30分鐘內(nèi)活躍過的服務(wù)進(jìn)程。
HOME_APP_ADJ, adj = 600
Home進(jìn)程腔寡,對(duì)應(yīng)launcher屬性的進(jìn)程焚鲜,類型為ACTIVITY_TYPE_HOME的應(yīng)用。
PREVIOUS_APP_ADJ放前, adj = 700
用戶使用的上一個(gè)進(jìn)程(有activity沒有finish)忿磅,通常是用戶兩個(gè)app間切換時(shí),被切換到后臺(tái)的應(yīng)用凭语。
這里“上一個(gè)”的概念葱她,不單只上一個(gè)使用app,如:用戶從A應(yīng)用切換到B應(yīng)用似扔,又從B應(yīng)用切換到C應(yīng)用吨些,這時(shí)A應(yīng)用可能是PREVIOUS_APP_ADJ
adj = 700。
CACHED_APP_MIN_ADJ炒辉, adj = 900
對(duì)應(yīng)后臺(tái)進(jìn)程豪墅,app切換到后臺(tái):
- app通常包含用戶當(dāng)前不可見的一個(gè)或多個(gè)
Activity
實(shí)例(onStop()
方法已被調(diào)用并返回)。 - 通常是所有Activity都被finish黔寇。
其他的adj類型偶器,大都是一些進(jìn)程的特殊狀態(tài),不再詳述了缝裤,這里在提一下SERVICE_B_ADJ
屏轰。
SERVICE_B_ADJ, adj = 800
SERVICE_B_ADJ
類型憋飞,可以理解成是SERVICE_ADJ
狀態(tài)的降級(jí)霎苗,當(dāng)出現(xiàn)一些特殊情況,如資源不足Service進(jìn)程可能會(huì)被降級(jí)成SERVICE_B_ADJ
榛做。
除此之外唁盏,對(duì)應(yīng)同級(jí)別,同樣adj值的進(jìn)程检眯,系統(tǒng)會(huì)優(yōu)先回收占用內(nèi)存高的app進(jìn)程升敲。
引用部分官方文檔內(nèi)容:
低內(nèi)存終止守護(hù)進(jìn)程
很多時(shí)候,
kswapd
不能為系統(tǒng)釋放足夠的內(nèi)存轰传。在這種情況下,系統(tǒng)會(huì)使用onTrimMemory()
通知應(yīng)用內(nèi)存不足瘪撇,應(yīng)該減少其分配量获茬。如果這還不夠港庄,內(nèi)核會(huì)開始終止進(jìn)程以釋放內(nèi)存。它會(huì)使用低內(nèi)存終止守護(hù)進(jìn)程 (LMK) 來執(zhí)行此操作恕曲。LMK 使用一個(gè)名為
oom_adj_score
的“內(nèi)存不足”分值來確定正在運(yùn)行的進(jìn)程的優(yōu)先級(jí)鹏氧,以此決定要終止的進(jìn)程。最高得分的進(jìn)程最先被終止佩谣。后臺(tái)應(yīng)用最先被終止把还,系統(tǒng)進(jìn)程最后被終止。下表列出了從高到低的 LMK 評(píng)分類別茸俭。評(píng)分最高的類別吊履,即第一行中的項(xiàng)目將最先被終止:以下是上表中各種類別的說明:
- 后臺(tái)應(yīng)用:之前運(yùn)行過且當(dāng)前不處于活動(dòng)狀態(tài)的應(yīng)用调鬓。LMK 將首先從具有最高
oom_adj_score
的應(yīng)用開始終止后臺(tái)應(yīng)用艇炎。- 上一個(gè)應(yīng)用:最近用過的后臺(tái)應(yīng)用。上一個(gè)應(yīng)用比后臺(tái)應(yīng)用具有更高的優(yōu)先級(jí)(得分更低)腾窝,因?yàn)橄啾饶硞€(gè)后臺(tái)應(yīng)用缀踪,用戶更有可能切換到上一個(gè)應(yīng)用。
- 主屏幕應(yīng)用:這是啟動(dòng)器應(yīng)用虹脯。終止該應(yīng)用會(huì)使壁紙消失驴娃。
- 服務(wù):服務(wù)由應(yīng)用啟動(dòng),可能包括同步或上傳到云端循集。
- 可覺察的應(yīng)用:用戶可通過某種方式察覺到的非前臺(tái)應(yīng)用唇敞,例如運(yùn)行一個(gè)顯示小界面的搜索進(jìn)程或聽音樂。
- 前臺(tái)應(yīng)用:當(dāng)前正在使用的應(yīng)用暇榴。終止前臺(tái)應(yīng)用看起來就像是應(yīng)用崩潰了厚棵,可能會(huì)向用戶提示設(shè)備出了問題。
- 持久性(服務(wù)):這些是設(shè)備的核心服務(wù)蔼紧,例如電話和 WLAN婆硬。
- 系統(tǒng):系統(tǒng)進(jìn)程。這些進(jìn)程被終止后奸例,手機(jī)可能看起來即將重新啟動(dòng)彬犯。
- 原生:系統(tǒng)使用的極低級(jí)別的進(jìn)程(例如,
kswapd
)查吊。設(shè)備制造商可以更改 LMK 的行為谐区。
更多關(guān)于ADJ詳細(xì)的介紹和相關(guān)介紹,可以參考大佬的文章:《解讀Android進(jìn)程優(yōu)先級(jí)ADJ算法》逻卖,講解的特別詳細(xì)了宋列,膜拜大佬。评也。
從進(jìn)程回收來看服務(wù)绷墩龋活
了解了Android進(jìn)程回收的機(jī)制和原理灭返,對(duì)后臺(tái)服務(wù)的保活也有一定的啟發(fā)坤邪,簡(jiǎn)單來說就是盡可能的提高服務(wù)進(jìn)程的adj級(jí)別:
- 特殊情況下可以通過
android:persistent="true"
屬性將進(jìn)程提升至PERSISTENT_SERVICE_ADJ(-800)
級(jí)別熙含,近乎無(wú)敵。當(dāng)然了條件也是非惩Х模苛刻的:app必須內(nèi)置為系統(tǒng)應(yīng)用怎静。 - 使用前臺(tái)服務(wù)
Service.startForeground()
,將進(jìn)程提升至PERCEPTIBLE_APP_ADJ(200)
。 - UI進(jìn)程與服務(wù)進(jìn)程分離黔衡,當(dāng)app切換到后臺(tái)蚓聘,未分離的Service進(jìn)程(app進(jìn)程)降級(jí)為
CACHED_APP_MIN_ADJ(900)
,而進(jìn)程分離的Service進(jìn)程,始終保持為SERVICE_ADJ(500)
员帮。 - 內(nèi)存優(yōu)化或粮,對(duì)應(yīng)同級(jí)別,同樣adj值的進(jìn)程捞高,系統(tǒng)會(huì)優(yōu)先回收占用內(nèi)存高的app進(jìn)程氯材。
小結(jié)
回到最開始的問題:Activity被系統(tǒng)回收了?只可能是因?yàn)檎麄€(gè)app進(jìn)程被系統(tǒng)回收嗎硝岗?其實(shí)并不是氢哮,我們知道Android系統(tǒng)為每個(gè)app進(jìn)程分配一個(gè)內(nèi)存上限,當(dāng)app使用內(nèi)存型檀,超過此上限時(shí)冗尤,就會(huì)發(fā)生OOM異常。針對(duì)此場(chǎng)景Android系統(tǒng)也制定了一些對(duì)Activity棧進(jìn)行回收的機(jī)制胀溺,簡(jiǎn)單來說:
對(duì)于單棧(TaskRecord)應(yīng)用裂七,在前臺(tái)的時(shí)候,所有界面都不會(huì)被回收仓坞,只有多棧情況下背零,系統(tǒng)才會(huì)回收不可見棧的Activity。
本文轉(zhuǎn)自 https://juejin.cn/post/7070764959898009607无埃,如有侵權(quán)徙瓶,請(qǐng)聯(lián)系刪除。