Android系統(tǒng)進(jìn)程優(yōu)先級(jí)策略-ADJ

本篇文章是基于Android9.0來(lái)介紹Android系統(tǒng)關(guān)于進(jìn)程的優(yōu)先級(jí)是如何定義和管理的。

概述

1.進(jìn)程

進(jìn)程-Process 是程序的一個(gè)運(yùn)行實(shí)例缆娃。通常會(huì)有唯一一個(gè)pid與之對(duì)應(yīng)跑芳。但pid不是絕對(duì)唯一的模暗,當(dāng)進(jìn)程死亡后pid會(huì)被回收給另外的進(jìn)程使用斑粱。在Android世界里三热,App開(kāi)發(fā)者很容易認(rèn)為系統(tǒng)的四大組件就是進(jìn)程的載體,實(shí)際上三幻,它們不能算是完整的進(jìn)程實(shí)例就漾,最多只能算是進(jìn)程的組成部分。由于Android系統(tǒng)框架中念搬,系統(tǒng)對(duì)進(jìn)程的創(chuàng)建和管理進(jìn)行了封裝抑堡,每當(dāng)我們?cè)趩?dòng)四大組件Activity, BroadcastReceiver,ContentProvider,Service任意一個(gè)的時(shí)候,系統(tǒng)就會(huì)去檢查該組件所在的進(jìn)程是否已經(jīng)存在朗徊,如果不存在首妖,框架就會(huì)自動(dòng)調(diào)用startProcessLocked函數(shù)去創(chuàng)建進(jìn)程。當(dāng)然一個(gè)app可以存在多個(gè)進(jìn)程爷恳,多個(gè)app也可以運(yùn)行在同一個(gè)進(jìn)程下有缆,我們可以通過(guò)Android:process來(lái)設(shè)置。

2.優(yōu)先級(jí)

Android系統(tǒng)框架設(shè)計(jì)理念里是希望對(duì)用戶很重要的進(jìn)程盡可能的長(zhǎng)期存活温亲,以此來(lái)提高用戶體驗(yàn)棚壁。我們知道Android app在啟動(dòng)的時(shí)候會(huì)去做很多事情,比如系統(tǒng)會(huì)檢測(cè)該app進(jìn)程是否存在栈虚,如果不存在則需要通知zygote進(jìn)程去fork app進(jìn)程袖外,同時(shí)完成application信息的初始化工作。此時(shí)如果app的進(jìn)程存在魂务,那么就會(huì)省略這個(gè)步驟曼验,直接快速喚起app。對(duì)于app來(lái)說(shuō)也是一樣粘姜,通過(guò)進(jìn)程摈拚眨活可以去實(shí)現(xiàn)更多的功能,但是如果所有的app不管有用沒(méi)用的都存活下來(lái)了相艇,系統(tǒng)的資源畢竟是有限的颖杏,系統(tǒng)內(nèi)存很快就會(huì)枯竭而亡,這時(shí)就需要有合理地進(jìn)程回收機(jī)制了坛芽。那么究竟該怎么回收進(jìn)程呢留储?其實(shí)系統(tǒng)是根據(jù)各個(gè)app四大組件的狀態(tài)來(lái)決定進(jìn)程的優(yōu)先級(jí)值adj翼抠。系統(tǒng)會(huì)根據(jù)一定的策略先去回收優(yōu)先級(jí)最低的(同時(shí)也是adj值最大的),其次再回收優(yōu)先級(jí)略低的获讳,依次類(lèi)推阴颖,直到回收了足夠的系統(tǒng)資源,保證系統(tǒng)正常運(yùn)轉(zhuǎn)丐膝。

3.adj取值范圍及含義

