“一文讀懂”系列:AMS是如何動態(tài)管理進(jìn)程的?

前言

前面一篇文章介紹了關(guān)于WMS在整個Android體系中的作用,主要可以劃分為四類職責(zé)1.窗口管理 2.窗口動畫 3.Surface管理 4.輸入事件中轉(zhuǎn)站。

如果把WMS比作古代將軍,那么這四類職責(zé)就是將軍手下幾元大將,而AMS作為Android整個體系的統(tǒng)籌者,理所當(dāng)然的就是古代的皇帝。

而今天要講的是Android體系中比較重要的一個概念:AMS進(jìn)程管理

傳統(tǒng)的進(jìn)程是指程序執(zhí)行的載體,進(jìn)程退出也就意味著程序退出了,而在Android中,進(jìn)程的概念被弱化了,進(jìn)程成為一個運行組件的容器。如應(yīng)用中Service,即可以在宿主進(jìn)程中運行也可以在服務(wù)進(jìn)程中運行,服務(wù)進(jìn)程退出,只是某個Service的退出,并非應(yīng)用退出。

在Android中,谷歌將進(jìn)程的管理和調(diào)度封裝在了AMS中,應(yīng)用層無需關(guān)心進(jìn)程是如何工作的。

AMS對進(jìn)程的管理主要體現(xiàn)在兩個方面:

  • 1.進(jìn)程LRU列表動態(tài)更新:動態(tài)調(diào)整進(jìn)程在mLruProcesses列表的位置
  • 2.進(jìn)程優(yōu)先級動態(tài)調(diào)整:實際是調(diào)整進(jìn)程oom_adj的值。

這兩項調(diào)整和系統(tǒng)進(jìn)行自動回收有關(guān),當(dāng)內(nèi)存不足時,系統(tǒng)會關(guān)閉一些進(jìn)程來釋放內(nèi)存、

下面筆者就依據(jù)這兩方面來看下AMS是如何管理進(jìn)程的。

目錄

進(jìn)程LRU列表動態(tài)更新

如果你進(jìn)程看Android源碼,應(yīng)該會常??纯聪旅孢@個方法:updateLruProcessLocked。當(dāng)時可能只是了解有這么個方法做了個緩存進(jìn)程的事,但是具體是如何實現(xiàn)的并不知曉,總感覺看代碼少了點什么,下面我們會圍繞這個方法展開。

AMS中的updateLruProcessLocked實現(xiàn)了對進(jìn)程LRU列表動態(tài)更新: 在講解updateLruProcessLocked方法前,我們先來講解下mLruProcesses進(jìn)程列表在AMS中的模型。

LRU進(jìn)程列表數(shù)據(jù)結(jié)構(gòu)

AMS進(jìn)程的LRU列表mLruProcesses:

final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();

AMS啟動的每個進(jìn)程都會被添加到LRU列表中,這個LRU列表不是隨意排序的或者僅僅根據(jù)先后順序排序的,而是根據(jù)具體規(guī)則進(jìn)行計算,以及進(jìn)程的當(dāng)前狀態(tài)進(jìn)行改變的、 LRU列表中存儲的是一個個ProcessRecord,AMS中使用ProcessRecord來代表一個進(jìn)程、內(nèi)部存儲了一個進(jìn)程所有的信息。

LRU列表被分為3段:

  • 1.hasActivity:帶Activity的進(jìn)程
  • 2.hasService:帶Service的進(jìn)程
  • 3.other:其他進(jìn)程。

這三段使用兩個字段分割開:mLruProcessServiceStartmLruProcessActivityStart,分別表示hasActivity段的開始位置以及hasService段的開始位置。

大概模型如下:

每次優(yōu)先級較高的進(jìn)程,如帶前臺Activity的進(jìn)程就會優(yōu)先被放到尾部,所以進(jìn)程優(yōu)先級由頭到尾

有了上面這個模型基礎(chǔ),下面我們從源碼角度來看LRU列表就更輕松了。

關(guān)鍵方法詳解

AMS使用updateLruProcessLocked方法對進(jìn)程列表進(jìn)行更新操作。

updateLruProcessLocked()方法在ActivityStack類中有3處可能被調(diào)用

其中2處調(diào)用位置都處于ActivityStack類中的resumeTopActivityInnerLocked()方法

  • 1.pausing:通過home鍵返回或者back鍵退出一個Activity,此時進(jìn)程中不止一個Activity、

  • 2.resume:熱啟動Activity

    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        //省略。。
        if (pausing && !resumeWhilePausing) {
            if (next.app != null && next.app.thread != null) {
                mService.updateLruProcessLocked(next.app, true, null);
            }
        }
    
        //省略
        if (next.app != null && next.app.thread != null) {
            mService.updateLruProcessLocked(next.app, true, null);
            next.app.thread.scheduleResumeActivity(next.appToken....);
        }
        //省略
    
    }
    

1處位于destroyActivityLocked()方法:如按back鍵退出最后一個Activity的時候。

final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) {
    if (hadApp) {
        if (r.app.activities.isEmpty()) {
            mService.updateLruProcessLocked(r.app, false, null);
            mService.updateOomAdjLocked();
        }
    }
    r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,..;
}                   

下面具體來看下該方法:

final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
        ProcessRecord client) {
    //1.判斷該進(jìn)程是否存在Activity
    final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
            || app.treatLikeActivity;
    //2.判斷進(jìn)程是否存在Service
    final boolean hasService = false; // not impl yet. app.services.size() > 0;

    //3.給LRU的序列號+1
    mLruSeq++;
    //4.如果hasActivity為true
    if (hasActivity) {
        final int N = mLruProcesses.size();
        //如果當(dāng)前進(jìn)程有Activity且mLruProcesses最尾部的元素是當(dāng)前進(jìn)程,則什么都不用處理,直接退出
        if (N > 0 && mLruProcesses.get(N-1) == app) {
            if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top activity: " + app);
            return;
        }
    } else {
        //如果當(dāng)前進(jìn)程沒有Activity且在Other段的top元素是當(dāng)前進(jìn)程,則也不處理,直接退出。
        if (mLruProcessServiceStart > 0
                && mLruProcesses.get(mLruProcessServiceStart-1) == app) {
            if (DEBUG_LRU) Slog.d(TAG_LRU, "Not moving, already top other: " + app);
            return;
        }
    }
    //5.獲取當(dāng)前進(jìn)程在mLruProcesses中的索引
    int lrui = mLruProcesses.lastIndexOf(app);
    //6.如果是persistent永久進(jìn)程,且索引不為0,則直接退出不處理
    if (app.persistent && lrui >= 0) {

        return;
    }
    //7.索引大于等于0的情況下,對mLruProcessActivityStart和mLruProcessServiceStart進(jìn)行更改并刪除列表對應(yīng)的索引上的進(jìn)程
    if (lrui >= 0) {
        if (lrui < mLruProcessActivityStart) {
            mLruProcessActivityStart--;
        }
        if (lrui < mLruProcessServiceStart) {
            mLruProcessServiceStart--;
        }

        mLruProcesses.remove(lrui);
    }

    int nextIndex;

    if (hasActivity) {
        final int N = mLruProcesses.size();
        //8.如果hasActivity為true但是app.activities.size為0,其實就是1處的第二種判斷app.hasClientActivities為true,且mLruProcessActivityStart分割點沒超過列表進(jìn)程數(shù)
        if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
            //9.將進(jìn)程添加到mLruProcesses列表的倒數(shù)第二個位置,因為倒數(shù)第一個位置是提供給有Activity的進(jìn)程使用。切記帶索引的add方法只是插入不會覆蓋,被頂替的元素自動后移
            mLruProcesses.add(N - 1, app);

            final int uid = app.info.uid;
            //10.為了防止當(dāng)前進(jìn)程創(chuàng)建很多Client端的進(jìn)程,導(dǎo)致進(jìn)程被濫用,將當(dāng)前進(jìn)程的子進(jìn)程Client往重要性低處的列表排序,直到碰到不是當(dāng)前進(jìn)程的子進(jìn)程Client端為止。
            for (int i = N - 2; i > mLruProcessActivityStart; i--) {
                ProcessRecord subProc = mLruProcesses.get(i);
                if (subProc.info.uid == uid) {

                    if (mLruProcesses.get(i - 1).info.uid != uid) {
                        //交換i和i-1位置的進(jìn)程元素
                        ProcessRecord tmp = mLruProcesses.get(i);
                        mLruProcesses.set(i, mLruProcesses.get(i - 1));
                        mLruProcesses.set(i - 1, tmp);
                        i--;
                    }
                } else {
                    // A gap, we can stop here.
                    //如果出現(xiàn)一個uid不一致的退出for循環(huán)交換
                    break;
                }
            }
        } else {
            //11.對于有Activity的進(jìn)程,則直接將進(jìn)程添加到末尾。        
            mLruProcesses.add(app);
        }
        //設(shè)置nextIndex為mLruProcessServiceStart
        nextIndex = mLruProcessServiceStart;
    } else if (hasService) {
        //12.如果是有Service的進(jìn)程,則將進(jìn)程插入到hasService段的末尾,也就是hasActivity段的開頭位置
        mLruProcesses.add(mLruProcessActivityStart, app);
        //設(shè)置nextIndex為mLruProcessServiceStart
        nextIndex = mLruProcessServiceStart;
        //將mLruProcessActivityStart hasActivity的起始索引+1;
        mLruProcessActivityStart++;
    } else  {
        // Process not otherwise of interest, it goes to the top of the non-service area.
        int index = mLruProcessServiceStart;
        //方法的第三個參數(shù)client一般都為null,這里不進(jìn)入
        if (client != null) {
            //省略。。
        }
        //13.對于其他也沒Activity也沒Service的情況,則將進(jìn)程對象下添加到Other字段末尾:此時index = mLruProcessServiceStart,也就是Other字段的末尾。
        mLruProcesses.add(index, app);
        //插入的索引的前一個索引位置
        nextIndex = index-1;
        //mLruProcessActivityStart和mLruProcessServiceStart索引均向后移動1位。
        mLruProcessActivityStart++;
        mLruProcessServiceStart++;
    }

    //對于有Service和ContentProvider的情況,也需要將Service的進(jìn)程和ContentProvider的進(jìn)程對象也插入到列表中。
    for (int j=app.connections.size()-1; j>=0; j--) {
        ConnectionRecord cr = app.connections.valueAt(j);
        if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
                && cr.binding.service.app != null
                && cr.binding.service.app.lruSeq != mLruSeq
                && !cr.binding.service.app.persistent) {
            nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,
                    "service connection", cr, app);
        }
    }
    for (int j=app.conProviders.size()-1; j>=0; j--) {
        ContentProviderRecord cpr = app.conProviders.get(j).provider;
        if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) {
            nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
                    "provider reference", cpr, app);
        }
    }
}

