前言
前面一篇文章介紹了關(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是如何管理進程的搜变。
目錄
進程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:其他進程。
這三段使用兩個字段分割開:mLruProcessServiceStart和mLruProcessActivityStart拌滋,分別表示hasActivity段的開始位置以及hasService段的開始位置。
大概模型如下:
每次優(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列表中愕撰。
看圖說話:
好了,關(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交互圖:
有了上面的基礎(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ù)這個圖自行去閱讀源碼。
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í)室】