Android 原生開發(fā):系統(tǒng)解析之進(jìn)程優(yōu)先級

本文基于原生 Android 9.0 源碼來解讀進(jìn)程優(yōu)先級原理.

一芋肠、概述

1.1 進(jìn)程

Android 框架對進(jìn)程創(chuàng)建與管理進(jìn)行了封裝,對于 APP開發(fā)者只需知道 Android 四大組件的使用央串。當(dāng) Activity, Service, ContentProvider, BroadcastReceiver 任一組件啟動時,當(dāng)其所承載的進(jìn)程存在則直接使用,不存在則由框架代碼自動調(diào)用 startProcessLocked 創(chuàng)建進(jìn)程锋叨。一個 APP 可以擁有多個進(jìn)程菊匿,多個 APP 也可以運(yùn)行在同一個進(jìn)程付呕,通過配置 Android:process 屬性來決定。所以說對 APP 來說進(jìn)程幾乎是透明的捧请,但了解進(jìn)程對于深刻理解 Android 系統(tǒng)是至關(guān)關(guān)鍵的凡涩。

1.2 優(yōu)先級

Android 系統(tǒng)的設(shè)計理念正是希望應(yīng)用進(jìn)程能盡量長時間地存活,以提升用戶體驗(yàn)疹蛉。應(yīng)用首次打開比較慢活箕,這個過程有進(jìn)程創(chuàng)建以及 Application 等信息的初始化,所以應(yīng)用在啟動之后可款,即便退到后臺并非立刻殺死育韩,而是存活一段時間枢纠,這樣下次再使用則會非忱蠲#快。對于APP同樣希望自身盡可能存活更長的時間派任,甚至探索各種泵校活黑科技悉罕。物極必反,系統(tǒng)處于低內(nèi)存的狀態(tài)下立镶,手機(jī)性能會有所下降壁袄;系統(tǒng)繼續(xù)放任所有進(jìn)程一直存活,系統(tǒng)內(nèi)存很快就會枯竭而亡媚媒,那么需要合理地進(jìn)程回收機(jī)制嗜逻。

到底該回收哪個進(jìn)程呢?系統(tǒng)根據(jù)進(jìn)程的組件狀態(tài)來決定每個進(jìn)程的優(yōu)先級值 ADJ缭召,系統(tǒng)根據(jù)一定策略先殺優(yōu)先級最低的進(jìn)程栈顷,然后逐步殺優(yōu)先級更低的進(jìn)程逆日,依此類推,以回收預(yù)期的可用系統(tǒng)資源萄凤,從而保證系統(tǒng)正常運(yùn)轉(zhuǎn)室抽。

談到優(yōu)先級,可能有些人會想到 Linux 進(jìn)程本身有 nice 值蛙卤,這個能決定 CPU 資源調(diào)度的優(yōu)先級狠半;而本文介紹 Android 系統(tǒng)中的 ADJ,主要決定在什么場景下什么類型的進(jìn)程可能會被殺颤难,影響的是進(jìn)程存活時間神年。ADJ 與 nice 值兩者定位不同,不過也有一定的聯(lián)系行嗤,優(yōu)先級很高的進(jìn)程已日,往往也是用戶不希望被殺的進(jìn)程,是具有有一定正相關(guān)性栅屏。

1.3 ADJ級別

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

從 Android 7.0 開始,ADJ 采用100栈雳、200护奈、300;在這之前的版本 ADJ 采用數(shù)字 1哥纫、2霉旗、3,這樣的調(diào)整可以更進(jìn)一步地細(xì)化進(jìn)程的優(yōu)先級蛀骇,比如在 VISIBLE_APP_ADJ(100)PERCEPTIBLE_APP_ADJ(200)之間厌秒,可以有 ADJ=101、102 級別的進(jìn)程擅憔。

  • 省去 lmk 對 oom_score_adj 的計算過程鸵闪,Android 7.0 之前的版本,oom_score_adj= oom_adj * 1000/17暑诸; 而Android 7.0開始蚌讼,oom_score_adj= oom_adj,不用再經(jīng)過一次轉(zhuǎn)換个榕。

1.4 LMK

為了防止剩余內(nèi)存過低啦逆,Android 在內(nèi)核空間有 lowmemorykiller (簡稱 LMK ), LMK 是通過注冊 shrinker 來觸發(fā)低內(nèi)存回收的笛洛,這個機(jī)制并不太優(yōu)雅,可能會拖慢 Shrinkers 內(nèi)存掃描速度乃坤,已從內(nèi)核 4.12 中移除苛让,后續(xù)會采用用戶空間的 LMKD + memory cgroups 機(jī)制沟蔑,這里先不展開 LMK 講解。

進(jìn)程剛啟動時 ADJ 等于 INVALID_ADJ狱杰,當(dāng)執(zhí)行完attachApplication()瘦材,該該進(jìn)程的 curAdj 和 setAdj 不相等,則會觸發(fā)執(zhí)行 setOomAdj() 將該進(jìn)程的節(jié)點(diǎn)/proc/pid/oom_score_adj寫入oomadj值仿畸。下圖參數(shù)為 Android 原生閾值食棕,當(dāng)系統(tǒng)剩余空閑內(nèi)存低于某閾值(比如147MB),則從 ADJ 大于或等于相應(yīng)閾值(比如 900)的進(jìn)程中错沽,選擇 ADJ 值最大的進(jìn)程簿晓,如果存在多個 ADJ 相同的進(jìn)程,則選擇內(nèi)存最大的進(jìn)程千埃。 如下是 64 位機(jī)器憔儿, LMK 默認(rèn)閾值圖:

lmk_adj

在 updateOomLevels() 過程,會根據(jù)手機(jī)屏幕尺寸或內(nèi)存大小來調(diào)整 scale放可,默認(rèn)大多數(shù)手機(jī)內(nèi)存都大于 700MB谒臼,則 scale 等于1。對于 64 位手機(jī)耀里,閾值會更大些蜈缤,具體如下。

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 值都對應(yīng)著怎樣條件的進(jìn)程,包括正在運(yùn)行的組件以及這些組件的狀態(tài)幾何织堂。這里重點(diǎn)介紹上圖標(biāo)紅的 ADJ 級別所對應(yīng)的進(jìn)程叠艳。

Androi d系統(tǒng)中計算各進(jìn)程 ADJ 算法的核心方法:

  • updateOomAdjLocked:更新 adj,當(dāng)目標(biāo)進(jìn)程為空或者被殺則返回 false易阳;否則返回 true;
  • computeOomAdjLocked:計算 adj附较,返回計算后 RawAdj 值;
  • applyOomAdjLocked:應(yīng)用 adj,當(dāng)需要?dú)⒌裟繕?biāo)進(jìn)程則返回 false潦俺;否則返回 true拒课。

當(dāng)Android四大組件狀態(tài)改變時會 updateOomAdjLocked() 來同步更新相應(yīng)進(jìn)程的 ADJ 優(yōu)先級。這里需要說明一下事示,當(dāng)同一個進(jìn)程有多個決定其優(yōu)先級的組件狀態(tài)時早像,取優(yōu)先級最高的 ADJ 作為最終的 ADJ。另外肖爵,進(jìn)程會通過設(shè)置 maxAdj 來限定 ADJ 的上限卢鹦。

關(guān)于分析進(jìn)程ADJ相關(guān)信息,常用命令如下:

  • dumpsys meminfo劝堪,
  • dumpsys activity o
  • dumpsys activity p

2.0 ADJ<0的進(jìn)程

  • NATIVE_ADJ(-1000):是由 init 進(jìn)程 fork 出來的 Native 進(jìn)程冀自,并不受 system 管控揉稚;
  • SYSTEM_ADJ(-900):是指 system_server 進(jìn)程;
  • PERSISTENT_PROC_ADJ(-800): 是指在 AndroidManifest.xml 中申明 android:persistent=”true”的系統(tǒng)(即帶有 FLAG_SYSTEM 標(biāo)記)進(jìn)程熬粗,persistent 進(jìn)程一般情況并不會被殺搀玖,即便被殺或者發(fā)生 Crash 系統(tǒng)會立即重新拉起該進(jìn)程。
  • PERSISTENT_SERVICE_ADJ(-700):是由 startIsolatedProcess() 方式啟動的進(jìn)程驻呐,或者是由system_server 或者 persistent 進(jìn)程所綁定(并且?guī)в?BIND_ABOVE_CLIENT 或者 BIND_IMPORTANT )的服務(wù)進(jìn)程

再來說一下其他優(yōu)先級:

  • BACKUP_APP_ADJ(300):執(zhí)行 bindBackupAgent() 過程的進(jìn)程
  • HEAVY_WEIGHT_APP_ADJ(400): realStartActivityLocked() 過程灌诅,當(dāng)應(yīng)用的 privateFlags 標(biāo)識 PRIVATE_FLAG_CANT_SAVE_STATE 的進(jìn)程;
  • HOME_APP_ADJ(600):當(dāng)類型為 ACTIVITY_TYPE_HOME 的應(yīng)用含末,比如桌面 APP
  • PREVIOUS_APP_ADJ(700):用戶上一個使用的APP進(jìn)程

SYSTEM_ADJ(-900)

SYSTEM_ADJ: 僅指 system_server 進(jìn)程猜拾。在執(zhí)行 SystemServer 的 startBootstrapServices() 過程會調(diào)用 AMS.setSystemProcess(),將 system_server 進(jìn)程的 maxAdj 設(shè)置成 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();
    }
    ...
}

PERSISTENT_PROC_ADJ(-800)

PERSISTENT_PROC_ADJ:在 AndroidManifest.xml 中申明 android:persistent=”true” 的系統(tǒng)(即帶有FLAG_SYSTEM 標(biāo)記)進(jìn)程关带,稱之為 persistent 進(jìn)程。對于 persistent 進(jìn)程常規(guī)情況都不會被殺沼撕,一旦被殺或者發(fā)生 Crash宋雏,進(jìn)程會立即重啟。

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;
}

在每一次進(jìn)程啟動的時候都會判斷該進(jìn)程是否persistent進(jìn)程务豺,如果是則會設(shè)置maxAdj=PERSISTENT_PROC_ADJ磨总。 system_server 進(jìn)程應(yīng)該也是 persistent 進(jìn)程?

場景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;
}

開機(jī)過程會先啟動persistent進(jìn)程笼沥,并賦予 maxAdj 為 PERSISTENT_PROC_ADJ蚪燕,調(diào)用鏈:

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

PERSISTENT_SERVICE_ADJ(-700)

PERSISTENT_SERVICE_ADJ: startIsolatedProcess()方式啟動的進(jìn)程,或者是由 system_server 或者 persistent 進(jìn)程所綁定的服務(wù)進(jìn)程奔浅。

場景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;
}

調(diào)用鏈:

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";
    }
}

  • 執(zhí)行 bindBackupAgent() 過程馆纳,設(shè)置 mBackupTarget 值;
  • 執(zhí)行 clearPendingBackup() 或 unbindBackupAgent() 過程汹桦,置空 mBackupTarget 值鲁驶;

HEAVY_WEIGHT_APP_ADJ(400)

  • realStartActivityLocked() 過程,當(dāng)應(yīng)用的 privateFlags 標(biāo)識 PRIVATE_FLAG_CANT_SAVE_STATE舞骆,設(shè)置 mHeavyWeightProcess 值钥弯;
  • finishHeavyWeightApp(), 置空 mHeavyWeightProcess 值;

HOME_APP_ADJ(600)

當(dāng)類型為 ACTIVITY_TYPE_HOME 的應(yīng)用啟動后會設(shè)置 mHomeProcess督禽,比如桌面 APP脆霎。

PREVIOUS_APP_ADJ(700)

場景1:用戶上一個使用的包含 UI 的進(jìn)程,為了給用戶在兩個 APP 之間更好的切換體驗(yàn)狈惫,將上一個進(jìn)程 ADJ 設(shè)置到 PREVIOUS_APP_ADJ 的檔次睛蛛。 當(dāng) activityStoppedLocked() 過程會更新上一個應(yīng)用。

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: 當(dāng) provider 進(jìn)程,上一次使用時間不超過 20S 的情況下玖院,優(yōu)先級不低于 PREVIOUS_APP_ADJ菠红。provider 進(jìn)程這個是 Android 7.0 以后新增的邏輯 ,這樣做的好處是在內(nèi)存比較低的情況下避免擁有 provider 的進(jìn)程出現(xiàn)顛簸难菌,也就是啟動后殺,然后又被拉蔑滓。

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:滿足以下任一條件的進(jìn)程都屬于 FOREGROUND_APP_ADJ(0) 優(yōu)先級:

  • 正處于resumed狀態(tài)的Activity
  • 正執(zhí)行一個生命周期回調(diào)的Service(比如執(zhí)行 onCreate , onStartCommand, onDestroy等)
  • 正執(zhí)行 onReceive() 的 BroadcastReceiver
  • 通過 startInstrumentation() 啟動的進(jìn)程

源碼如下:

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: 當(dāng)客戶端進(jìn)程 activity 里面調(diào)用 bindService() 方法時 flags 帶有 BIND_ADJUST_WITH_ACTIVITY 參數(shù)郊酒,并且該 activity 處于可見狀態(tài),則當(dāng)前服務(wù)進(jìn)程也屬于前臺進(jìn)程键袱,源碼如下:

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 進(jìn)程燎窘,還有以下兩個條件能成為前臺進(jìn)程:

  • 當(dāng) Provider 的客戶端進(jìn)程 ADJ<=FOREGROUND_APP_ADJ 時,則 Provider 進(jìn)程 ADJ 等于 FOREGROUND_APP_ADJ
  • 當(dāng)Provider有外部(非框架)進(jìn)程依賴蹄咖,也就是調(diào)用了 getContentProviderExternal() 方法褐健,則 ADJ 至少等于 FOREGROUND_APP_ADJ
for (int provi = app.pubProviders.size()-1; provi >= 0; provi--) {
    ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
    //根據(jù)client來調(diào)整provider進(jìn)程的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;
        }
        ...
    }
    //根據(jù)provider外部依賴情況來調(diào)整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)

可見進(jìn)程:當(dāng) ActivityRecord 的 visible=true,也就是 Activity 可見的進(jìn)程澜汤。

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 開始蚜迅,進(jìn)一步細(xì)化 ADJ 級別,增加了 VISIBLE_APP_LAYER_MAX(99) 俊抵,是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200) 之間有 99 個槽谁不,則可見級別 ADJ 的取值范圍為 [100,199]。 算法會根據(jù)其所在 task 的 mLayerRank 來調(diào)整其 ADJ徽诲,100 加上 mLayerRank 就等于目標(biāo) ADJ刹帕,layer 越大,則 ADJ 越小谎替。

關(guān)于 TaskRecord 的 mLayerRank 的計算方式是在 updateOomAdjLocked() 過程調(diào)用 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;
}

當(dāng) TaskRecord 頂部的 ActivityRecord 為空或者結(jié)束或者不可見時,則設(shè)置該 TaskRecord 的 mLayerRank 等于 -1; 每個 ActivityDisplay 的 baseLayer 都是從 0 開始钱贯,從最上面的 TaskRecord 開始挫掏,第一個 ADJ=100,從上至下依次加 1喷舀,直到 199 為上限砍濒。

visible_adj_layer

service客戶端

ServiceRecord 的成員變量 startRequested=true,是指被顯式調(diào)用了 startService() 方法硫麻。當(dāng) service 被 stop 或 kill 會將其置為 false爸邢。

一般情況下,即便客戶端進(jìn)程處于前臺進(jìn)程 (ADJ=0) 級別拿愧,服務(wù)進(jìn)程只會提升到可見 (ADJ=1) 級別杠河。以下 flags 是由調(diào)用 bindService() 過程所傳遞的 flags 來決定的。

flag 含義
BIND_WAIVE_PRIORITY 是指客戶端進(jìn)程的優(yōu)先級不會影響目標(biāo)服務(wù)進(jìn)程的優(yōu)先級。比如當(dāng)調(diào)用bindService又不希望提升目標(biāo)服務(wù)進(jìn)程的優(yōu)先級的情況下券敌,可以使用該flags
BIND_ADJUST_WITH_ACTIVITY 是指當(dāng)從Activity綁定到該進(jìn)程時唾戚,允許目標(biāo)服務(wù)進(jìn)程根據(jù)該activity的可見性來提升優(yōu)先級
BIND_ABOVE_CLIENT 當(dāng)客戶端進(jìn)程綁定到一個服務(wù)進(jìn)程時,則服務(wù)進(jìn)程比客戶端進(jìn)程更重要
BIND_IMPORTANT 標(biāo)記該服務(wù)對于客戶端進(jìn)程很重要待诅,當(dāng)客戶端進(jìn)程處于前臺進(jìn)程(ADJ=0)級別時叹坦,會把服務(wù)進(jìn)程也提升到前臺進(jìn)程級別
BIND_NOT_VISIBLE 當(dāng)客戶端進(jìn)程處于可見(ADJ=1)級別,也不允許被綁定的服務(wù)進(jìn)程提升到可見級別卑雁,該類服務(wù)進(jìn)程的優(yōu)先級上限為可感知(ADJ=2)級別
BIND_NOT_FOREGROUND 不允許被綁定的服務(wù)進(jìn)程提升到前臺調(diào)度優(yōu)先級募书,但是內(nèi)存優(yōu)先級可以提升到前臺級別。比如不希望服務(wù)進(jìn)程占用

作為工程師很多時候可能還是想看看源碼 测蹲。但是關(guān)于 ADJ 計算這一塊源碼場景 computeOomAdjLocked() 莹捡, 為了更清晰地說明客戶端進(jìn)程如何影響服務(wù)進(jìn)程,在保證不失去原意的情況下重寫了這塊部分邏輯:

這個過程主要根據(jù) service 本身扣甲、client 端情況以及 activity 狀態(tài)分別來調(diào)整 adj 和 schedGroup

for (int is = app.services.size()-1; is >= 0; is--) {
    ServiceRecord s = app.services.valueAt(is);
    if (s.startRequested) {
        ... // 根據(jù)service本身調(diào)整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);
            //根據(jù)client端來調(diào)整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;
            // 根據(jù)client的activity來調(diào)整adj和schedGroup
            if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
                ...
            }
        }
    }
}

上段代碼說明服務(wù)端進(jìn)程優(yōu)先級(adj)不會低于客戶端進(jìn)程優(yōu)先級(newAdj)篮赢,而newAdj的上限受限于flags,具體服務(wù)端進(jìn)程受客戶端進(jìn)程影響的ADJ上限如下:

  • BIND_ABOVE_CLIENTBIND_IMPORTANT的情況下琉挖,ADJ 上限為 PERSISTENT_SERVICE_ADJ启泣;
  • BIND_NOT_VISIBLE 的情況下, ADJ 上限為PERCEPTIBLE_APP_ADJ粹排;
  • 否則种远,一般情況下,ADJ 上限為 VISIBLE_APP_ADJ顽耳;

由此坠敷,可見當(dāng) bindService 過程帶有 BIND_ABOVE_CLIENT 或者 BIND_IMPORTANT flags 的同時,客戶端進(jìn)程 ADJ 小于或等于 PERSISTENT_SERVICE_ADJ 的情況下射富,該進(jìn)程則為 PERSISTENT_SERVICE_ADJ膝迎。另外,即便是啟動過 Activity 的進(jìn)程胰耗,當(dāng)客戶端進(jìn)程 ADJ<=200 時限次,還是可以提升該服務(wù)進(jìn)程的優(yōu)先級。

2.3 PERCEPTIBLE_APP_ADJ(200)

可感知進(jìn)程:當(dāng)該進(jìn)程存在不可見的 Activity柴灯,但 Activity 正處于 PAUSING卖漫、PAUSED、STOPPING 狀態(tài)赠群,則為 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;
        }
    }
}

滿足以下任一條件的進(jìn)程也屬于可感知進(jìn)程:

  • foregroundServices 非空:前臺服務(wù)進(jìn)程羊始,執(zhí)行 startForegroundService() 方法
  • app.forcingToImportant 非空:執(zhí)行setProcessImportant()方法,比如 Toast 彈出過程查描。
  • hasOverlayUi 非空:非 activity 的 UI 位于屏幕最頂層突委,比如顯示類型 TYPE_APPLICATION_OVERLAY 的窗口
還有 38% 的精彩內(nèi)容
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
支付 ¥9.99 繼續(xù)閱讀
  • 序言:七十年代末柏卤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子匀油,更是在濱河造成了極大的恐慌缘缚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敌蚜,死亡現(xiàn)場離奇詭異桥滨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弛车,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門该园,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人帅韧,你說我怎么就攤上這事】忻悖” “怎么了忽舟?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淮阐。 經(jīng)常有香客問我叮阅,道長,這世上最難降的妖魔是什么泣特? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任浩姥,我火速辦了婚禮,結(jié)果婚禮上状您,老公的妹妹穿的比我還像新娘勒叠。我一直安慰自己,他們只是感情好膏孟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布眯分。 她就那樣靜靜地躺著,像睡著了一般柒桑。 火紅的嫁衣襯著肌膚如雪弊决。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天魁淳,我揣著相機(jī)與錄音飘诗,去河邊找鬼。 笑死界逛,一個胖子當(dāng)著我的面吹牛昆稿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仇奶,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼貌嫡,長吁一口氣:“原來是場噩夢啊……” “哼比驻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岛抄,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤别惦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后夫椭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掸掸,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年蹭秋,在試婚紗的時候發(fā)現(xiàn)自己被綠了扰付。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡仁讨,死狀恐怖羽莺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洞豁,我是刑警寧澤盐固,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站丈挟,受9級特大地震影響刁卜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜曙咽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一蛔趴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧例朱,春花似錦孝情、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至烁竭,卻和暖如春菲茬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背派撕。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工婉弹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人终吼。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓镀赌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親际跪。 傳聞我的和親對象是個殘疾皇子商佛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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