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

前言

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

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

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

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

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

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

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

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

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

目錄

image

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

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

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

AMS進程的LRU列表mLruProcesses:

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

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

LRU列表被分為3段:

  • 1.hasActivity:帶Activity的進程
  • 2.hasService:帶Service的進程
  • 3.other:其他進程。

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

大概模型如下:

image

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

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

關(guān)鍵方法詳解

AMS使用updateLruProcessLocked方法對進程列表進行更新操作昌犹。

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

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

  • 1.pausing:通過home鍵返回或者back鍵退出一個Activity,此時進程中不止一個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.判斷該進程是否存在Activity
    final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
            || app.treatLikeActivity;
    //2.判斷進程是否存在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)前進程有Activity且mLruProcesses最尾部的元素是當(dāng)前進程缚忧,則什么都不用處理,直接退出
        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)前進程沒有Activity且在Other段的top元素是當(dāng)前進程杈笔,則也不處理闪水,直接退出。
        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)前進程在mLruProcesses中的索引
    int lrui = mLruProcesses.lastIndexOf(app);
    //6.如果是persistent永久進程蒙具,且索引不為0球榆,則直接退出不處理
    if (app.persistent && lrui >= 0) {

        return;
    }
    //7.索引大于等于0的情況下,對mLruProcessActivityStart和mLruProcessServiceStart進行更改并刪除列表對應(yīng)的索引上的進程
    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分割點沒超過列表進程數(shù)
        if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
            //9.將進程添加到mLruProcesses列表的倒數(shù)第二個位置,因為倒數(shù)第一個位置是提供給有Activity的進程使用篱昔。切記帶索引的add方法只是插入不會覆蓋每强,被頂替的元素自動后移
            mLruProcesses.add(N - 1, app);

            final int uid = app.info.uid;
            //10.為了防止當(dāng)前進程創(chuàng)建很多Client端的進程,導(dǎo)致進程被濫用旱爆,將當(dāng)前進程的子進程Client往重要性低處的列表排序舀射,直到碰到不是當(dāng)前進程的子進程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位置的進程元素
                        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的進程怀伦,則直接將進程添加到末尾脆烟。        
            mLruProcesses.add(app);
        }
        //設(shè)置nextIndex為mLruProcessServiceStart
        nextIndex = mLruProcessServiceStart;
    } else if (hasService) {
        //12.如果是有Service的進程,則將進程插入到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,這里不進入
        if (client != null) {
            //省略桑孩。拜鹤。
        }
        //13.對于其他也沒Activity也沒Service的情況,則將進程對象下添加到Other字段末尾:此時index = mLruProcessServiceStart流椒,也就是Other字段的末尾敏簿。
        mLruProcesses.add(index, app);
        //插入的索引的前一個索引位置
        nextIndex = index-1;
        //mLruProcessActivityStart和mLruProcessServiceStart索引均向后移動1位。
        mLruProcessActivityStart++;
        mLruProcessServiceStart++;
    }

    //對于有Service和ContentProvider的情況,也需要將Service的進程和ContentProvider的進程對象也插入到列表中惯裕。
    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)在代碼中做了說明温数,如果你仔細對照前面說的模型去看,一定能看懂蜻势。
這里額外說明下兩點:

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

看圖說話:

image

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

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

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

進程優(yōu)先級(OOM_ADJ)

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

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

獲取oom_adj:

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

adb shell cat /proc/21375/oom_adj

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

進程state級別(ProcState)

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

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

進程組schedGroup

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

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

LMK機制

LMK 全稱 Low Memory Killer术健。

在Android中,即使當(dāng)用戶退出應(yīng)用程序后粘衬,應(yīng)用進程也還會存在內(nèi)存中荞估,方便下次可以快速進入應(yīng)用而不需要重新創(chuàng)建進程

這樣帶來的直接影響就是由于進程數(shù)量越來越多稚新,系統(tǒng)內(nèi)存會越來越少勘伺,這個時候就需要殺死一部分進程來緩解內(nèi)存壓力。至于哪些進程會被殺死褂删,這個時候就需要用到Low Memory Killer機制來進行判定飞醉。

Android的Low Memory Killer基于Linux的OOM機制:在Linux中,內(nèi)存是以頁面為單位分配的笤妙,當(dāng)申請頁面分配時如果內(nèi)存不足會通過以下流程選擇bad進程來殺掉從而釋放內(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)時,如果一個進程的oom_adj值大于或等于這個臨界值對應(yīng)的oom_adj值就會被殺掉蹲盘。

使用命令:cat /sys/module/lowmemorykiller/parameters/minfree來查看某個手機的內(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)先級最低的空進程股毫。

LMK還維護著一個管理系統(tǒng)中所有進程及其adj信息的雙向鏈表數(shù)組,這個雙向鏈表數(shù)組的每一個元素都是一個雙向鏈表召衔,一個數(shù)組元素中的雙向鏈表里面的元素铃诬,都是adj相同的進程