方法每個步驟已經(jīng)在代碼中做了說明,如果你仔細(xì)對照前面說的模型去看,一定能看懂。 這里額外說明下兩點:

  • 1.對于永久性的進(jìn)程即設(shè)置了persistent標(biāo)志的進(jìn)程在列表中的位置不會更改
  • 2.mLruProcessActivityStart和mLruProcessServiceStart會隨著列表的改變而改變,而不是固定的。
  • 3.為了防止某些進(jìn)程自己又沒Activity,卻可能創(chuàng)建很多Client端的進(jìn)程,導(dǎo)致進(jìn)程被濫用的情況。會將當(dāng)前進(jìn)程的子進(jìn)程Client往重要性低處的列表排序,直到碰到不是當(dāng)前進(jìn)程的子進(jìn)程Client端為止。
  • 4.對于有Service和ContentProvider的情況,也需要將Service的進(jìn)程和ContentProvider的進(jìn)程對象也插入到LRU列表中。

看圖說話:

好了,關(guān)于進(jìn)程列表的動態(tài)更新就講到這里。下面我們來講解進(jìn)程優(yōu)先級動態(tài)調(diào)整。

進(jìn)程優(yōu)先級動態(tài)調(diào)整

AMS中的updateOomAdjLocked方法實現(xiàn)了進(jìn)程優(yōu)先級的動態(tài)更新。 在講解updateOomAdjLocked方法前,我們先來了解下與進(jìn)程相關(guān)的幾個重要概念。

進(jìn)程優(yōu)先級(OOM_ADJ)

OOM_ADJ定義在ProcessList.java文件,大概劃分為20個級。

ADJ級別 adjString 取值 解釋
UNKNOWN_ADJ 1001 預(yù)留的最低級別,一般對于緩存的進(jìn)程才有可能設(shè)置成這個級別
CACHED_APP_MAX_ADJ 999 不可見進(jìn)程的adj最大值,在內(nèi)存不足的情況下就會優(yōu)先被kill。
CACHED_APP_LMK_FIRST_ADJ 950 lowmem 查殺的最小等級
CACHED_APP_MIN_ADJ cch 900 不可見進(jìn)程的adj最小值,在內(nèi)存不足的情況下就會優(yōu)先被kill
SERVICE_B_ADJ svcb 800 非活躍進(jìn)程,B List中的Service(運行時間較長、使用可能性更小)
PREVIOUS_APP_ADJ prev 700 上一個App的進(jìn)程(上一個stopActivity的進(jìn)程/20s內(nèi)剛被使用的provider進(jìn)程)
HOME_APP_ADJ home 600 Home進(jìn)程
SERVICE_ADJ svc 500 服務(wù)進(jìn)程(Service process)
HEAVY_WEIGHT_APP_ADJ hvy 400 后臺的重量級進(jìn)程
BACKUP_APP_ADJ bkup 300 備份進(jìn)程
PERCEPTIBLE_LOW_APP_ADJ prcl 250 由系統(tǒng)(或其他應(yīng)用程序)綁定的進(jìn)程,它比服務(wù)更重要,但不易察覺(clientAdj<200通過BIND_NOT_PERCEPTIBLE bind)
PERCEPTIBLE_APP_ADJ prcp 200 可感知進(jìn)程,比如后臺音樂播放 (前臺服務(wù)/display an overlay UI/currently used for toasts/clientAdj<200通過BIND_NOT_VISIBLE bind)
VISIBLE_APP_ADJ(VISIBLE_APP_LAYER_MAX200-100-1) vis 100 可見進(jìn)程(Visible process) ,一般是100+當(dāng)前可見的layer數(shù):activity不在前臺,但是確實可見的或者正在運行遠(yuǎn)程動畫
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ 50 應(yīng)用有前臺服務(wù),從前臺切換到前臺service,且在15s內(nèi)到過前臺
FOREGROUND_APP_ADJ fg 0 前臺進(jìn)程(Foreground process):應(yīng)用本身就是在前臺或者正在接收處理廣播isReceivingBroadcastLocked或者服務(wù)執(zhí)行過程中
PERSISTENT_SERVICE_ADJ psvc -700 關(guān)聯(lián)著系統(tǒng)或persistent進(jìn)程(由startIsolatedProcess()方式啟動的進(jìn)程,或者是由system_server或者persistent進(jìn)程所綁定的服務(wù)進(jìn)程)
PERSISTENT_PROC_ADJ pers -800 系統(tǒng)persistent進(jìn)程,比如telephony(一般不會被殺,即使被殺或crash,立即重啟)
SYSTEM_ADJ sys -900 系統(tǒng)進(jìn)程(system_server進(jìn)程)
NATIVE_ADJ ntv -1000 native進(jìn)程(由init進(jìn)程fork出的進(jìn)程,并不受system管控)

獲取oom_adj:

adb shell ps|grep com.android.yuhb.test

adb shell cat /proc/21375/oom_adj

每個等級的進(jìn)程又有對應(yīng)的優(yōu)先級,使用oom_adj值來表示,進(jìn)程回收機(jī)制就是根據(jù)這個adj值來進(jìn)行的 前臺進(jìn)程adj值最低,代表進(jìn)程優(yōu)先級最高,空進(jìn)程adj值越高,最容易被kill,對于相等優(yōu)先級的進(jìn)程:使用的內(nèi)存越多越容易被殺死

進(jìn)程state級別(ProcState)

ProcState定義在ActivityManager.java文件,大概劃分為22類。用來表示當(dāng)前進(jìn)程的一組狀態(tài)

state級別 procStateString 取值 解釋
PROCESS_STATE_NONEXISTENT NONE 20 不存在的進(jìn)程
PROCESS_STATE_CACHED_EMPTY CEM 19 處于cached狀態(tài)的空進(jìn)程
PROCESS_STATE_CACHED_RECENT CRE 18 有activity在最近任務(wù)列表的cached進(jìn)程
PROCESS_STATE_CACHED_ACTIVITY_CLIENT CACC 17 進(jìn)程處于cached狀態(tài),且為另一個cached進(jìn)程(內(nèi)含Activity)的client進(jìn)程
PROCESS_STATE_CACHED_ACTIVITY CAC 16 進(jìn)程處于cached狀態(tài)(內(nèi)含Activity)
PROCESS_STATE_LAST_ACTIVITY LAST 15 后臺進(jìn)程(擁有上一次顯示的Activity)
PROCESS_STATE_HOME HOME 14 后臺進(jìn)程(擁有home Activity)
PROCESS_STATE_HEAVY_WEIGHT HVY 13 后臺進(jìn)程(但無法執(zhí)行restore,因此盡量避免kill該進(jìn)程)
PROCESS_STATE_TOP_SLEEPING TPSL 12 與PROCESS_STATE_TOP一樣,但此時設(shè)備正處于休眠狀態(tài)
PROCESS_STATE_RECEIVER RCVR 11 后臺進(jìn)程,且正在運行receiver
PROCESS_STATE_SERVICE SVC 10 后臺進(jìn)程,且正在運行service
PROCESS_STATE_BACKUP BKUP 9 后臺進(jìn)程,正在運行backup/restore操作
PROCESS_STATE_TRANSIENT_BACKGROUND TRNB 8 后臺進(jìn)程
PROCESS_STATE_IMPORTANT_BACKGROUND IMPB 7 對用戶很重要的進(jìn)程,用戶不可感知其存在
PROCESS_STATE_IMPORTANT_FOREGROUND IMPF 6 對用戶很重要的進(jìn)程,用戶可感知其存在
PROCESS_STATE_BOUND_FOREGROUND_SERVICE , BFGS 5 通過系統(tǒng)綁定擁有一個前臺Service
PROCESS_STATE_FOREGROUND_SERVICE FGS 4 擁有一個前臺Service
PROCESS_STATE_BOUND_TOP BTOP 3 綁定到top應(yīng)用的進(jìn)程
PROCESS_STATE_TOP TOP 2 擁有當(dāng)前用戶可見的top Activity
PROCESS_STATE_PERSISTENT_UI PERU 1 persistent系統(tǒng)進(jìn)程,并正在執(zhí)行UI操作
PROCESS_STATE_PERSISTENT PER 0 persistent系統(tǒng)進(jìn)程
PROCESS_STATE_UNKNOWN -1 UNKNOWN進(jìn)

進(jìn)程組schedGroup

用來表示當(dāng)前進(jìn)程所在的進(jìn)程調(diào)度組序列。

schedGroup 含義
SCHED_GROUP_BACKGROUN 0 后臺進(jìn)程組
SCHED_GROUP_RESTRICTED 1
SCHED_GROUP_DEFAULT 2 前臺進(jìn)程組
SCHED_GROUP_TOP_APP 3 TOP進(jìn)程組
SCHED_GROUP_TOP_APP_BOUND 4 TOP進(jìn)程組

LMK機(jī)制