ADJ級(jí)別 取值 含義
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 前臺(tái)進(jìn)程
VISIBLE_APP_ADJ 100 可見(jiàn)進(jìn)程
PERCEPTIBLE_APP_ADJ 200 可感知進(jìn)程量愧,比如后臺(tái)音樂(lè)播放
BACKUP_APP_ADJ 300 備份進(jìn)程
HEAVY_WEIGHT_APP_ADJ 400 重量級(jí)進(jìn)程
SERVICE_ADJ 500 服務(wù)進(jìn)程
HOME_APP_ADJ 600 Home進(jìn)程
PREVIOUS_APP_ADJ 700 上一個(gè)進(jìn)程
SERVICE_B_ADJ 800 B List中的Service
CACHED_APP_MIN_ADJ 900 不可見(jiàn)進(jìn)程的adj最小值
CACHED_APP_MAX_ADJ 906 不可見(jiàn)進(jìn)程的adj最大值

從Android 7.0開(kāi)始,ADJ采用100帅矗、200偎肃、300;在這之前的版本ADJ采用數(shù)字1浑此、2累颂、3,這樣的調(diào)整可以更進(jìn)一步地細(xì)化進(jìn)程的優(yōu)先級(jí)凛俱,比如在VISIBLE_APP_ADJ(100)與PERCEPTIBLE_APP_ADJ(200)之間紊馏,可以有ADJ=101、102級(jí)別的進(jìn)程蒲犬。

關(guān)于進(jìn)程的優(yōu)先級(jí)可以通過(guò)如下的命令查看

adb shell
cd /proc/pid/
oom_adj   oom_score  oom_score_adj
cat oom_adj
0 表示前臺(tái)進(jìn)程FOREGROUND_APP_ADJ

Android 7.0之前的版本:
oom_score -- 是該進(jìn)程的最終得分朱监,分?jǐn)?shù)越高,越容易被殺死原叮;
oom_score_adj -- 范圍:[-1000, 1000]赫编,是kernel用來(lái)配置進(jìn)程優(yōu)先級(jí)的。值越低篇裁,最終的oom_score越低沛慢。
oom_adj -- 范圍:[-16, 15],是給用戶來(lái)配置進(jìn)程優(yōu)先級(jí)的达布。為了方便用戶配置团甲,提供了范圍更小的oom_adj參數(shù),數(shù)字越小優(yōu)先級(jí)越高黍聂,-17表示該進(jìn)程被保護(hù)躺苦,不被OOMKiller殺死。
kernel會(huì)轉(zhuǎn)換并更新該進(jìn)程實(shí)際的oom_score_adj值产还,它們的換算關(guān)系是:

#define OOM_DISABLE (-17)
    #define OOM_SCORE_ADJ_MAX 1000

    oom_score_adj = (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
    oom_adj = (oom_score_adj * - OOM_DISABLE) / OOM_SCORE_ADJ_MAX;

Android 7.0之前的版本匹厘,oom_score_adj= oom_adj * 1000/17; 而Android 7.0開(kāi)始脐区,oom_score_adj= oom_adj愈诚,不用再經(jīng)過(guò)一次轉(zhuǎn)換。

4.LowMemoryKiller

Android的Low Memory Killer基于Linux的OOM(Out Of Memory Killer)機(jī)制,在Linux中炕柔,內(nèi)存是以頁(yè)面為單位分配的酌泰,OOM的策略更多的是用于分配內(nèi)存不足時(shí)觸發(fā),將得分最高的進(jìn)程殺掉匕累。而lmk則會(huì)每隔一段時(shí)間檢查一次陵刹,當(dāng)系統(tǒng)剩余可用內(nèi)存較低時(shí),便會(huì)觸發(fā)殺進(jìn)程的策略欢嘿,根據(jù)不同的剩余內(nèi)存檔位來(lái)選擇殺不同優(yōu)先級(jí)的進(jìn)程衰琐,而不是等到OOM時(shí)再來(lái)殺進(jìn)程,真正OOM時(shí)系統(tǒng)可能已經(jīng)處于異常狀態(tài)炼蹦,系統(tǒng)更希望的是未雨綢繆羡宙,在內(nèi)存很低時(shí)來(lái)殺掉一些優(yōu)先級(jí)較低的進(jìn)程來(lái)保障后續(xù)操作的順利進(jìn)行。這里不詳細(xì)說(shuō)掐隐,接下來(lái)會(huì)有一篇關(guān)于Low Memory Killer的文章來(lái)詳細(xì)介紹辛辨。
那么在哪里可以看到各個(gè)剩余內(nèi)存檔位呢?可以通過(guò)如下方式:

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
// These are the various interesting memory levels that we will give to
// the OOM killer.  Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
        FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
        BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
從ProcessList.java中關(guān)于mOomAdj的定義及注釋中我們可以發(fā)現(xiàn)瑟枫,
系統(tǒng)一共定義了6個(gè)檔位,
分別對(duì)應(yīng)了上表格中列出的幾種進(jìn)程類(lèi)型指攒。
adb shell
cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906

adj值對(duì)應(yīng)如下:
FOREGROUND_APP_ADJ(0)
VISIBLE_APP_ADJ(100)
PERCEPTIBLE_APP_ADJ(200)
BACKUP_APP_ADJ(300)
CACHED_APP_MIN_ADJ(900)
CACHED_APP_MAX_ADJ(906)

cat /sys/module/lowmemorykiller/parameters/minfree
18432,23040,27648,32256,85296,120640
由于一個(gè)page=4k,對(duì)應(yīng)的換算公式是:value*4/1024,換算結(jié)果如下:
72M,90M,108M,126M,144M,180M

剩余內(nèi)存值檔位對(duì)應(yīng)如下:
FOREGROUND_APP_ADJ(72M)
VISIBLE_APP_ADJ(90M)
PERCEPTIBLE_APP_ADJ(108M)
BACKUP_APP_ADJ(126M)
CACHED_APP_MIN_ADJ(144M)
CACHED_APP_MAX_ADJ(180M)

進(jìn)程剛啟動(dòng)時(shí)ADJ等于INVALID_ADJ慷妙,當(dāng)執(zhí)行完attachApplication(),該該進(jìn)程的curAdj和setAdj不相等允悦,則會(huì)觸發(fā)執(zhí)行setOomAdj()將該進(jìn)程的節(jié)點(diǎn)/proc/pid/oom_score_adj寫(xiě)入oomadj值膝擂。舉例:當(dāng)系統(tǒng)剩余空閑內(nèi)存低于某閾值(比如140MB),則從ADJ大于或等于相應(yīng)閾值(比如900)的進(jìn)程中隙弛,選擇ADJ值最大的進(jìn)程架馋,如果存在多個(gè)ADJ相同的進(jìn)程,則選擇內(nèi)存最大的進(jìn)程全闷。
在updateOomLevels()過(guò)程叉寂,會(huì)根據(jù)手機(jī)屏幕尺寸或內(nèi)存大小來(lái)調(diào)整scale,默認(rèn)大多數(shù)手機(jī)內(nèi)存都大于700MB总珠,則scale等于1屏鳍。對(duì)于64位手機(jī),cached進(jìn)程的閾值會(huì)更大些局服。

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
        // Scale buckets from avail memory: at 300MB we use the lowest values to
        // 700MB or more for the top values.
        float scaleMem = ((float)(mTotalMemMb-350))/(700-350);
        ...
        // Scale buckets from screen size.
        int minSize = 480*800;  //  384000
        int maxSize = 1280*800; // 1024000  230400 870400  .264
        float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
        ...
        float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
        if (scale < 0) scale = 0;
        else if (scale > 1) scale = 1;
        ...
        final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
        for (int i=0; i<mOomAdj.length; i++) {
            int low = mOomMinFreeLow[i];
            int high = mOomMinFreeHigh[i];
            if (is64bit) {
                // Increase the high min-free levels for cached processes for 64-bit
                if (i == 4) high = (high*3)/2;
                else if (i == 5) high = (high*7)/4;
            }
            mOomMinFree[i] = (int)(low + ((high-low)*scale));
        }
}

ADJ詳解

1.ADJ優(yōu)先級(jí)小于0的钓瞭,就是說(shuō)正常情況下肯定不會(huì)被殺死,即使被殺死了也會(huì)被重啟的進(jìn)程淫奔。
  • NATIVE_ADJ(-1000):是由init進(jìn)程fork出來(lái)的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)程一般情況并不會(huì)被殺,即便被殺或者發(fā)生Crash系統(tǒng)會(huì)立即重新拉起該進(jìn)程
  • PERSISTENT_SERVICE_ADJ(-700):是由startIsolatedProcess()方式啟動(dòng)的進(jìn)程鸭丛,或者是由system_server或者persistent進(jìn)程所綁定(并且?guī)в蠦IND_ABOVE_CLIENT或者BIND_IMPORTANT)的服務(wù)進(jìn)程

2.ADJ優(yōu)先級(jí)大于等于0的

  • FOREGROUND_APP_ADJ(0): 正在前臺(tái)運(yùn)行的進(jìn)程竞穷,是不應(yīng)該被干掉的進(jìn)程
    1.正處于resumed狀態(tài)的Activity; app.adjType = "top-activity"
    2.正執(zhí)行一個(gè)生命周期回調(diào)的Service(比如執(zhí)行onCreate,onStartCommand,onDestroy等); app.adjType = "exec-service";
    3.正執(zhí)行onReceive()的BroadcastReceiver; app.adjType = "broadcast";
    4.通過(guò)startInstrumentation()啟動(dòng)的進(jìn)程; app.adjType = "instrumentation"
    5.當(dāng)客戶端進(jìn)程activity里面調(diào)用bindService()方法時(shí)flags帶有BIND_ADJUST_WITH_ACTIVITY參數(shù),并且該activity處于可見(jiàn)狀態(tài)系吩,則當(dāng)前服務(wù)進(jìn)程也屬于前臺(tái)進(jìn)程; app.adjType = "service";
    6.對(duì)于provider進(jìn)程来庭,還有以下兩個(gè)條件能成為前臺(tái)進(jìn)程:

    • 當(dāng)Provider的客戶端進(jìn)程ADJ<=FOREGROUND_APP_ADJ時(shí),則Provider進(jìn)程ADJ等于FOREGROUND_APP_ADJ; adjType = "provider";
    • 當(dāng)Provider有外部進(jìn)程依賴(lài)穿挨,也就是調(diào)用了getContentProviderExternal()方法月弛,則ADJ至少等于FOREGROUND_APP_ADJ; app.adjType = "ext-provider";
  • VISIBLE_APP_ADJ(100): 可見(jiàn)進(jìn)程,比如生命周期還沒(méi)有執(zhí)行到onstop的activity,有widget組件在桌面的等還是可以被用戶看到的; app.adjType = "vis-activity";
    從Android P開(kāi)始科盛,進(jìn)一步細(xì)化ADJ級(jí)別帽衙,增加了VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1=99,是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之間有99個(gè)槽贞绵,則可見(jiàn)級(jí)別ADJ的取值范圍為[100,199]厉萝。 算法會(huì)根據(jù)其所在task的mLayerRank來(lái)調(diào)整其ADJ,100加上mLayerRank就等于目標(biāo)ADJ榨崩,layer越大谴垫,則ADJ越小。
    關(guān)于TaskRecord的mLayerRank的計(jì)算方式是在updateOomAdjLocked()過(guò)程調(diào)用ASS的rankTaskLayersIfNeeded()方法

  • PERCEPTIBLE_APP_ADJ(200): 當(dāng)該進(jìn)程存在不可見(jiàn)的Activity母蛛,但Activity正處于PAUSING翩剪、PAUSED、STOPPING狀態(tài)彩郊,則為PERCEPTIBLE_APP_ADJ前弯;app.adjType = "pause-activity" or app.adjType = "stop-activity";
    滿足以下任一條件的進(jìn)程也屬于可感知進(jìn)程:
    1.foregroundServices非空:前臺(tái)服務(wù)進(jìn)程,執(zhí)行startForegroundService()方法; app.adjType = "fg-service";
    2.app.forcingToImportant非空:執(zhí)行setProcessImportant()方法秫逝,比如Toast彈出過(guò)程; app.adjType = "force-imp";
    3.hasOverlayUi非空:非activity的UI位于屏幕最頂層恕出,比如顯示類(lèi)型TYPE_APPLICATION_OVERLAY的窗口; app.adjType = "has-overlay-ui";

  • BACKUP_APP_ADJ(300):執(zhí)行bindBackupAgent()過(guò)程的進(jìn)程;
    1.執(zhí)行bindBackupAgent()過(guò)程,設(shè)置mBackupTarget值违帆;
    2.執(zhí)行clearPendingBackup()或unbindBackupAgent()過(guò)程浙巫,置空mBackupTarget值;

  • HEAVY_WEIGHT_APP_ADJ(400): realStartActivityLocked()過(guò)程前方,當(dāng)應(yīng)用的privateFlags標(biāo)識(shí)PRIVATE_FLAG_CANT_SAVE_STATE的進(jìn)程狈醉;
    HEAVY_WEIGHT_APP一般是在后臺(tái)運(yùn)行,要避免它被干掉惠险。Value set in system/rootdir/init.rc on startup.

  • SERVICE_ADJ(500):沒(méi)有啟動(dòng)過(guò)Activity苗傅,并且30分鐘之內(nèi)活躍過(guò)的服務(wù)進(jìn)程。startRequested為true班巩,則代表執(zhí)行startService()且沒(méi)有stop的進(jìn)程渣慕; app.adjType = "started-services";

  • HOME_APP_ADJ(600):當(dāng)類(lèi)型為ACTIVITY_TYPE_HOME的應(yīng)用嘶炭,比如桌面APP

  • PREVIOUS_APP_ADJ(700):
    1.用戶上一個(gè)使用的包含UI的進(jìn)程,為了給用戶在兩個(gè)APP之間更好的切換體驗(yàn)逊桦,將上一個(gè)進(jìn)程ADJ設(shè)置到PREVIOUS_APP_ADJ的檔次眨猎。 當(dāng)activityStoppedLocked()過(guò)程會(huì)更新上一個(gè)應(yīng)用。
    2.當(dāng)provider進(jìn)程强经,上一次使用時(shí)間不超過(guò)20S的情況下睡陪,優(yōu)先級(jí)不低于PREVIOUS_APP_ADJ。provider進(jìn)程這個(gè)是Android 7.0以后新增的邏輯 匿情,這樣做的好處是在內(nèi)存比較低的情況下避免擁有provider的進(jìn)程出現(xiàn)顛簸兰迫,也就是啟動(dòng)后殺,然后又被拉炬称。

  • SERVICE_B_ADJ(800):進(jìn)程由SERVICE_ADJ(500)降低到SERVICE_B_ADJ(800)汁果,有以下兩種情況:
    1.A類(lèi)Service占比過(guò)高:當(dāng)A類(lèi)Service個(gè)數(shù) > Service總數(shù)的1/3時(shí),則加入到B類(lèi)Service玲躯。換句話說(shuō)据德,B Service的個(gè)數(shù)至少是A Service的2倍。
    2.內(nèi)存緊張&&A類(lèi)Service占用內(nèi)存較高:當(dāng)系統(tǒng)內(nèi)存緊張級(jí)別(mLastMemoryLevel)高于ADJ_MEM_FACTOR_NORMAL跷车,且該應(yīng)用所占內(nèi)存lastPss大于或等于CACHED_APP_MAX_ADJ級(jí)別所對(duì)應(yīng)的內(nèi)存閾值的1/3(默認(rèn)值閾值約等于110MB)
    內(nèi)存因子ADJ_MEM_FACTOR共有4個(gè)級(jí)別棘利,當(dāng)前處于哪個(gè)內(nèi)存因子級(jí)別,取決于當(dāng)前進(jìn)程中cached進(jìn)程和空進(jìn)程的個(gè)數(shù)

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;
}
內(nèi)存因子 取值 先決條件
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
  • 最大緩存進(jìn)程個(gè)數(shù):CUR_MAX_CACHED_PROCESSES = MAX_CACHED_PROCESSES = 32
  • 最大空進(jìn)程個(gè)數(shù): CUR_MAX_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/2 = 16
  • Trim空進(jìn)程上限:CUR_TRIM_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/4 = 8
  • Trim緩存進(jìn)程上限:CUR_TRIM_CACHED_PROCESSES = MAX_CACHED_PROCESSES/6 = 5

注:用于限制empty或cached進(jìn)程的上限為16個(gè)朽缴,
并且empty超過(guò)8個(gè)時(shí)會(huì)清理掉30分鐘沒(méi)有活躍的進(jìn)程赡译。 cached和empty主要是區(qū)別是否有Activity
系統(tǒng)會(huì)有相應(yīng)的log輸出:

//默認(rèn)cachedProcessLimit=16
if (numCached > cachedProcessLimit) {
    app.kill("cached #" + numCached, true);
}
//默認(rèn)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);
}
//默認(rèn)cachedProcessLimit=16
if (numEmpty > emptyProcessLimit) {
     app.kill("empty #" + numEmpty, true);
}
  • CACHED_APP_MIN_ADJ(900):
    緩存進(jìn)程優(yōu)先級(jí)從CACHED_APP_MIN_ADJ(900)到 CACHED_APP_MAX_ADJ(906)。
    ADJ的轉(zhuǎn)換算法:
    cached: 900, 901, 903, 905
    empty: 900, 902, 904, 906


    cache process

    1.foregroundActivities代表當(dāng)前不是前臺(tái)(FOREGROUND_APP_ADJ)進(jìn)程不铆,并且存在Activity的進(jìn)程,當(dāng)該Activity窗口不可見(jiàn)裹唆,并且不處于PAUSING(正在)誓斥、PAUSED(onPause個(gè))、STOPPING的任一狀態(tài)的情況下许帐,則設(shè)置該進(jìn)程為PROCESS_STATE_CACHED_ACTIVITY劳坑。
    app.adjType = "cch-act";
    2.當(dāng)該進(jìn)程Service的客戶端進(jìn)程存在Activity或者是treatLikeActivity的進(jìn)程,其進(jìn)程狀態(tài)都是cached進(jìn)程成畦。
    app.adjType = "cch-as-act";
    app.adjType = "cch-client-act";

總結(jié)

Android進(jìn)程優(yōu)先級(jí)ADJ的每一個(gè)ADJ級(jí)別往往都有多種場(chǎng)景距芬,使用adjType完美地區(qū)分相同ADJ下的不同場(chǎng)景; 不同ADJ進(jìn)程所對(duì)應(yīng)的schedGroup不同循帐,從而分配的CPU資源也不同框仔,schedGroup大體分為T(mén)OP(T)、前臺(tái)(F)拄养、后臺(tái)(B)离斩; ADJ跟AMS中的procState有著緊密的聯(lián)系。

adj:通過(guò)調(diào)整oom_score_adj來(lái)影響進(jìn)程壽命(Lowmemorykiller殺進(jìn)程策略);
schedGroup:影響進(jìn)程的CPU資源調(diào)度與分配跛梗;
procState:從進(jìn)程所包含的四大組件運(yùn)行狀態(tài)來(lái)評(píng)估進(jìn)程狀態(tài)寻馏,影響framework的內(nèi)存控制策略。比如控制緩存進(jìn)程和空進(jìn)程個(gè)數(shù)上限依賴(lài)于procState核偿,再比如控制APP執(zhí)行handleLowMemory()的觸發(fā)時(shí)機(jī)等诚欠。


from gityuan

CPU調(diào)度組:

調(diào)度級(jí)別 縮寫(xiě) 解釋
SCHED_GROUP_BACKGROUND(0) B 后臺(tái)進(jìn)程組
SCHED_GROUP_DEFAULT(1) F 前臺(tái)進(jìn)程組
SCHED_GROUP_TOP_APP(2) T TOP進(jìn)程組
SCHED_GROUP_TOP_APP_BOUND(3) T TOP進(jìn)程組
  1. 常說(shuō)的前臺(tái)進(jìn)程與后臺(tái)進(jìn)程,其實(shí)是從CPU調(diào)度角度來(lái)劃分的前臺(tái)與后臺(tái)漾岳;為了讓用戶正在使用的TOP進(jìn)程能分配到更多的CPU資源轰绵,從Android 6.0開(kāi)始新增了TOP進(jìn)程組,CPU調(diào)度優(yōu)先分配給當(dāng)前正在跟用戶交互的APP蝗羊,提升用戶體驗(yàn)藏澳。
  2. 上圖adjType=”broadcast”的CPU調(diào)度組的選擇取決于廣播隊(duì)列,當(dāng)receiver接收到的廣播來(lái)自于前臺(tái)廣播隊(duì)列則采用前臺(tái)進(jìn)程組耀找,當(dāng)receiver接收到的廣播來(lái)自于后臺(tái)廣播隊(duì)列則采用后臺(tái)進(jìn)程組翔悠。前后臺(tái)廣播隊(duì)列的CPU資源調(diào)度優(yōu)先級(jí)不同,所以前臺(tái)廣播超時(shí)10秒就會(huì)ANR野芒,而后臺(tái)廣播超時(shí)60秒才會(huì)ANR蓄愁。更少的CPU資源分配就需要更長(zhǎng)的時(shí)間來(lái)完成執(zhí)行,這也就是為何兩個(gè)廣播隊(duì)列定義了不同的超時(shí)閾值狞悲。
  3. 上圖adjType=”exec-service”的CPU調(diào)度組的選擇取決于caller, 當(dāng)發(fā)起bindService或者startService的調(diào)用者caller屬于后臺(tái)進(jìn)程組撮抓,callerFg=false,則Service的生命周期回調(diào)運(yùn)行在后臺(tái)進(jìn)程組摇锋,非常少的CPU資源丹拯;當(dāng)caller屬于前臺(tái)或者TOP進(jìn)程組,則Service的生命周期回調(diào)運(yùn)行在前臺(tái)進(jìn)程組荸恕,分配較多的CPU資源乖酬。
  4. 上圖adjType=”service”也有機(jī)會(huì)選擇TOP組, 前提條件是在bindService的時(shí)候帶有BIND_IMPORTANT的flags,用于標(biāo)記該服務(wù)對(duì)于客戶端進(jìn)程很重要融求。

對(duì)于app開(kāi)發(fā)者的建議:

  1. UI進(jìn)程與Service進(jìn)程一定要分離咬像,因?yàn)閷?duì)于包含activity的service進(jìn)程,一旦進(jìn)入后臺(tái)就成為”cch-started-ui-services”類(lèi)型的cache進(jìn)程(ADJ>=900)生宛,隨時(shí)可能會(huì)被系統(tǒng)回收县昂;而分離后的Service進(jìn)程服務(wù)屬于SERVICE_ADJ(500),被殺的可能性相對(duì)較小陷舅。尤其是系統(tǒng)允許自啟動(dòng)的服務(wù)進(jìn)程必須做UI分離倒彰,避免消耗系統(tǒng)較大內(nèi)存。
  2. 只有真正需要用戶可感知的應(yīng)用莱睁,才調(diào)用startForegroundService()方法來(lái)啟動(dòng)前臺(tái)服務(wù)狸驳,此時(shí)ADJ=PERCEPTIBLE_APP_ADJ(200)预明,常駐內(nèi)存,并且會(huì)在通知欄常駐通知提醒用戶耙箍,比如音樂(lè)播放撰糠,地圖導(dǎo)航。切勿為了常駐而濫用前臺(tái)服務(wù)辩昆,這會(huì)嚴(yán)重影響用戶體驗(yàn)阅酪。
  3. 進(jìn)程中的Service工作完成后,務(wù)必主動(dòng)調(diào)用stopService或stopSelf來(lái)停止服務(wù)汁针,避免占據(jù)內(nèi)存术辐,浪費(fèi)系統(tǒng)資源;
  4. 不要長(zhǎng)時(shí)間綁定其他進(jìn)程的service或者provider施无,每次使用完成后應(yīng)立刻釋放辉词,避免其他進(jìn)程常駐于內(nèi)存;
  5. APP應(yīng)該實(shí)現(xiàn)接口onTrimMemory()和onLowMemory()猾骡,根據(jù)TrimLevel適當(dāng)?shù)貙⒎潜仨殐?nèi)存在回調(diào)方法中加以釋放瑞躺。當(dāng)系統(tǒng)內(nèi)存緊張時(shí)會(huì)回調(diào)該接口,減少系統(tǒng)卡頓與殺進(jìn)程頻次兴想。
  6. 減少在贝鄙冢活上花心思,更應(yīng)該在優(yōu)化內(nèi)存上下功夫嫂便,因?yàn)樵谙嗤珹DJ級(jí)別的情況下捞镰,系統(tǒng)會(huì)選擇優(yōu)先殺內(nèi)存占用的進(jìn)程。

參考:
http://gityuan.com/2018/05/19/android-process-adj/
https://blog.csdn.net/kickxxx/article/details/13996565
https://blog.csdn.net/u012602304/article/details/79066000
https://blog.csdn.net/sinat_34606064/article/details/77932268
http://gityuan.com/2016/09/17/android-lowmemorykiller/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毙替,一起剝皮案震驚了整個(gè)濱河市岸售,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厂画,老刑警劉巖冰评,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異木羹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)解孙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)坑填,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人弛姜,你說(shuō)我怎么就攤上這事脐瑰。” “怎么了廷臼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵苍在,是天一觀的道長(zhǎng)绝页。 經(jīng)常有香客問(wèn)我,道長(zhǎng)寂恬,這世上最難降的妖魔是什么续誉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮初肉,結(jié)果婚禮上酷鸦,老公的妹妹穿的比我還像新娘。我一直安慰自己牙咏,他們只是感情好臼隔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著妄壶,像睡著了一般摔握。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丁寄,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天氨淌,我揣著相機(jī)與錄音,去河邊找鬼狡逢。 笑死宁舰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奢浑。 我是一名探鬼主播蛮艰,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼雀彼!你這毒婦竟也來(lái)了壤蚜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤徊哑,失蹤者是張志新(化名)和其女友劉穎袜刷,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體莺丑,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡著蟹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梢莽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萧豆。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖昏名,靈堂內(nèi)的尸體忽然破棺而出涮雷,到底是詐尸還是另有隱情,我是刑警寧澤轻局,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布洪鸭,位于F島的核電站样刷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏览爵。R本人自食惡果不足惜置鼻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拾枣。 院中可真熱鬧沃疮,春花似錦、人聲如沸梅肤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)姨蝴。三九已至俊啼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間左医,已是汗流浹背授帕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浮梢,地道東北人跛十。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秕硝,于是被迫代替她去往敵國(guó)和親芥映。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

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