本文主要介紹Android的lowmemorykiller的oom_adj的相關概念,以及根據一些案例來闡述瞭解oom_adj對於作Android應用開發的重要意義。java
1、lowmeorykiller中進程的分類以及各種進程的adj值android
在Android的lowmemroykiller機制中,會對於全部進程進行分類,對於每一類別的進程會有其oom_adj值的取值範圍,oom_adj值越高則表明進程越不重要,在系統執行低殺操做時,會從oom_adj值越高的開始殺。系統lowmemeorykiller機制下對於進程的級別的以變量的形式定義在framework/base/core/java/com/android/server/am/ProcessList.java類中,可總結成下表:web
再補充介紹一下:數據庫
1.AMS角度對於進程的分級 app
上表帶分級只是從lowmemroykiller角度來分的,時用於lowmemeorykiller執行殺進程操做,可是從android的系統管理角度看,便是從AMS執行相關邏輯時,又有一套本身的分級機制,固然這兩套機制也有着不少互通的點。AMS角度的級別劃分以變量的形式定義在framework/base/core/java/android/app/ActivityManager.java類中,以PROCESS_STATE開頭的變量。框架
2.沒有stopService其內含activity的後臺進程ide
這類進程從lowmemorykiller角度是劃分爲cached,由於若是這類進程每每佔有較大的內存,這類含有activity的後臺進程每每佔有較大內存,因此即便這類進程包含了Service,lowmemorykiller的機制也會更加傾向於優先殺死這類進程。ui
可是通常啓動了服務的進程每每是但願服務在後臺可以執行某些任務,這樣看是不但願這些服務由於進程被殺而過早的被終止的,那如何調和這種矛盾呢?正確的作法是,對於指望較長時間留在後臺的服務,應該將服務運行在單獨的進程裏,便是UI進程與Servie進程分離,這樣指望長時間留在後臺的Serivce會存在與一個被lmk分類爲Service 進程的服務而得到較小的Adj值,而佔有大量內存的UI進程則會分類爲Cached進程,可以在須要的時候更快地被回收。this
還有一點,這類進程雖然被lmk劃分爲cached進程,可是從ams角度是被劃分爲PROCESS_STATE_SERVICE這個類別的,即視爲服務進程,在ams相關流程中也是以服務進程來執行相關邏輯的,此外在使用dumpsys meminfo查看全部進程時,這類進程也是被列在B service這個類別的。spa
3.A-Service與B-Service的劃分
全部啓動了服務的進程,且該服務所在的進程沒有顯示過UI,且該服務未執行startForeground(執行後會變爲perveptible服務)動做,那該進程則爲A-Service與B-Service中的一種。而後根據這類服務進程所處於Lru進程表中的位置,前1/3點服務爲A-Service,其他的則爲B-Service。
4.perceptible的標準
perceptible名爲可感知的進程,但並非說可以感知到進程就必定表示該進程屬於perveptible進程,好比播放音樂的進程活着狀態欄上有通知的進程,雖然可以感知到進程的存在,可是不表明進程必定時perceptible類別的進程。決定該進程是否屬於perceptible進程並未進程的可感知性,而是該進程的服務是否執行了startForeground動做。
2、如何查詢應用的adj級別
1.dumpsys meminfo
使用dumpsys meminfo命令時,會列出當前系統的全部進程,不一樣進程放入不一樣的分類,對應的分類名基本與lmk的分類一致。有一點不一樣的就是,退到後臺啓動了服務且顯示過UI的進程,在dumpsys meminfo命令中會歸爲b service一類,但從lmk角度分配的oom_adj值爲9~16的範圍,屬於cached一類
2.cat /proc/[PID]/oom_adj: 使用該命令會直接顯示出對應進程號的adj值
3、未控制好oom_adj的案例
1.ui進程啓動service的隱患
案例a:備份進程啓動一個服務開始執行備份,備份服務運行在ui進程(服務未調用startForeground())
隱患:備份服務通常須要較長時間,在用戶按Home鍵退出後臺後,備份進程會處於previous狀態,繼續使用手機其餘應用,會是使得備份進程處於cch-started-ui-services的狀態,便是啓動了服務而且包含ui的進程退到後臺狀態,此時進程的adj值處於9~16,隨着時間推移逐漸增大。若是在較長的備份過程當中,觸發了lowmemorykiller,很容易致使備份進程被殺掉,從而致使備份的失敗。
案例b:備份進程啓動一個服務開始執行備份,備份服務運行在ui進程(服務調用了startForeground())
隱患:這種狀況下備份進程會被劃分爲perceptible進程,基本上是不會被lowmemorykiller殺掉的,可是這也致使內存佔用較大的備份常駐了,從內存管理角度來將,備份進程的UI部分是並不指望他常駐的,而大量內存的常駐也容易致使lowmemorykiller的出現,從而致使系統進入內存較低的等級,而當系統處於內存較低等級時,會觸發系統回調全部進程進行進程回收動做,容易致使系統卡頓場景的出現。此外,調用了startForeground()會致使進程被系統斷定爲前景進程,這樣備份進程便會搶佔用戶操做手機時前臺應用的cpu資源,增長了卡頓場景出現的概率。
解決方法:將Service運行在獨立的進程,這樣應用退到後臺後,備份服務進程會處於A-Service中(逐漸掉落到B-Service),而B-Service進程通常也是很難被lowmemorykiller砍。該獨立是否要startForeground()?若是指望保證備份儘快到完成,即可以犧牲一些用戶在操做其餘應用時到用戶體驗,將服務推爲前景應用;對於不少須要保證功能的流暢運行的服務進程,例如音樂播放,錄音等,則須要將這類服務進程經過startForeground()設置爲前景進程,但前提仍是須要作到ui與Service分離。
2.使用線程解決耗時操做形成anr問題的隱患
案例:短信、郵件、或筆記本應用,在用戶按BACK鍵時存下草稿
public class MyActivity extents Activity { public void onPause(){ //存儲草稿 } }
問題(1):因爲存儲草稿定操做通常時保存到數據庫,某些狀況下可能會佔用較長時間,這裏就有可能致使anr的隱患
解決方案1:
public class MyActivity extents Activity { public void onPause(){ new Thread() { public void run() { //存儲草稿 } }.start() } }
問題(2):當用戶以back鍵離開應用時(以home鍵離開會處於previous狀態),應用退到後臺處於empty-cached狀態,內存不足時,可能會馬上殺。
解決方案2:若是線程有這問題,是否能夠用服務來完成存儲草稿的動做呢?
問題(3):若是用服務來存儲草稿,即將存儲草稿動做寫在onStartCommand中,因爲onStartCommmand操做依舊是執行在主線程的,因此在其中執行耗時操做時,依舊可能會致使ANR
最終解決方案:使用IntentService來執行保存草稿的動做
public class MyActivity extents Activity { public void onPause(){ ... startService(new Intent(this, MyIntentService.class)); ... } } public class MyIntentService extends IntentService { protected void onHandleIntent(Intent intent) { //保存草稿 } }
3.provider被binder致使的隱患
案例:systemui進程獲取手機中的手機管家應用提供的content provider,用於獲取當前應用相關信息
問題:管家應用的UID爲System,在Android機制中,System UID進程提供的provider一旦被訪問,即便訪問完成關掉provider後,鏈接依舊存在,全部管家應用因爲其provider持續被persistent進程咬住,因此管家應用便會長時間處於foreground級別的應用中,oom_adj爲0,致使管家應用佔用的大量內存很難被回收
解決方案:單獨使用一個進程提供provider,提供provider進程因爲佔用內存較小,因此即便沒法被回收也影響較小,這樣管家應用的UI進程可以按照系統正常的回收流程在須要時被回收
4、總結一些經驗
1.進程在啓動服務後,在事情作完後,必須呼叫stopService或stopSelf通知框架,避免事情作完了,服務進程依舊常駐內存
2.對於須要長時間停留在後臺的服務,且服務設置爲具備重啓特性時,須要作到ui與service分離,一避免佔用內存較大的ui進程的常駐
3.對於須要長時間停留在後臺的服務,且服務設置爲具備重啓特性時,不可長時間bind住其餘進程的service或provider,避免其餘進程常駐;
4.常駐性質的進程(oom_adj<=6),不可一直bind住其餘進程的服務或provider,用完必須立刻放掉
5.不可以使用SystemUID進程內的provider,在Android設計中,若針對System UID的進程使用provider,即便已關掉provider,但框架仍會保持provider connection
6.利用onStartCommand方法回傳值(START_STICKY,START_NOT_STICKY等)控制好服務的重啓屬性,在設計時充分考慮進程被lmk殺死的狀況
7.IntentService繼承自Service,針對CPU scheduling、工做排程等都有完整實現,建議多采用IntentnService進行功能實現