Android內存管理篇 - adj的概念與進程adj級別控制


本文主要介紹Android的lowmemorykiller的oom_adj的相關概念,以及根據一些案例來闡述了解oom_adj對於做Android應用開發的重要意義。

一、lowmeorykiller中進程的分類以及各類進程的adj值

        在Android的lowmemroykiller機制中,會對於所有進程進行分類,對於每一類別的進程會有其oom_adj值的取值范圍,oom_adj值越高則代表進程越不重要,在系統執行低殺操作時,會從oom_adj值越高的開始殺。系統lowmemeorykiller機制下對於進程的級別的以變量的形式定義在framework/base/core/java/com/android/server/am/ProcessList.java類中,可總結成下表:

 

 

再補充介紹一下:

1.AMS角度對於進程的分級       

上表帶分級只是從lowmemroykiller角度來分的,時用於lowmemeorykiller執行殺進程操作,但是從android的系統管理角度看,即是從AMS執行相關邏輯時,又有一套自己的分級機制,當然這兩套機制也有着很多互通的點。AMS角度的級別划分以變量的形式定義在framework/base/core/java/android/app/ActivityManager.java類中,以PROCESS_STATE開頭的變量。

2.沒有stopService其內含activity的后台進程

        這類進程從lowmemorykiller角度是划分為cached,因為如果這類進程往往占有較大的內存,這類含有activity的后台進程往往占有較大內存,所以即使這類進程包含了Service,lowmemorykiller的機制也會更加傾向於優先殺死這類進程。

        但是一般啟動了服務的進程往往是希望服務在后台能夠執行某些任務,這樣看是不希望這些服務因為進程被殺而過早的被終止的,那如何調和這種矛盾呢?正確的做法是,對於期望較長時間留在后台的服務,應該將服務運行在單獨的進程里,即是UI進程與Servie進程分離,這樣期望長時間留在后台的Serivce會存在與一個被lmk分類為Service 進程的服務而獲得較小的Adj值,而占有大量內存的UI進程則會分類為Cached進程,能夠在需要的時候更快地被回收。

        還有一點,這類進程雖然被lmk划分為cached進程,但是從ams角度是被划分為PROCESS_STATE_SERVICE這個類別的,即視為服務進程,在ams相關流程中也是以服務進程來執行相關邏輯的,此外在使用dumpsys meminfo查看所有進程時,這類進程也是被列在B service這個類別的。

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動作。 

 

二、如何查詢應用的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值

 

三、未控制好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進程能夠按照系統正常的回收流程在需要時被回收

 

 

四、總結一些經驗

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進行功能實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM