解讀Android進程優先級ADJ算法


copy from :  http://gityuan.com/2018/05/19/android-process-adj/

本文基於原生Android 9.0源碼來解讀進程優先級原理,基於篇幅考慮會精煉部分代碼

一、概述

1.1 進程

Android框架對進程創建與管理進行了封裝,對於APP開發者只需知道Android四大組件的使用。當Activity, Service, ContentProvider, BroadcastReceiver任一組件啟動時,當其所承載的進程存在則直接使用,不存在則由框架代碼自動調用startProcessLocked創建進程。一個APP可以擁有多個進程,多個APP也可以運行在同一個進程,通過配置Android:process屬性來決定。所以說對APP來說進程幾乎是透明的,但了解進程對於深刻理解Android系統是至關關鍵的。

1.2 優先級

Android系統的設計理念正是希望應用進程能盡量長時間地存活,以提升用戶體驗。應用首次打開比較慢,這個過程有進程創建以及Application等信息的初始化,所以應用在啟動之后,即便退到后台並非立刻殺死,而是存活一段時間,這樣下次再使用則會非常快。對於APP同樣希望自身盡可能存活更長的時間,甚至探索各種保活黑科技。物極必反,系統處於低內存的狀態下,手機性能會有所下降;系統繼續放任所有進程一直存活,系統內存很快就會枯竭而亡,那么需要合理地進程回收機制。

到底該回收哪個進程呢?系統根據進程的組件狀態來決定每個進程的優先級值ADJ,系統根據一定策略先殺優先級最低的進程,然后逐步殺優先級更低的進程,依此類推,以回收預期的可用系統資源,從而保證系統正常運轉。

談到優先級,可能有些人會想到Linux進程本身有nice值,這個能決定CPU資源調度的優先級;而本文介紹Android系統中的ADJ,主要決定在什么場景下什么類型的進程可能會被殺,影響的是進程存活時間。ADJ與nice值兩者定位不同,不過也有一定的聯系,優先級很高的進程,往往也是用戶不希望被殺的進程,是具有有一定正相關性。

1.3 ADJ級別

ADJ級別 取值 含義
NATIVE_ADJ -1000 native進程
SYSTEM_ADJ -900 僅指system_server進程
PERSISTENT_PROC_ADJ -800 系統persistent進程
PERSISTENT_SERVICE_ADJ -700 關聯着系統或persistent進程
FOREGROUND_APP_ADJ 0 前台進程
VISIBLE_APP_ADJ 100 可見進程
PERCEPTIBLE_APP_ADJ 200 可感知進程,比如后台音樂播放
BACKUP_APP_ADJ 300 備份進程
HEAVY_WEIGHT_APP_ADJ 400 重量級進程
SERVICE_ADJ 500 服務進程
HOME_APP_ADJ 600 Home進程
PREVIOUS_APP_ADJ 700 上一個進程
SERVICE_B_ADJ 800 B List中的Service
CACHED_APP_MIN_ADJ 900 不可見進程的adj最小值
CACHED_APP_MAX_ADJ 906 不可見進程的adj最大值

從Android 7.0開始,ADJ采用100、200、300;在這之前的版本ADJ采用數字1、2、3,這樣的調整可以更進一步地細化進程的優先級,比如在VISIBLE_APP_ADJ(100)與PERCEPTIBLE_APP_ADJ(200)之間,可以有ADJ=101、102級別的進程。

  • 省去lmk對oom_score_adj的計算過程,Android 7.0之前的版本,oom_score_adj= oom_adj * 1000/17; 而Android 7.0開始,oom_score_adj= oom_adj,不用再經過一次轉換。

1.4 LMK

為了防止剩余內存過低,Android在內核空間有lowmemorykiller(簡稱LMK),LMK是通過注冊shrinker來觸發低內存回收的,這個機制並不太優雅,可能會拖慢Shrinkers內存掃描速度,已從內核4.12中移除,后續會采用用戶空間的LMKD + memory cgroups機制,這里先不展開LMK講解。

進程剛啟動時ADJ等於INVALID_ADJ,當執行完attachApplication(),該該進程的curAdj和setAdj不相等,則會觸發執行setOomAdj()將該進程的節點/proc/pid/oom_score_adj寫入oomadj值。下圖參數為Android原生閾值,當系統剩余空閑內存低於某閾值(比如147MB),則從ADJ大於或等於相應閾值(比如900)的進程中,選擇ADJ值最大的進程,如果存在多個ADJ相同的進程,則選擇內存最大的進程。 如下是64位機器,LMK默認閾值圖:

lmk_adj

在updateOomLevels()過程,會根據手機屏幕尺寸或內存大小來調整scale,默認大多數手機內存都大於700MB,則scale等於1。對於64位手機,閾值會更大些,具體如下。

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) { ... for (int i=0; i<mOomAdj.length; i++) { int low = mOomMinFreeLow[i]; int high = mOomMinFreeHigh[i]; if (is64bit) { if (i == 4) high = (high*3)/2; else if (i == 5) high = (high*7)/4; } mOomMinFree[i] = (int)(low + ((high-low)*scale)); } } 

二、解讀ADJ

接下來,解讀每個ADJ值都對應着怎樣條件的進程,包括正在運行的組件以及這些組件的狀態幾何。這里重點介紹上圖標紅的ADJ級別所對應的進程。

Android系統中計算各進程ADJ算法的核心方法:

  • updateOomAdjLocked:更新adj,當目標進程為空或者被殺則返回false;否則返回true;
  • computeOomAdjLocked:計算adj,返回計算后RawAdj值;
  • applyOomAdjLocked:應用adj,當需要殺掉目標進程則返回false;否則返回true。

當Android四大組件狀態改變時會updateOomAdjLocked()來同步更新相應進程的ADJ優先級。這里需要說明一下,當同一個進程有多個決定其優先級的組件狀態時,取優先級最高的ADJ作為最終的ADJ。另外,進程會通過設置maxAdj來限定ADJ的上限。

關於分析進程ADJ相關信息,常用命令如下:

  • dumpsys meminfo,
  • dumpsys activity o
  • dumpsys activity p

2.0 ADJ<0的進程

  • NATIVE_ADJ(-1000):是由init進程fork出來的Native進程,並不受system管控;
  • SYSTEM_ADJ(-900):是指system_server進程;
  • PERSISTENT_PROC_ADJ(-800): 是指在AndroidManifest.xml中申明android:persistent=”true”的系統(即帶有FLAG_SYSTEM標記)進程,persistent進程一般情況並不會被殺,即便被殺或者發生Crash系統會立即重新拉起該進程。
  • PERSISTENT_SERVICE_ADJ(-700):是由startIsolatedProcess()方式啟動的進程,或者是由system_server或者persistent進程所綁定(並且帶有BIND_ABOVE_CLIENT或者BIND_IMPORTANT)的服務進程

再來說一下其他優先級:

  • BACKUP_APP_ADJ(300):執行bindBackupAgent()過程的進程
  • HEAVY_WEIGHT_APP_ADJ(400): realStartActivityLocked()過程,當應用的privateFlags標識PRIVATE_FLAG_CANT_SAVE_STATE的進程;
  • HOME_APP_ADJ(600):當類型為ACTIVITY_TYPE_HOME的應用,比如桌面APP
  • PREVIOUS_APP_ADJ(700):用戶上一個使用的APP進程

SYSTEM_ADJ(-900)

SYSTEM_ADJ: 僅指system_server進程。在執行SystemServer的startBootstrapServices()過程會調用AMS.setSystemProcess(),將system_server進程的maxAdj設置成SYSTEM_ADJ,源碼如下:

public void setSystemProcess() { ... ApplicationInfo info = mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY); mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader()); synchronized (this) { ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0); app.persistent = true; app.pid = MY_PID; app.maxAdj = ProcessList.SYSTEM_ADJ; app.makeActive(mSystemThread.getApplicationThread(), mProcessStats); synchronized (mPidsSelfLocked) { mPidsSelfLocked.put(app.pid, app); } updateLruProcessLocked(app, false, null); updateOomAdjLocked(); } ... } 

但system_server的ADJ並非等於-900,而是-800?是由於startPersistentApps()過程直接把其adj重新被設置為-800,這算是一個小BUG,但 其實目前來說對於ADJ<0的進程,LMK不會殺,兩者沒有什么區別。

PERSISTENT_PROC_ADJ(-800)

PERSISTENT_PROC_ADJ:在AndroidManifest.xml中申明android:persistent=”true”的系統(即帶有FLAG_SYSTEM標記)進程,稱之為persistent進程。對於persistent進程常規情況都不會被殺,一旦被殺或者發生Crash,進程會立即重啟。

AMS.addAppLocked()或AMS.newProcessRecordLocked()過程會賦值:

場景1: newProcessRecordLocked

final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, boolean isolated, int isolatedUid) { String proc = customProcess != null ? customProcess : info.processName; final int userId = UserHandle.getUserId(info.uid); int uid = info.uid; ... final ProcessRecord r = new ProcessRecord(stats, info, proc, uid); if (!mBooted && !mBooting && userId == UserHandle.USER_SYSTEM && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { r.persistent = true; r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; } if (isolated && isolatedUid != 0) { r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ; } return r; } 

在每一次進程啟動的時候都會判斷該進程是否persistent進程,如果是則會設置maxAdj=PERSISTENT_PROC_ADJ。 system_server進程應該也是persistent進程?

場景2:addAppLocked

final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, String abiOverride) { ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName, info.uid, true); } else { app = null; } if (app == null) { app = newProcessRecordLocked(info, customProcess, isolated, 0); updateLruProcessLocked(app, false, null); updateOomAdjLocked(); } ... if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { app.persistent = true; app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; } if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) { mPersistentStartingProcesses.add(app); startProcessLocked(app, "added application", customProcess != null ? customProcess : app.processName, abiOverride); } return app; } 

開機過程會先啟動persistent進程,並賦予maxAdj為PERSISTENT_PROC_ADJ,調用鏈:

startOtherServices() AMS.systemReady AMS.startPersistentApps AMS.addAppLocked 

PERSISTENT_SERVICE_ADJ(-700)

PERSISTENT_SERVICE_ADJ: startIsolatedProcess()方式啟動的進程,或者是由system_server或者persistent進程所綁定的服務進程。

場景1:newProcessRecordLocked

final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, boolean isolated, int isolatedUid) { String proc = customProcess != null ? customProcess : info.processName; final int userId = UserHandle.getUserId(info.uid); int uid = info.uid; ... final ProcessRecord r = new ProcessRecord(stats, info, proc, uid); if (!mBooted && !mBooting && userId == UserHandle.USER_SYSTEM && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) { r.persistent = true; r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ; } if (isolated && isolatedUid != 0) { //startIsolatedProcess r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ; } return r; } 

調用鏈:

startOtherServices WebViewUpdateService.prepareWebViewInSystemServer WebViewUpdateServiceImpl.prepareWebViewInSystemServer WebViewUpdater.prepareWebViewInSystemServer WebViewUpdater.onWebViewProviderChanged SystemImpl.onWebViewProviderChanged WebViewFactory.onWebViewProviderChanged WebViewLibraryLoader.prepareNativeLibraries WebViewLibraryLoader.createRelros WebViewLibraryLoader.createRelroFile AMS.startIsolatedProcess 

BACKUP_APP_ADJ(300)

if (mBackupTarget != null && app == mBackupTarget.app) { if (adj > ProcessList.BACKUP_APP_ADJ) { adj = ProcessList.BACKUP_APP_ADJ; if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; } app.adjType = "backup"; app.cached = false; } if (procState > ActivityManager.PROCESS_STATE_BACKUP) { procState = ActivityManager.PROCESS_STATE_BACKUP; app.adjType = "backup"; } } 
  • 執行bindBackupAgent()過程,設置mBackupTarget值;
  • 執行clearPendingBackup()或unbindBackupAgent()過程,置空mBackupTarget值;

HEAVY_WEIGHT_APP_ADJ(400)

  • realStartActivityLocked()過程,當應用的privateFlags標識PRIVATE_FLAG_CANT_SAVE_STATE,設置mHeavyWeightProcess值;
  • finishHeavyWeightApp(), 置空mHeavyWeightProcess值;

HOME_APP_ADJ(600)

當類型為ACTIVITY_TYPE_HOME的應用啟動后會設置mHomeProcess,比如桌面APP。

PREVIOUS_APP_ADJ(700)

場景1:用戶上一個使用的包含UI的進程,為了給用戶在兩個APP之間更好的切換體驗,將上一個進程ADJ設置到PREVIOUS_APP_ADJ的檔次。 當activityStoppedLocked()過程會更新上一個應用。

if (app == mPreviousProcess && app.activities.size() > 0) { if (adj > ProcessList.PREVIOUS_APP_ADJ) { adj = ProcessList.PREVIOUS_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false; app.adjType = "previous"; } if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; app.adjType = "previous"; } } 

場景2: 當provider進程,上一次使用時間不超過20S的情況下,優先級不低於PREVIOUS_APP_ADJ。provider進程這個是Android 7.0以后新增的邏輯 ,這樣做的好處是在內存比較低的情況下避免擁有provider的進程出現顛簸,也就是啟動后殺,然后又被拉。

if (app.lastProviderTime > 0 && (app.lastProviderTime+mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) { if (adj > ProcessList.PREVIOUS_APP_ADJ) { adj = ProcessList.PREVIOUS_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false; app.adjType = "recent-provider"; } if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; app.adjType = "recent-provider"; } } 

2.1 FOREGROUND_APP_ADJ(0)

場景1:滿足以下任一條件的進程都屬於FOREGROUND_APP_ADJ(0)優先級:

  • 正處於resumed狀態的Activity
  • 正執行一個生命周期回調的Service(比如執行onCreate,onStartCommand,onDestroy等)
  • 正執行onReceive()的BroadcastReceiver
  • 通過startInstrumentation()啟動的進程

源碼如下:

if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_TOP_APP; app.adjType = "top-activity"; foregroundActivities = true; procState = PROCESS_STATE_CUR_TOP; } else if (app.instr != null) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.adjType = "instrumentation"; procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; } else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = (mTmpBroadcastQueue.contains(mFgBroadcastQueue)) ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "broadcast"; procState = ActivityManager.PROCESS_STATE_RECEIVER; } else if (app.executingServices.size() > 0) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = app.execServicesFg ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "exec-service"; procState = ActivityManager.PROCESS_STATE_SERVICE; } else if (app == TOP_APP) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "top-sleeping"; foregroundActivities = true; procState = PROCESS_STATE_CUR_TOP; } else { schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; adj = cachedAdj; procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; app.cached = true; app.empty = true; app.adjType = "cch-empty"; } 

場景2: 當客戶端進程activity里面調用bindService()方法時flags帶有BIND_ADJUST_WITH_ACTIVITY參數,並且該activity處於可見狀態,則當前服務進程也屬於前台進程,源碼如下:

for (int is = app.services.size()-1; is >= 0; is--) { ServiceRecord s = app.services.valueAt(is); for (int conni = s.connections.size()-1; conni >= 0; conni--) { ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); for (int i = 0; i < clist.size(); i++) { ConnectionRecord cr = clist.get(i); if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) { ... } final ActivityRecord a = cr.activity; if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && (a.visible || a.state == ActivityState.RESUMED || a.state == ActivityState.PAUSING)) { adj = ProcessList.FOREGROUND_APP_ADJ; if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { if ((cr.flags&Context.BIND_IMPORTANT) != 0) { schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND; } else { schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } } app.cached = false; app.adjType = "service"; app.adjTypeCode = ActivityManager.RunningAppProcessInfo .REASON_SERVICE_IN_USE; app.adjSource = a; app.adjSourceProcState = procState; app.adjTarget = s.name; } } } } } 

provider客戶端

場景3: 對於provider進程,還有以下兩個條件能成為前台進程:

  • 當Provider的客戶端進程ADJ<=FOREGROUND_APP_ADJ時,則Provider進程ADJ等於FOREGROUND_APP_ADJ
  • 當Provider有外部(非框架)進程依賴,也就是調用了getContentProviderExternal()方法,則ADJ至少等於FOREGROUND_APP_ADJ
for (int provi = app.pubProviders.size()-1; provi >= 0; provi--) { ContentProviderRecord cpr = app.pubProviders.valueAt(provi); //根據client來調整provider進程的adj和procState for (int i = cpr.connections.size()-1; i >= 0; i--) { ContentProviderConnection conn = cpr.connections.get(i); ProcessRecord client = conn.client; int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); if (adj > clientAdj) { if (app.hasShownUi && app != mHomeProcess && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { ... } else { adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; adjType = "provider"; } app.cached &= client.cached; } ... } //根據provider外部依賴情況來調整adj和schedGroup if (cpr.hasExternalProcessHandles()) { if (adj > ProcessList.FOREGROUND_APP_ADJ) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false; app.adjType = "ext-provider"; app.adjTarget = cpr.name; } } } 

2.2 VISIBLE_APP_ADJ(100)

可見進程:當ActivityRecord的visible=true,也就是Activity可見的進程。

if (!foregroundActivities && activitiesSize > 0) { int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX; for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { if (adj > ProcessList.VISIBLE_APP_ADJ) { adj = ProcessList.VISIBLE_APP_ADJ; app.adjType = "vis-activity"; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; app.adjType = "vis-activity"; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false; app.empty = false; foregroundActivities = true; final TaskRecord task = r.getTask(); if (task != null && minLayer > 0) { final int layer = task.mLayerRank; if (layer >= 0 && minLayer > layer) { minLayer = layer; } } break; } ... } if (adj == ProcessList.VISIBLE_APP_ADJ) { adj += minLayer; } } 

從Android P開始,進一步細化ADJ級別,增加了VISIBLE_APP_LAYER_MAX(99),是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之間有99個槽,則可見級別ADJ的取值范圍為[100,199]。 算法會根據其所在task的mLayerRank來調整其ADJ,100加上mLayerRank就等於目標ADJ,layer越大,則ADJ越小。

關於TaskRecord的mLayerRank的計算方式是在updateOomAdjLocked()過程調用ASS的rankTaskLayersIfNeeded()方法,如下:

[-> ActivityStackSupervisor.java]
void rankTaskLayersIfNeeded() { if (!mTaskLayersChanged) { return; } mTaskLayersChanged = false; for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); displayNdx++) { final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); int baseLayer = 0; for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); baseLayer += stack.rankTaskLayers(baseLayer); } } } 
[-> ActivityStack.java]
final int rankTaskLayers(int baseLayer) { int layer = 0; for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); ActivityRecord r = task.topRunningActivityLocked(); if (r == null || r.finishing || !r.visible) { task.mLayerRank = -1; } else { task.mLayerRank = baseLayer + layer++; } } return layer; } 

當TaskRecord頂部的ActivityRecord為空或者結束或者不可見時,則設置該TaskRecord的mLayerRank等於-1; 每個ActivityDisplay的baseLayer都是從0開始,從最上面的TaskRecord開始,第一個ADJ=100,從上至下依次加1,直到199為上限。

visible_adj_layer

service客戶端

ServiceRecord的成員變量startRequested=true,是指被顯式調用了startService()方法。當service被stop或kill會將其置為false。

一般情況下,即便客戶端進程處於前台進程(ADJ=0)級別,服務進程只會提升到可見(ADJ=1)級別。以下flags是由調用bindService()過程所傳遞的flags來決定的。

flag 含義
BIND_WAIVE_PRIORITY 是指客戶端進程的優先級不會影響目標服務進程的優先級。比如當調用bindService又不希望提升目標服務進程的優先級的情況下,可以使用該flags
BIND_ADJUST_WITH_ACTIVITY 是指當從Activity綁定到該進程時,允許目標服務進程根據該activity的可見性來提升優先級
BIND_ABOVE_CLIENT 當客戶端進程綁定到一個服務進程時,則服務進程比客戶端進程更重要
BIND_IMPORTANT 標記該服務對於客戶端進程很重要,當客戶端進程處於前台進程(ADJ=0)級別時,會把服務進程也提升到前台進程級別
BIND_NOT_VISIBLE 當客戶端進程處於可見(ADJ=1)級別,也不允許被綁定的服務進程提升到可見級別,該類服務進程的優先級上限為可感知(ADJ=2)級別
BIND_NOT_FOREGROUND 不允許被綁定的服務進程提升到前台調度優先級,但是內存優先級可以提升到前台級別。比如不希望服務進程占用

作為工程師很多時候可能還是想看看源碼,show me the code。但是關於ADJ計算這一塊源碼場景computeOomAdjLocked(),Google真心寫得比較亂,為了更清晰地說明客戶端進程如何影響服務進程,在保證不失去原意的情況下重寫了這塊部分邏輯:

這個過程主要根據service本身、client端情況以及activity狀態分別來調整adj和schedGroup

for (int is = app.services.size()-1; is >= 0; is--) { ServiceRecord s = app.services.valueAt(is); if (s.startRequested) { ... // 根據service本身調整adj和adjType } for (int conni = s.connections.size()-1; conni >= 0; conni--) { ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); for (int i = 0; i < clist.size(); i++) { ConnectionRecord cr = clist.get(i); //根據client端來調整adj if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) { if (adj > clientAdj) { if (app.hasShownUi && app != mHomeProcess && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { ... } else { int newAdj = clientAdj; if ((cr.flags&(Context.BIND_ABOVE_CLIENT |Context.BIND_IMPORTANT)) != 0) { if(clientAdj < ProcessList.PERSISTENT_SERVICE_ADJ) { newAdj = PERSISTENT_SERVICE_ADJ; } } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0) { if(clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ) { newAdj = PERCEPTIBLE_APP_ADJ; } } else { if (clientAdj < ProcessList.VISIBLE_APP_ADJ) { newAdj = VISIBLE_APP_ADJ; } } if (adj > newAdj) { adj = newAdj; adjType = "service"; } } } } final ActivityRecord a = cr.activity; // 根據client的activity來調整adj和schedGroup if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { ... } } } } 

上段代碼說明服務端進程優先級(adj)不會低於客戶端進程優先級(newAdj),而newAdj的上限受限於flags,具體服務端進程受客戶端進程影響的ADJ上限如下:

  • BIND_ABOVE_CLIENT或BIND_IMPORTANT的情況下,ADJ上限為PERSISTENT_SERVICE_ADJ;
  • BIND_NOT_VISIBLE的情況下, ADJ上限為PERCEPTIBLE_APP_ADJ;
  • 否則,一般情況下,ADJ上限為VISIBLE_APP_ADJ;

由此,可見當bindService過程帶有BIND_ABOVE_CLIENT或者BIND_IMPORTANT flags的同時,客戶端進程ADJ小於或等於PERSISTENT_SERVICE_ADJ的情況下,該進程則為PERSISTENT_SERVICE_ADJ。另外,即便是啟動過Activity的進程,當客戶端進程ADJ<=200時,還是可以提升該服務進程的優先級。

2.3 PERCEPTIBLE_APP_ADJ(200)

可感知進程:當該進程存在不可見的Activity,但Activity正處於PAUSING、PAUSED、STOPPING狀態,則為PERCEPTIBLE_APP_ADJ

if (!foregroundActivities && activitiesSize > 0) { int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX; for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { ... } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "pause-activity"; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; app.adjType = "pause-activity"; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false; app.empty = false; foregroundActivities = true; } else if (r.state == ActivityState.STOPPING) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "stop-activity"; } if (!r.finishing) { if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; app.adjType = "stop-activity"; } } app.cached = false; app.empty = false; foregroundActivities = true; } } } 

滿足以下任一條件的進程也屬於可感知進程:

  • foregroundServices非空:前台服務進程,執行startForegroundService()方法
  • app.forcingToImportant非空:執行setProcessImportant()方法,比如Toast彈出過程。
  • hasOverlayUi非空:非activity的UI位於屏幕最頂層,比如顯示類型TYPE_APPLICATION_OVERLAY的窗口
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { if (app.foregroundServices) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; app.cached = false; app.adjType = "fg-service"; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } else if (app.hasOverlayUi) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; app.cached = false; app.adjType = "has-overlay-ui"; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } } if (adj > ProcessList.PERCEPTIBLE_APP_ADJ || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) { if (app.forcingToImportant != null) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; app.cached = false; app.adjType = "force-imp"; app.adjSource = app.forcingToImportant; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; } } 

2.4 SERVICE_ADJ(500)

服務進程:沒有啟動過Activity,並且30分鍾之內活躍過的服務進程。 startRequested為true,則代表執行startService()且沒有stop的進程。

for (int is = app.services.size()-1; is >= 0; is--) { ServiceRecord s = app.services.valueAt(is); if (s.startRequested) { app.hasStartedServices = true; if (procState > ActivityManager.PROCESS_STATE_SERVICE) { procState = ActivityManager.PROCESS_STATE_SERVICE; app.adjType = "started-services"; } if (app.hasShownUi && app != mHomeProcess) { if (adj > ProcessList.SERVICE_ADJ) { app.adjType = "cch-started-ui-services"; } } else { if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { if (adj > ProcessList.SERVICE_ADJ) { adj = ProcessList.SERVICE_ADJ; app.adjType = "started-services"; app.cached = false; } } } } for (int conni = s.connections.size()-1; conni >= 0; conni--) { ... //根據client情況來調整adj } } 

2.5 SERVICE_B_ADJ(800)

進程由SERVICE_ADJ(500)降低到SERVICE_B_ADJ(800),有以下兩種情況:

  • A類Service占比過高:當A類Service個數 > Service總數的1/3時,則加入到B類Service。換句話說,B Service的個數至少是A Service的2倍。
  • 內存緊張&&A類Service占用內存較高:當系統內存緊張級別(mLastMemoryLevel)高於ADJ_MEM_FACTOR_NORMAL,且該應用所占內存lastPss大於或等於CACHED_APP_MAX_ADJ級別所對應的內存閾值的1/3(默認值閾值約等於110MB)。

源碼如下:

if (adj == ProcessList.SERVICE_ADJ) { if (doingAll) { app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); mNewNumServiceProcs++; if (!app.serviceb) { if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { app.serviceHighRam = true; app.serviceb = true; } else { mNewNumAServiceProcs++; } } else { app.serviceHighRam = false; } } if (app.serviceb) { adj = ProcessList.SERVICE_B_ADJ; } } 

ADJ_MEM_FACTOR

這里順便一下,內存因子ADJ_MEM_FACTOR共有4個級別,當前處於哪個內存因子級別,取決於當前進程中cached進程和空進程的個數。

final int numCachedAndEmpty = numCached + numEmpty; int memFactor; if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) { if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL; } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW; } else { memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE; } } else { memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL; } 

ADJ內存因子:決定允許后台運行Jobs的最大上限,以及決定TrimMemory的級別(包括ThreadedRenderer的回收級別),再進一步來看看內存因子:

內存因子 取值 先決條件
ADJ_MEM_FACTOR_CRITICAL 3 Cached+Empty<=3
ADJ_MEM_FACTOR_LOW 2 Cached+Empty<=5
ADJ_MEM_FACTOR_MODERATE 1 Cached<=5 && Empty<=8
ADJ_MEM_FACTOR_NORMAL 0 Cached>5或者Empty>8

也就是說

默認情況取值如下:

  • 最大緩存進程個數:CUR_MAX_CACHED_PROCESSES = MAX_CACHED_PROCESSES = 32
  • 最大空進程個數: CUR_MAX_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/2 = 16
  • Trim空進程上限:CUR_TRIM_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/4 = 8
  • Trim緩存進程上限:CUR_TRIM_CACHED_PROCESSES = MAX_CACHED_PROCESSES/6 = 5

當mOverrideMaxCachedProcesses有值的情況下,最大緩存進程個數和最大空進程個數上限優先取mOverrideMaxCachedProcesses,可通過AMS.setProcessLimit(int max)調整mOverrideMaxCachedProcesses值;Trim的緩存進程和空進程上限不受mOverrideMaxCachedProcesses影響。

再來看看cached和empty進程:

final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES; final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit; final long oldTime = now - ProcessList.MAX_EMPTY_TIME; switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: mNumCachedHiddenProcs++; numCached++; //默認cachedProcessLimit=16 if (numCached > cachedProcessLimit) { app.kill("cached #" + numCached, true); } break; case ActivityManager.PROCESS_STATE_CACHED_EMPTY: //默認CUR_TRIM_EMPTY_PROCESSES=8, 且滿足30min if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES && app.lastActivityTime < oldTime) { app.kill("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) / 1000) + "s", true); } else { numEmpty++; //默認cachedProcessLimit=16 if (numEmpty > emptyProcessLimit) { app.kill("empty #" + numEmpty, true); } } break; default: mNumNonCachedProcs++; break; 

用於限制empty或cached進程的上限為16個,並且empty超過8個時會清理掉30分鍾沒有活躍的進程。 cached和empty主要是區別是否有Activity。

2.6 CACHED_APP_MIN_ADJ(900)

緩存進程優先級從CACHED_APP_MIN_ADJ(900)到 CACHED_APP_MAX_ADJ(906)。

ADJ的轉換算法:

  • cached: 900, 901, 903, 905
  • empty: 900, 902, 904, 906

adj_900

final int N = mLruProcesses.size(); //numSlots等於3 int numSlots = (ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2; //mNumNonCachedProcs是指empty和cached之外的進程, mNumCachedHiddenProcs代表的是cached進程個數 int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs; if (numEmptyProcs > cachedProcessLimit) { numEmptyProcs = cachedProcessLimit; } //emptyFactor和cachedFactor分別代表每個slot里面包括的進程個數,大於或等於1 int emptyFactor = numEmptyProcs/numSlots; int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots; mNumNonCachedProcs = 0; mNumCachedHiddenProcs = 0; int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextCachedAdj = curCachedAdj+1; int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextEmptyAdj = curEmptyAdj+2; 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); if (app.curAdj >= ProcessList.UNKNOWN_ADJ) { switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: case ActivityManager.PROCESS_STATE_CACHED_RECENT: app.curRawAdj = curCachedAdj; app.curAdj = app.modifyRawOomAdj(curCachedAdj); if (curCachedAdj != nextCachedAdj) { stepCached++; if (stepCached >= cachedFactor) { stepCached = 0; curCachedAdj = nextCachedAdj; nextCachedAdj += 2; //每次加2 if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; } } } break; default: app.curRawAdj = curEmptyAdj; //ADJ閾值 app.curAdj = app.modifyRawOomAdj(curEmptyAdj); if (curEmptyAdj != nextEmptyAdj) { stepEmpty++; if (stepEmpty >= emptyFactor) { stepEmpty = 0; curEmptyAdj = nextEmptyAdj; nextEmptyAdj += 2; //每次加2 if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ; } } } break; } } applyOomAdjLocked(app, true, now, nowElapsed); ... } } 

numSlots=3, emptyFactor= 空進程個數/3, cachedFactor= 緩存進程個數/3,

再來看看PROCESS_STATE_CACHED_ACTIVITY的定義:

if (!foregroundActivities && activitiesSize > 0) { for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { ... } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { ... } else if (r.state == ActivityState.STOPPING) { ... } else { if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; app.adjType = "cch-act"; } } } } 

foregroundActivities代表當前不是前台(FOREGROUND_APP_ADJ)進程,並且存在Activity的進程,當該Activity窗口不可見,並且不處於PAUSING(正在)、PAUSED(onPause個)、STOPPING的任一狀態的情況下,則設置該進程為PROCESS_STATE_CACHED_ACTIVITY。

PROCESS_STATE_CACHED_ACTIVITY_CLIENT的定義:

if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) { if (app.hasClientActivities) { procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; app.adjType = "cch-client-act"; } else if (app.treatLikeActivity) { procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; app.adjType = "cch-as-act"; } } 

當該進程Service的客戶端進程存在Activity或者是treatLikeActivity的進程,其進程狀態都是cached進程。

三、查看進程優先級

3.1 CPU調度優先級

bindService或者startService是否前台調用取決於caller進程的調度組。當caller屬於SCHED_GROUP_BACKGROUND則認為是后台調用,當不屬於SCHED_GROUP_BACKGROUND則認為是前台調用。callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;

關於CPU調度組:

調度級別 進程組 備注
SCHED_GROUP_BACKGROUND(0) THREAD_GROUP_BG_NONINTERACTIVE 后台進程組
SCHED_GROUP_DEFAULT(1) THREAD_GROUP_DEFAULT 前台進程組
SCHED_GROUP_TOP_APP(2) THREAD_GROUP_TOP_APP TOP進程組
SCHED_GROUP_TOP_APP_BOUND(3) THREAD_GROUP_TOP_APP TOP進程組

THREAD_GROUP_TOP_APP

  • SCHED_GROUP_TOP_APP:
    • setRenderThread()過程,根據屬性sys.use_fifo_ui來決定采用SCHED_FIFO,或者設置當前線程的優先級為-10
    • TOP_APP或者app.hasTopUi,則設置為該值
  • SCHED_GROUP_TOP_APP_BOUND:
    • 對於ConnectionRecord帶有BIND_ADJUST_WITH_ACTIVITY和BIND_IMPORTANT,並且沒帶有BIND_NOT_FOREGROUND的情況下, 當客戶端進程的有可見的Activity,或者處於RESUMED/PAUSING狀態時,則設置為該值

當進程調度級別由非TOP切換到TOP級別,則主線程和rendThread可設置為SCHED_FIFO或者更高優先級;當由TOP級別切換回非TOP級別,則恢復原來的調度策略或優先級。

THREAD_GROUP_DEFAULT

  • SCHED_GROUP_DEFAULT:
    • 默認值
    • ADJ <= FOREGROUND_APP_ADJ;
    • 正在接收來自於mFgBroadcastQueue廣播隊列的廣播;
    • 正在執行來自於前台調度進程發起的服務(execServicesFg=true)
    • Activity處於可見狀態(visible=true)
    • 具有fg-service或者設置forcingToImportant的服務
    • 正在顯示一個overlay UI(app.hasOverlayUi=true)
    • 當ConnectionRecord同時沒有指定BIND_NOT_FOREGROUND和BIND_IMPORTANT_BACKGROUND、BIND_IMPORTANT情況下, 當客戶端進程的schedGroup高於服務進程,則設置為該值
    • 對於ConnectionRecord帶有BIND_ADJUST_WITH_ACTIVITY,並且沒帶有BIND_NOT_FOREGROUND和BIND_IMPORTANT的情況下, 當客戶端進程的有可見的Activity,或者處於RESUMED/PAUSING狀態時,則設置為該值
    • 當ContentProviderConnection所對應的客戶端進程的schedGroup高於服務進程,則設置為該值
    • 當cpr.hasExternalProcessHandles為true的情況
    • 最后,當maxAdj <= PERCEPTIBLE_APP_ADJ的情況

THREAD_GROUP_BG_NONINTERACTIVE

  • SCHED_GROUP_BACKGROUND:
    • 應用已結束(app.thread == null)
    • 正在接收來自於mBgBroadcastQueue廣播隊列的廣播;
    • 正在執行來自於前台調度進程發起的服務(execServicesFg=false)
    • TOP_APP,且設備處於睡眠狀態
    • 等等

四、總結

Android進程優先級ADJ的每一個ADJ級別往往都有多種場景,使用adjType完美地區分相同ADJ下的不同場景; 不同ADJ進程所對應的schedGroup不同,從而分配的CPU資源也不同,schedGroup大體分為TOP(T)、前台(F)、后台(B); ADJ跟AMS中的procState有着緊密的聯系。

  • adj:通過調整oom_score_adj來影響進程壽命(Lowmemorykiller殺進程策略);
  • schedGroup:影響進程的CPU資源調度與分配;
  • procState:從進程所包含的四大組件運行狀態來評估進程狀態,影響framework的內存控制策略。比如控制緩存進程和空進程個數上限依賴於procState,再比如控制APP執行handleLowMemory()的觸發時機等。

為了說明整體關系,以ADJ為中心來講解跟adjType,schedGroup,procState的對應關系,下面以一幅圖來詮釋整個ADJ算法的精髓,幾乎涵蓋了ADJ算法調整的絕大多數場景。

adj_summary

CPU調度組:

調度級別 縮寫 解釋
SCHED_GROUP_BACKGROUND(0) B 后台進程組
SCHED_GROUP_DEFAULT(1) F 前台進程組
SCHED_GROUP_TOP_APP(2) T TOP進程組
SCHED_GROUP_TOP_APP_BOUND(3) T TOP進程組
  1. 常說的前台進程與后台進程,其實是從CPU調度角度來划分的前台與后台;為了讓用戶正在使用的TOP進程能分配到更多的CPU資源,從Android 6.0開始新增了TOP進程組,CPU調度優先分配給當前正在跟用戶交互的APP,提升用戶體驗。
  2. 上圖adjType=”broadcast”的CPU調度組的選擇取決於廣播隊列,當receiver接收到的廣播來自於前台廣播隊列則采用前台進程組,當receiver接收到的廣播來自於后台廣播隊列則采用后台進程組。前后台廣播隊列的CPU資源調度優先級不同,所以前台廣播超時10秒就會ANR,而后台廣播超時60秒才會ANR。更少的CPU資源分配就需要更長的時間來完成執行,這也就是為何兩個廣播隊列定義了不同的超時閾值。
  3. 上圖adjType=”exec-service”的CPU調度組的選擇取決於caller, 當發起bindService或者startService的調用者caller屬於后台進程組,callerFg=false,則Service的生命周期回調運行在后台進程組,非常很少的CPU資源;當caller屬於前台或者TOP進程組,則Service的生命周期回調運行在前台進程組,分配較多的CPU資源。
  4. 上圖adjType=”service”也有機會選擇TOP組, 前提條件是在bindService的時候帶有BIND_IMPORTANT的flags,用於標記該服務對於客戶端進程很重要。

最后,給廣大應用開發者一些友好的建議:

  1. UI進程與Service進程一定要分離,因為對於包含activity的service進程,一旦進入后台就成為”cch-started-ui-services”類型的cache進程(ADJ>=900),隨時可能會被系統回收;而分離后的Service進程服務屬於SERVICE_ADJ(500),被殺的可能性相對較小。尤其是系統允許自啟動的服務進程必須做UI分離,避免消耗系統較大內存。
  2. 只有真正需要用戶可感知的應用,才調用startForegroundService()方法來啟動前台服務,此時ADJ=PERCEPTIBLE_APP_ADJ(200),常駐內存,並且會在通知欄常駐通知提醒用戶,比如音樂播放,地圖導航。切勿為了常駐而濫用前台服務,這會嚴重影響用戶體驗。
  3. 進程中的Service工作完成后,務必主動調用stopService或stopSelf來停止服務,避免占據內存,浪費系統資源;
  4. 不要長時間綁定其他進程的service或者provider,每次使用完成后應立刻釋放,避免其他進程常駐於內存;
  5. APP應該實現接口onTrimMemory()和onLowMemory(),根據TrimLevel適當地將非必須內存在回調方法中加以釋放。當系統內存緊張時會回調該接口,減少系統卡頓與殺進程頻次。
  6. 減少在保活上花心思,更應該在優化內存上下功夫,因為在相同ADJ級別的情況下,系統會選擇優先殺內存占用的進程。


免責聲明!

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



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