在系統(tǒng)可用內(nèi)存較低時,就會選擇性殺死進程的策略趣席。防止內(nèi)存過低影響系統(tǒng)運行兵志。

LMK殺死進程的兩個指標:
1.oom_adj 2.內(nèi)存占用大小

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

LMKD與AMS交互圖:

lmk.png

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

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

前面說過楼镐,當(dāng)AMS需要更新進程的優(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進程列表中的每個進程調(diào)用computeOomAdjLocked以及applyOomAdjLocked處理
核心方法:computeOomAdjLocked以及applyOomAdjLocked

  • 1.computeOomAdjLocked:計算adj秉宿,返回計算后RawAdj值
  • 2.applyOomAdjLocked:將計算后的adj寫入lmkd,當(dāng)需要殺掉目標進程則返回false屯碴;否則返回true蘸鲸。

computeOomAdjLocked:

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

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

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

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

image

applyOomAdjLocked:

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

  • 1.設(shè)置進程優(yōu)先級:將前面計算好的curAdj傳遞給LMKD服務(wù)
  • 2.設(shè)置進程狀態(tài):將curProcState線程狀態(tài)回傳給應(yīng)用進程ApplicationThread
  • 3.設(shè)置進程的調(diào)度策略:將schedGroup設(shè)置為對應(yīng)的進程調(diào)度組悼潭。
1.設(shè)置進程優(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通道中舰褪。之后的進程adj更新就是由lmkd來負責(zé)了皆疹。
lmkd根據(jù)傳入的參數(shù),去Proc文件系統(tǒng)中更新進程優(yōu)先級信息占拍。

2.設(shè)置進程狀態(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)用進程的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)值略就,將其改為虛擬機運行時環(huán)境可以識別的DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE值。
最終調(diào)用到了VMRuntime.getRuntime().updateProcessState(dalvikProcessState)晃酒,將狀態(tài)設(shè)置到AndroidRuntime運行時環(huán)境中表牢。這里其實就是告訴ART運行時當(dāng)前進程的可感知能力,
用來切換虛擬機之間的GC算法贝次,即到底是前臺進程GC還是后臺進程GC崔兴,前臺GC算法效率高,但是會產(chǎn)生碎片蛔翅,后臺GC效率低敲茄,但是不會產(chǎn)生碎片。

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

3.設(shè)置進程調(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è)置進程調(diào)度策略山析,原理就是:
    利用linux的cgroup機制堰燎,根據(jù)進程狀態(tài)將進程放入預(yù)先設(shè)定的cgroup分組中,分組中包含了對cpu使用率笋轨、cpuset爽待、cpu調(diào)頻等子資源的配置损同,以滿足特定狀態(tài)進程對系統(tǒng)資源的需求
  • 2.對schedGroup在某前臺和后臺之間切換時鸟款,調(diào)用setThreadPriority方法膏燃,切換主線程以及繪制線程的優(yōu)先級,以提高用戶的響應(yīng)速度何什。

總結(jié)

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

作為應(yīng)用開發(fā)可能我們平時用不到這些,但是在做一些性能優(yōu)化处渣,進程绷娣。活的操作時,這些儲備知識卻是必備的罐栈。一些高階用法黍衙,需要你去了解更深層次的東西,而不僅局限于表面

如果 本篇文章對你幫助荠诬,請幫忙關(guān)注下琅翻,點個贊,這是我持續(xù)創(chuàng)作的最大動力柑贞。

  • 筆者公眾號【小余的自習(xí)室
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末方椎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钧嘶,更是在濱河造成了極大的恐慌棠众,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件有决,死亡現(xiàn)場離奇詭異闸拿,居然都是意外死亡,警方通過查閱死者的電腦和手機书幕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門胸墙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人按咒,你說我怎么就攤上這事迟隅。” “怎么了励七?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵智袭,是天一觀的道長。 經(jīng)常有香客問我掠抬,道長吼野,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任两波,我火速辦了婚禮瞳步,結(jié)果婚禮上闷哆,老公的妹妹穿的比我還像新娘。我一直安慰自己单起,他們只是感情好抱怔,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嘀倒,像睡著了一般屈留。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上测蘑,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天灌危,我揣著相機與錄音,去河邊找鬼碳胳。 笑死勇蝙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挨约。 我是一名探鬼主播味混,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼烫罩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洽故,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贝攒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后时甚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體隘弊,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年荒适,在試婚紗的時候發(fā)現(xiàn)自己被綠了梨熙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡刀诬,死狀恐怖咽扇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情陕壹,我是刑警寧澤质欲,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站糠馆,受9級特大地震影響嘶伟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜又碌,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一九昧、第九天 我趴在偏房一處隱蔽的房頂上張望绊袋。 院中可真熱鬧,春花似錦铸鹰、人聲如沸癌别。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽规个。三九已至,卻和暖如春姓建,著一層夾襖步出監(jiān)牢的瞬間诞仓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工速兔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留墅拭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓涣狗,卻偏偏與公主長得像谍婉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子镀钓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容