LMK 全稱 Low Memory Killer`。

在Android中,即使當(dāng)用戶退出應(yīng)用程序后,應(yīng)用進(jìn)程也還會存在內(nèi)存中,方便下次可以快速進(jìn)入應(yīng)用而不需要重新創(chuàng)建進(jìn)程。 這樣帶來的直接影響就是由于進(jìn)程數(shù)量越來越多,系統(tǒng)內(nèi)存會越來越少,這個時候就需要殺死一部分進(jìn)程來緩解內(nèi)存壓力。 至于哪些進(jìn)程會被殺死,這個時候就需要用到Low Memory Killer機(jī)制來進(jìn)行判定。

Android的Low Memory Killer基于Linux的OOM機(jī)制, 在Linux中,內(nèi)存是以頁面為單位分配的,當(dāng)申請頁面分配時如果內(nèi)存不足會通過以下流程選擇bad進(jìn)程來殺掉從而釋放內(nèi)存

alloc_pages -> out_of_memory() -> select_bad_process() -> badness()

LMK驅(qū)動層在用戶空間指定了一組內(nèi)存臨界值及與之一一對應(yīng)的一組oom_adj值, 當(dāng)系統(tǒng)剩余內(nèi)存位于內(nèi)存臨界值中的一個范圍內(nèi)時,如果一個進(jìn)程的oom_adj值大于或等于這個臨界值對應(yīng)的oom_adj值就會被殺掉。

使用命令:cat /sys/module/lowmemorykiller/parameters/minfree來查看某個手機(jī)的內(nèi)存閾值

18432,23040,27648,32256,36864,46080

注意這些數(shù)字的單位是page. 1 page = 4 kb.上面的六個數(shù)字對應(yīng)的就是(MB): 72,90,108,126,144,180 如數(shù)180代表內(nèi)存低于180M時會清除優(yōu)先級最低的空進(jìn)程。

LMK還維護(hù)著一個管理系統(tǒng)中所有進(jìn)程及其adj信息的雙向鏈表數(shù)組,這個雙向鏈表數(shù)組的每一個元素都是一個雙向鏈表,一個數(shù)組元素中的雙向鏈表里面的元素,都是adj相同的進(jìn)程。 在系統(tǒng)可用內(nèi)存較低時,就會選擇性殺死進(jìn)程的策略。防止內(nèi)存過低影響系統(tǒng)運行。 LMK殺死進(jìn)程的兩個指標(biāo): 1.oom_adj 2.內(nèi)存占用大小

而AMS通過四大組件的運行狀態(tài)更新這些組件相關(guān)聯(lián)的進(jìn)程的oom_adj(包括adj,proc_state,schedule_group等值), AMS計算好每個進(jìn)程的oom_adj,通過socket向lmkd服務(wù)發(fā)送請求,讓lmkd去更新進(jìn)程的優(yōu)先級,lmkd收到請求后,會通過/proc文件系統(tǒng)去更新內(nèi)核中的進(jìn)程優(yōu)先級這樣AMS就可以間接通過LMK實現(xiàn)對進(jìn)程的動態(tài)管理。

LMKD與AMS交互圖:

有了上面的基礎(chǔ),我們再來具體看下updateOomAdjLocked是如何進(jìn)行動態(tài)更新adj的。

6.關(guān)鍵方法詳解

前面說過,當(dāng)AMS需要更新進(jìn)程的優(yōu)先級時,就會調(diào)用它的updateOomAdjLocked方法,這里只提取方法的updateOomAdjLocked的一些核心代碼:

final void updateOomAdjLocked() {
    //省略。。。
    for (int i=N-1; i>=0; i--) {
        ProcessRecord app = mLruProcesses.get(i);
        if (!app.killedByAm && app.thread != null) {
            app.procStateChanged = false;
            computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
            //...
            applyOomAdjLocked(app, true, now, nowElapsed);
            //...
        }
    }

}

可以看到updateOomAdjLocked內(nèi)部主要是對LUR進(jìn)程列表中的每個進(jìn)程調(diào)用computeOomAdjLocked以及applyOomAdjLocked處理

核心方法:computeOomAdjLocked以及applyOomAdjLocked

  • 1.computeOomAdjLocked:計算adj,返回計算后RawAdj值
  • 2.applyOomAdjLocked:將計算后的adj寫入lmkd,當(dāng)需要殺掉目標(biāo)進(jìn)程則返回false;否則返回true。

computeOomAdjLocked:

該方法會傳入需要更新adj的進(jìn)程描述符ProcessRecord,然后根據(jù)參數(shù)計算出當(dāng)前進(jìn)程甚至關(guān)聯(lián)客戶端進(jìn)程的優(yōu)先級,進(jìn)程狀態(tài),進(jìn)程組等信息。

由于這個方法較長,這里列出代碼流程。

  • 1.通過mAdjSeq字段判斷此輪更新是否已經(jīng)計算過adj,是的話直接返回當(dāng)前app.curRawAdj
  • 2.判斷進(jìn)程的客戶端線程是否存在,不存在,則:將adj設(shè)置為CACHED_APP_MAX_ADJ。
  • 3.判斷是否是前臺進(jìn)程,如果不是:則根據(jù)TOP_APP,app.hasTopUi,activitiesSize,systemNoUi等參數(shù)計算adj。
  • 4.前臺進(jìn)程繼續(xù)往下,初始化一些前臺進(jìn)程相關(guān)的默認(rèn)值,后續(xù)再根據(jù)具體情況細(xì)化。
  • 5.根據(jù)是否為TOP_APP,是否有正在接受的動畫,是否有正在執(zhí)行的服務(wù),是否有正在運行的Activity以及Activity的狀態(tài)等對adj等參數(shù)賦值。
  • 6.對可見進(jìn)程或者擁有可感知的前臺服務(wù)或者后臺服務(wù)等參數(shù)設(shè)置adj
  • 7.對后臺進(jìn)程設(shè)置優(yōu)先級
  • 8.遍歷在進(jìn)程上運行的Service,根據(jù)Service的狀態(tài)進(jìn)一步更新adj等值。
  • 9.同Service。遍歷進(jìn)程上的ContentProvider,根據(jù)ContentProvider的狀態(tài)進(jìn)一步更新adj等值。
  • 10.根據(jù)cache進(jìn)程運行狀態(tài),細(xì)分出cache進(jìn)程還有empty進(jìn)程。
  • 11.將計算好的adj等值賦值給對應(yīng)的進(jìn)程屬性

代碼就不列出來了,筆者根據(jù)代碼,畫了個流程圖,方便大家查看,感興趣的可以根據(jù)這個圖自行去閱讀源碼。

applyOomAdjLocked:

這個方法主要有三個作用:

  • 1.設(shè)置進(jìn)程優(yōu)先級:將前面計算好的curAdj傳遞給LMKD服務(wù)
  • 2.設(shè)置進(jìn)程狀態(tài):將curProcState線程狀態(tài)回傳給應(yīng)用進(jìn)程ApplicationThread
  • 3.設(shè)置進(jìn)程的調(diào)度策略:將schedGroup設(shè)置為對應(yīng)的進(jìn)程調(diào)度組。
1.設(shè)置進(jìn)程優(yōu)先級

在applyOomAdjLocked方法中比較重要的一段代碼:

if (app.curAdj != app.setAdj) {
    ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
    if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
            "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
            + app.adjType);
    app.setAdj = app.curAdj;
    app.verifiedAdj = ProcessList.INVALID_ADJ;
}

繼續(xù)看ProcessList的setOomAdj方法:

public static final void setOomAdj(int pid, int uid, int amt) {
    if (amt == UNKNOWN_ADJ)
        return;
    long start = SystemClock.elapsedRealtime();
    ByteBuffer buf = ByteBuffer.allocate(4 * 4);
    buf.putInt(LMK_PROCPRIO);
    buf.putInt(pid);
    buf.putInt(uid);
    buf.putInt(amt);
    writeLmkd(buf);
    long now = SystemClock.elapsedRealtime();
    if ((now-start) > 250) {
        Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid
                + " = " + amt);
    }
}
private static void writeLmkd(ByteBuffer buf) {

    for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
                if (openLmkdSocket() == false) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ie) {
                    }
                    continue;
                }
        }

        try {
            sLmkdOutputStream.write(buf.array(), 0, buf.position());
            return;
        } catch (IOException ex) {
            Slog.w(TAG, "Error writing to lowmemorykiller socket");

            try {
                sLmkdSocket.close();
            } catch (IOException ex2) {
            }

            sLmkdSocket = null;
        }
    }
}

private static boolean openLmkdSocket() {
    try {
        sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
        sLmkdSocket.connect(
            new LocalSocketAddress("lmkd",
                    LocalSocketAddress.Namespace.RESERVED));
        sLmkdOutputStream = sLmkdSocket.getOutputStream();
    } catch (IOException ex) {
        Slog.w(TAG, "lowmemorykiller daemon socket open failed");
        sLmkdSocket = null;
        return false;
    }
    return true;
}

可以看到最終將adj,pid,uid寫入名為lmkd的Socket通道中。之后的進(jìn)程adj更新就是由lmkd來負(fù)責(zé)了。 lmkd根據(jù)傳入的參數(shù),去Proc文件系統(tǒng)中更新進(jìn)程優(yōu)先級信息。

2.設(shè)置進(jìn)程狀態(tài)

代碼片段:

if (app.repProcState != app.curProcState) {
    app.repProcState = app.curProcState;
    if (app.thread != null) {
        try {
            app.thread.setProcessState(app.repProcState);
        } catch (RemoteException e) {
        }
    }
}

這里調(diào)用了應(yīng)用進(jìn)程的ApplicationThread的setProcessState方法:

public void setProcessState(int state) {
    updateProcessState(state, true);
}

public void updateProcessState(int processState, boolean fromIpc) {
    synchronized (this) {
        if (mLastProcessState != processState) {
            mLastProcessState = processState;
            // Update Dalvik state based on ActivityManager.PROCESS_STATE_* constants.
            final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
            final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
            int dalvikProcessState = DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE;
            // TODO: Tune this since things like gmail sync are important background but not jank perceptible.
            if (processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
                dalvikProcessState = DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE;
            }
            VMRuntime.getRuntime().updateProcessState(dalvikProcessState);
            if (false) {
                Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
                        + (fromIpc ? " (from ipc": ""));
            }
        }
    }
}

ApplicationThread的setProcessState方法: 判斷當(dāng)前processState是否小余或等于ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND狀態(tài)值,將其改為虛擬機(jī)運行時環(huán)境可以識別的DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE值。 最終調(diào)用到了VMRuntime.getRuntime().updateProcessState(dalvikProcessState),將狀態(tài)設(shè)置到AndroidRuntime運行時環(huán)境中。這里其實就是告訴ART運行時當(dāng)前進(jìn)程的可感知能力, 用來切換虛擬機(jī)之間的GC算法,即到底是前臺進(jìn)程GC還是后臺進(jìn)程GC,前臺GC算法效率高,但是會產(chǎn)生碎片,后臺GC效率低,但是不會產(chǎn)生碎片。

具體可以參考下面這篇文章: [ART運行時Foreground GC和Background GC切換過程分析](羅生陽)

3.設(shè)置進(jìn)程調(diào)度策略
if (app.setSchedGroup != app.curSchedGroup) {
    int oldSchedGroup = app.setSchedGroup;
    app.setSchedGroup = app.curSchedGroup;

    switch (app.curSchedGroup) {
        case ProcessList.SCHED_GROUP_BACKGROUND:
            processGroup = THREAD_GROUP_BG_NONINTERACTIVE;
            break;
        case ProcessList.SCHED_GROUP_TOP_APP:
        case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
            processGroup = THREAD_GROUP_TOP_APP;
            break;
        default:
            processGroup = THREAD_GROUP_DEFAULT;
            break;
    }
    long oldId = Binder.clearCallingIdentity();
    try {
        Process.setProcessGroup(app.pid, processGroup);    //1 
        if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
            // do nothing if we already switched to RT
            if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
                mVrController.onTopProcChangedLocked(app);
                if (mUseFifoUiScheduling) {
                    //...
                } else {
                    // Boost priority for top app UI and render threads
                    setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);//2
                    if (app.renderThreadTid != 0) {
                        try {
                            setThreadPriority(app.renderThreadTid,
                                    TOP_APP_PRIORITY_BOOST);
                        } catch (IllegalArgumentException e) {
                            // thread died, ignore
                        }
                    }
                }
            }
        } 
    } catch (Exception e) {                  
    } 
}

這段代碼主要做了兩件事情:

  • 1.調(diào)用Process.setProcessGroup(int pid, int group)去設(shè)置進(jìn)程調(diào)度策略,原理就是: 利用linux的cgroup機(jī)制,根據(jù)進(jìn)程狀態(tài)將進(jìn)程放入預(yù)先設(shè)定的cgroup分組中,分組中包含了對cpu使用率、cpuset、cpu調(diào)頻等子資源的配置,以滿足特定狀態(tài)進(jìn)程對系統(tǒng)資源的需求。
  • 2.對schedGroup在某前臺和后臺之間切換時,調(diào)用setThreadPriority方法,切換主線程以及繪制線程的優(yōu)先級,以提高用戶的響應(yīng)速度

總結(jié)

這篇文章主要講解了關(guān)于Android系統(tǒng)中常見的進(jìn)程管理相關(guān)的知識點: 其中對AMS中兩個比較常見的方法:updateLruProcessLocked以及updateOomAdjLocked做了詳細(xì)介紹。

作為應(yīng)用開發(fā)可能我們平時用不到這些,但是在做一些性能優(yōu)化,進(jìn)程?;?/code>的操作時,這些儲備知識卻是必備的。一些高階用法,需要你去了解更深層次的東西,而不僅局限于表面

希望這篇文章對你有幫助。如果你想了解更多關(guān)于Framework的知識請關(guān)注我、

作者:小余的自習(xí)室
鏈接:https://juejin.cn/post/7174713775944138809

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容