Android進程保活-自“裁”或者耍流氓,呸!

App操作影響進程優先級

本篇文章是后臺殺死系列的最后一篇,主要探討一下進程的保活,Android本身設計的時候是非常善良的,它希望進程在不可見或者其他一些場景下APP要懂得主動釋放,可是Android低估了”貪婪“,尤其是很多國產APP,只希望索取來提高自己的性能,不管其他APP或者系統的死活,導致了很嚴重的資源浪費,這也是Android被iOS詬病的最大原因。本文的保活手段也分兩種:遵紀守法的進程保活與流氓手段換來的進程保活。

聲明:堅決反對流氓手段實現進程保活 堅決反對流氓進程保活 堅決反對流氓進程保活 “請告訴產品:無法進入白名單”

  • 正常守法的進程保活:內存裁剪(好學生APP要使用)
  • 流氓的進程保活,提高優先級(好學生APP別用)
  • 流氓的進程保活,雙Service進程相互喚醒(binder訃告原理)(好學生APP別用)
  • 普通的Service殺死喚醒手段(START_STICKY)

針對LowmemoryKiller所做的進程保活

LowmemoryKiller會在內存不足的時候掃描所有的用戶進程,找到不是太重要的進程殺死,至于LowmemoryKiller殺進程夠不夠狠,要看當前的內存使用情況,內存越少,下手越狠。在內核中,LowmemoryKiller.c定義了幾種內存回收等級如下:(也許不同的版本會有些不同)

static short lowmem_adj[6] = {
    0,
    1,
    6,
    12,
};
static int lowmem_adj_size = 4;

static int lowmem_minfree[6] = {
    3 * 512,    /* 6MB */
    2 * 1024,   /* 8MB */
    4 * 1024,   /* 16MB */
    16 * 1024,  /* 64MB */
};
static int lowmem_minfree_size = 4;

lowmem_adj中各項數值代表閾值的警戒級數,lowmem_minfree代表對應級數的剩余內存,兩者一一對應,比如當系統的可用內存小于6MB時,警戒級數為0;當系統可用內存小于8M而大于6M時,警戒級數為1;當可用內存小于64M大于16MB時,警戒級數為12。LowmemoryKiller就是根據當前系統的可用內存多少來獲取當前的警戒級數,如果進程的oom_adj大于警戒級數并且占內存最大,將會被優先殺死, 具有相同omm_adj的進程,則殺死占用內存較多的。omm_adj越小,代表進程越重要。一些前臺的進程,oom_adj會比較小,而后臺的服務,omm_adj會比較大,所以當內存不足的時候,Lowmemorykiller先殺掉的是后臺服務而不是前臺的進程。對于LowmemoryKiller的殺死,這里有一句話很重要,就是: 具有相同omm_adj的進程,則殺死占用內存較多的,因此,如果我們的APP進入后臺,就盡量釋放不必要的資源,以降低自己被殺的風險。那么如何釋放呢?onTrimeMemory是個不錯的時機,而onLowmemory可能是最后的稻草,下面復習一下,LowmemoryKiller如何殺進程的,簡單看一下實現源碼(4.3):(其他版本原理大同小異)

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
    ...     
    <!--關鍵點1 獲取free內存狀況-->
    int other_free = global_page_state(NR_FREE_PAGES);
    int other_file = global_page_state(NR_FILE_PAGES);
    <!--關鍵點2 找到min_adj -->
    for(i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }
  <!--關鍵點3 找到p->oomkilladj>min_adj并且oomkilladj最大,內存最大的進程-->
    for_each_process(p) {
        // 找到第一個大于等于min_adj的,也就是優先級比閾值低的
        if (p->oomkilladj < min_adj || !p->mm)
            continue;
        // 找到tasksize這個是什么呢
        tasksize = get_mm_rss(p->mm);
        if (tasksize <= 0)
            continue;
        if (selected) {
        // 找到優先級最低,并且內存占用大的
            if (p->oomkilladj < selected->oomkilladj)
                continue;
            if (p->oomkilladj == selected->oomkilladj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;
        lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
                     p->pid, p->comm, p->oomkilladj, tasksize);
    }
    if(selected != NULL) {...
        force_sig(SIGKILL, selected);
    }
    return rem;
}

這里先看一下關鍵點1,這里是內核獲取當前的free內存狀況,并且根據當前空閑內存計算出當前后臺殺死的等級(關鍵點2),之后LowmemoryKiller會遍歷所有的進程,找到優先級低并且內存占用較大的進程,如果這個進程的p->oomkilladj>min_adj,就表示這個進程可以殺死,LowmemoryKiller就會送過發送SIGKILL信號殺死就進程,注意,lmkd會先找優先級低的進程,如果多個進程優先級相同,就優先殺死內存占用高的進程,這樣就為我們提供了兩種進程包活手段

  • 1、提高進程的優先級,其實就是減小進程的p->oomkilladj(越小越重要)
  • 2、降低APP的內存占用量,在oom_adj相同的時候,會優先干掉內存消耗大的進程

不過大多數情況下,Android對于進程優先級的管理都是比較合理,即使某些場景需要特殊手段提高優先級,Android也是給了參考方案的,比如音頻播放,UI隱藏的時候,需要將Sevice進程設置成特定的優先級防止被后臺殺死,比如一些備份的進程也需要一些特殊處理,但是這些都是在Android允許的范圍內的,所以絕大多數情況下,Android是不建議APP自己提高優先級的,因為這會與Android自身的的進程管理相悖,換句話說就是耍流氓。這里先討論第二種情況,通過合理的釋放內存降低被殺的風險,地主不想被殺,只能交公糧,自裁保身,不過這里也要看自裁的時機,什么時候瘦身比較劃算,O(∩_∩)O哈哈~!這里就牽扯到有一個onTrimeMemory函數,該函數是一個系統回調函數,主要是Android系統經過綜合評估,給APP一個內存裁剪的等級,比如當內存還算充足,APP退回后臺時候,會收到TRIM_MEMORY_UI_HIDDEN等級的裁剪,就是告訴APP,釋放一些UI資源,比如大量圖片內存,一些引入圖片瀏覽緩存的場景,可能就更加需要釋放UI資源,下面來看下onTrimeMemory的回調時機及APP應該做出相應處理。

onTrimeMemory的回調時機及內存裁剪等級

OnTrimMemory是在Android 4.0引入的一個回調接口,其主要作用就是通知應用程序在不同的場景下進行自我瘦身,釋放內存,降低被后臺殺死的風險,提高用戶體驗,由于目前APP的適配基本是在14之上,所以不必考慮兼容問題。onTrimeMemory支持不同裁剪等級,比如,APP通過HOME建進入后臺時,其優先級(oom_adj)就發生變化,從未觸發onTrimeMemory回調,這個時候系統給出的裁剪等級一般是TRIM_MEMORY_UI_HIDDEN,意思是,UI已經隱藏,UI相關的、占用內存大的資源就可以釋放了,比如大量的圖片緩存等,當然,還會有其他很多場景對應不同的裁剪等級。因此,需要弄清楚兩個問題:

  • 1、不同的裁剪等級是如何生成的,其意義是什么
  • 2、APP如何根據不同的裁剪等級釋放內存資源,(自裁的程度)

先看下ComponentCallbacks2中定義的不同裁剪等級的意義:這里一共定義了4+3共7個裁剪等級,為什么說是4+3呢?因為有4個是針對后臺進程的,還有3個是針對前臺(RUNNING)進程的,目標對象不同,具體看下分析

裁剪等級 數值 目標進程
TRIM_MEMORY_COMPLETE 80 后臺進程
TRIM_MEMORY_MODERATE 60 后臺進程
TRIM_MEMORY_BACKGROUND 40 后臺進程
TRIM_MEMORY_UI_HIDDEN 20 后臺進程
TRIM_MEMORY_RUNNING_CRITICAL 15 前臺RUNNING進程
TRIM_MEMORY_RUNNING_LOW 10 前臺RUNNING進程
TRIM_MEMORY_RUNNING_MODERATE 5 前臺RUNNING進程

其意義如下:

  • TRIM_MEMORY_UI_HIDDEN 當前應用程序的所有UI界面不可見,一般是用戶點擊了Home鍵或者Back鍵,導致應用的UI界面不可見,這時應該釋放一些UI相關資源,TRIM_MEMORY_UI_HIDDEN是使用頻率最高的裁剪等級。官方文檔:the process had been showing a user interface, and is no longer doing so. Large allocations with the UI should be released at this point to allow memory to be better managed

  • TRIM_MEMORY_BACKGROUND 當前手機目前內存吃緊(后臺進程數量少),系統開始根據LRU緩存來清理進程,而該程序位于LRU緩存列表的頭部位置,不太可能被清理掉的,但釋放掉一些比較容易恢復的資源能夠提高手機運行效率,同時也能保證恢復速度。官方文檔:the process has gone on to the LRU list. This is a good opportunity to clean up resources that can efficiently and quickly be re-built if the user returns to the app

  • TRIM_MEMORY_MODERATE 當前手機目前內存吃緊(后臺進程數量少),系統開始根據LRU緩存來清理進程,而該程序位于LRU緩存列表的中間位置,應該多釋放一些內存提高運行效率。官方文檔:the process is around the middle of the background LRU list; freeing memory can help the system keep other processes running later in the list for better overall performance.

  • TRIM_MEMORY_COMPLETE 當前手機目前內存吃緊 (后臺進程數量少),系統開始根據LRU緩存來清理進程,而該程序位于LRU緩存列表的最邊緣位置,系統會先殺掉該進程,應盡釋放一切可以釋放的內存。官方文檔:the process is nearing the end of the background LRU list, and if more memory isn't found soon it will be killed.

以下三個等級針對前臺運行應用

  • TRIM_MEMORY_RUNNING_MODERATE 表示該進程是前臺或可見進程,正常運行,一般不會被殺掉,但是目前手機有些吃緊(后臺及空進程存量不多),系統已經開始清理內存,有必要的話,可以釋放一些內存。官方文檔:the process is not an expendable background process, but the device is running moderately low on memory. Your running process may want to release some unneeded resources for use elsewhere。

  • TRIM_MEMORY_RUNNING_LOW 表示該進程是前臺或可見進程,正常運行,一般不會被殺掉,但是目前手機比較吃緊(后臺及空進程被全干掉了一大波),應該去釋放掉一些不必要的資源以提升系統性能。 官方文檔:the process is not an expendable background process, but the device is running low on memory. Your running process should free up unneeded resources to allow that memory to be used elsewhere.

  • TRIM_MEMORY_RUNNING_CRITICAL 表示該進程是前臺或可見進程,但是目前手機比較內存十分吃緊(后臺及空進程基本被全干掉了),這時應當盡可能地去釋放任何不必要的資源,否則,系統可能會殺掉所有緩存中的進程,并且殺一些本來應當保持運行的進程。官方文檔:the process is not an expendable background process, but the device is running extremely low on memory and is about to not be able to keep any background processes running. Your running process should free up as many non-critical resources as it can to allow that memory to be used elsewhere. The next thing that will happen after this is called to report that nothing at all can be kept in the background, a situation that can start to notably impact the user.

以上抽象的說明了一下Android既定參數的意義,下面看一下onTrimeMemory回調的時機及原理,這里采用6.0的代碼分析,因為6.0比之前4.3的代碼清晰很多:當用戶的操作導致APP優先級發生變化,就會調用updateOomAdjLocked去更新進程的優先級,在更新優先級的時候,會掃描一遍LRU進程列表, 重新計算進程的oom_adj,并且參考當前系統狀況去通知進程裁剪內存(這里只是針對Android Java層APP),這次操作一般發生在打開新的Activity界面、退回后臺、應用跳轉切換等等,updateOomAdjLocked代碼大概600多行,比較長,盡量精簡后如下,還是比較長,這里拆分成一段段梳理:

final void updateOomAdjLocked() {
    final ActivityRecord TOP_ACT = resumedAppLocked();
    <!--關鍵點1 找到TOP——APP,最頂層顯示的APP-->
    final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
    final long oldTime = SystemClock.uptimeMillis() - ProcessList.MAX_EMPTY_TIME;
    mAdjSeq++;
    mNewNumServiceProcs = 0;
    final int emptyProcessLimit;
    final int hiddenProcessLimit;
    <!--關鍵點2 找到TOP——APP,最頂層顯示的APP-->
    // 初始化一些進程數量的限制:
    if (mProcessLimit <= 0) {
        emptyProcessLimit = hiddenProcessLimit = 0;
    } else if (mProcessLimit == 1) {
        emptyProcessLimit = 1;
        hiddenProcessLimit = 0;
    } else {
        // 空進程跟后臺非空緩存繼承的比例
        emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);
        cachedProcessLimit = mProcessLimit - emptyProcessLimit;
    }

     <!--關鍵點3 確定下進程槽 3個槽->
    int numSlots = (ProcessList.HIDDEN_APP_MAX_ADJ - ProcessList.HIDDEN_APP_MIN_ADJ + 1) / 2;
    // 后臺進程/前臺進程/空進程
    int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
     int emptyFactor = numEmptyProcs/numSlots;
    if (emptyFactor < 1) emptyFactor = 1;
    int hiddenFactor = (mNumHiddenProcs > 0 ? mNumHiddenProcs : 1)/numSlots;
    if (hiddenFactor < 1) hiddenFactor = 1;
    int stepHidden = 0;
    int stepEmpty = 0;
    int numHidden = 0;
    int numEmpty = 0;
    int numTrimming = 0;
    mNumNonHiddenProcs = 0;
    mNumHiddenProcs = 0;
    int i = mLruProcesses.size();
    // 優先級
    int curHiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ;
    // 初始化的一些值
    int nextHiddenAdj = curHiddenAdj+1;
    // 優先級
    int curEmptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ;
    // 有意思
    int nextEmptyAdj = curEmptyAdj+2;

這前三個關鍵點主要是做了一些準備工作,關鍵點1 是單獨抽離出TOP_APP,因為它比較特殊,系統只有一個前天進程,關鍵點2主要是根據當前的配置獲取后臺緩存進程與空進程的數目限制,而關鍵點3是將后臺進程分為三備份,無論是后臺進程還是空進程,會間插的均分6個優先級,一個優先級是可以有多個進程的,而且并不一定空進程的優先級小于HIDDEN進程優先級。

    for (int i=N-1; i>=0; i--) {
            ProcessRecord app = mLruProcesses.get(i);
            if (!app.killedByAm && app.thread != null) {
                app.procStateChanged = false;
                <!--關鍵點4 計算進程的優先級或者緩存進程的優先級->   
                // computeOomAdjLocked計算進程優先級,但是對于后臺進程和empty進程computeOomAdjLocked無效,這部分優先級是AMS自己根據LRU原則分配的
                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
                //還未最終確認,有些進程的優先級,比如只有后臺activity或者沒有activity的進程,
              <!--關鍵點5 計算進程的優先級或者緩存進程的優先級->   
                if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
                    switch (app.curProcState) {
                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                            app.curRawAdj = curCachedAdj;
                                    <!--關鍵點6 根據LRU為后臺進程分配優先級-->
                            if (curCachedAdj != nextCachedAdj) {
                                stepCached++;
                                if (stepCached >= cachedFactor) {
                                    stepCached = 0;
                                    curCachedAdj = nextCachedAdj;
                                    nextCachedAdj += 2;
                                    if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
                                        nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
                                    }
                                }
                            }
                            break;
                        default:
                                                                <!--關鍵點7 根據LRU為后臺進程分配優先級-->
                            app.curRawAdj = curEmptyAdj;
                            app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
                            if (curEmptyAdj != nextEmptyAdj) {
                                stepEmpty++;
                                if (stepEmpty >= emptyFactor) {
                                    stepEmpty = 0;
                                    curEmptyAdj = nextEmptyAdj;
                                    nextEmptyAdj += 2;
                                    if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
                                        nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
                                    }
                                }
                            }
                            break;
                    }
                }
                <!--關鍵點8 設置優先級-->
                applyOomAdjLocked(app, true, now, nowElapsed);

上面的這幾個關鍵點主要是為所有進程計算出其優先級oom_adj之類的值,對于非后臺進程,比如HOME進程 服務進程,備份進程等都有自己的獨特的計算方式,而剩余的后臺進程就根據LRU三等分配優先級。

                 <!--關鍵點9 根據緩存進程的數由AMS選擇性殺進程,后臺進程太多-->
                switch (app.curProcState) {
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                        mNumCachedHiddenProcs++;
                        numCached++;
                        if (numCached > cachedProcessLimit) {
                            app.kill("cached #" + numCached, true);
                        }
                        break;
                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                        if (numEmpty > ProcessList.TRIM_EMPTY_APPS
                                && app.lastActivityTime < oldTime) {
                            app.kill("empty for "
                                    + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
                                    / 1000) + "s", true);
                        } else {
                            numEmpty++;
                            if (numEmpty > emptyProcessLimit) {
                                app.kill("empty #" + numEmpty, true);
                            }
                        }
                        break;
                    default:
                        mNumNonCachedProcs++;
                        break;
                }
                 <!--關鍵點10 計算需要裁剪進程的數目-->
                if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
                        && !app.killedByAm) {
                        // 比home高的都需要裁剪,不包括那些等級高的進程
                    numTrimming++;
                }
            }
        }

上面的兩個關鍵點是看當前后臺進程是否過多或者過老,如果存在過多或者過老的后臺進程,AMS是有權利殺死他們的。之后才是我們比較關心的存活進程的裁剪:

        final int numCachedAndEmpty = numCached + numEmpty;
        int memFactor;
         <!--關鍵點11 根據后臺進程數目確定當前系統的內存使用狀況 ,確立內存裁剪等級(內存因子)memFactor,android的理念是準許存在一定數量的后臺進程,并且只有內存不夠的時候,才會縮減后臺進程-->
        if (numCached <= ProcessList.TRIM_CACHED_APPS
                && numEmpty <= ProcessList.TRIM_EMPTY_APPS) {
            // 等級高低 ,殺的越厲害,越少,需要約緊急的時候才殺
            if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {//3
                memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
            } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { //5
                memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
            } else {
                memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
            }
        } else {
            // 后臺進程數量足夠說明內存充足
            memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
        }
       <!--關鍵點12 根據內存裁剪等級裁剪內存 Android認為后臺進程不足的時候,內存也不足-->
        if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
            if (mLowRamStartTime == 0) {
                mLowRamStartTime = now;
            }
            int step = 0;
            int fgTrimLevel;
         // 內存不足的時候,也要通知前臺或可見進程進行縮減
            switch (memFactor) {
                case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
                    fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
                    break;
                case ProcessStats.ADJ_MEM_FACTOR_LOW:
                    fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
                    break;
                default:
                    fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
                    break;
            }
            int factor = numTrimming/3;
            int minFactor = 2;
            if (mHomeProcess != null) minFactor++;
            if (mPreviousProcess != null) minFactor++;
            if (factor < minFactor) factor = minFactor;
            int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;

關鍵點11這里不太好理解:Android系統根據后臺進程的數目來確定當前系統內存的狀況,后臺進程越多,越說明內存并不緊張,越少,說明越緊張,回收等級也就越高,如果后臺進程的數目較多,內存裁剪就比較寬松是ProcessStats.ADJ_MEM_FACTOR_NORMAL,如果不足,則再根據緩存數目劃分等級。以6.0源碼來說:

  • 如果后臺進程數量(包含空進程)< 3 ,就說明內存非常緊張,內存裁剪因子就是ProcessStats.ADJ_MEM_FACTOR_CRITICAL
  • 如果后臺進程數量(包含空進程)< 5 ,就說明內存非常緊張,內存裁剪因子就是ProcessStats.ADJ_MEM_FACTOR_LOW
  • 如果比上面兩個多,但是仍然不足正常的后臺數目 ,內存裁剪因子就是ProcessStats.ADJ_MEM_FACTOR_MODERATE

與之對應的關鍵點12,是確立前臺RUNNING進程(也不一定是前臺顯示)的裁剪等級。

  • ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
  • ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
  • ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;

之后就真正開始裁剪APP,這里先看后臺進程不足的情況的裁剪,這部分相對復雜一些:

            <!--裁剪后臺進程-->
            for (int i=N-1; i>=0; i--) {
                ProcessRecord app = mLruProcesses.get(i);
                if (allChanged || app.procStateChanged) {
                    setProcessTrackerStateLocked(app, trackerMemFactor, now);
                    app.procStateChanged = false;
                }   
                //  PROCESS_STATE_HOME = 12;  
                //PROCESS_STATE_LAST_ACTIVITY = 13; 退到后臺的就會用
                // 優先級比較低,回收等級比較高ComponentCallbacks2.TRIM_MEMORY_COMPLETE
                //  當curProcState > 12且沒有被am殺掉的情況;上面的update的時候,在kill的時候,是會設置app.killedByAm的
                //裁剪的話,如果 >= ActivityManager.PROCESS_STATE_HOME,老的裁剪等級較高,不重要,越新鮮的進程,裁剪等級越低

                if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
                        && !app.killedByAm) {
                     // 先清理最陳舊的 ,最陳舊的那個遭殃
                    if (app.trimMemoryLevel < curLevel && app.thread != null) {
                        try {
                            app.thread.scheduleTrimMemory(curLevel);
                        } catch (RemoteException e) {
                        }
                    }
                    app.trimMemoryLevel = curLevel;
                    step++; 
                    // 反正一共就三個槽,將來再次刷新的 時候,要看看是不是從一個槽里面移動到另一個槽,
                    // 沒有移動,就不需要再次裁剪,等級沒變
                    if (step >= factor) {
                        step = 0;
                        switch (curLevel) {
                            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                                curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
                                break;
                            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                                curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
                                break;
                        }
                    }
                }

上面的這部分是負責 app.curProcState >= ActivityManager.PROCESS_STATE_HOME這部分進程裁剪,主要是針對后臺緩存進程,一般是oom_adj在9-11之間的進程,根據LRU確定不同的裁減等級。

                else {
                    if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                            || app.systemNoUi) && app.pendingUiClean) {
                        // 釋放UI
                        final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
                        if (app.trimMemoryLevel < level && app.thread != null) {
                            try {
                                app.thread.scheduleTrimMemory(level);
                            } catch (RemoteException e) {
                            }
                        }
                        app.pendingUiClean = false;
                    }
                    // 啟動的時候會回調一遍,如果有必要,啟動APP的時候,app.trimMemoryLevel=0
                    if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {
                        try {
                            app.thread.scheduleTrimMemory(fgTrimLevel);
                        } catch (RemoteException e) {
                        }
                    }
                    app.trimMemoryLevel = fgTrimLevel;
                }
            }
        } 

而這里的裁剪主要是一些優先級較高的進程,其裁剪一般是 ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN ,由于這部分進程比較重要,裁剪等級較低,至于前臺進程的裁剪,一般是在啟動的時候,這個時候app.pendingUiClean==false,只會裁剪當前進程:

        else {
              <!--關鍵點13 內存充足的時候,進程的裁剪-->
             ... 
            for (int i=N-1; i>=0; i--) {
                ProcessRecord app = mLruProcesses.get(i);
                // 在resume的時候,都是設置成true,所以退回后臺的時候app.pendingUiClean==true是滿足的,
                // 因此縮減一次,但是不會再次走這里的分支縮減即使優先級變化,但是已經縮減過
                // 除非走上面的后臺流程,那個時候這個進程的等級已經很低了,
                if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                        || app.systemNoUi) && app.pendingUiClean) {
                    if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
                            && app.thread != null) {
                        try {
                            app.thread.scheduleTrimMemory(
                                    ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
                        } catch (RemoteException e) {
                        }
                    }
                    // clean一次就弄成false
                    app.pendingUiClean = false;
                }
                // 基本算沒怎么裁剪
                app.trimMemoryLevel = 0;
            }
        }
 }

最后這部分是后臺進程數量充足的時候,系統只會針對app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND的進程進行裁剪,而裁剪等級也較低:ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,因此根據裁剪等級APP可以大概知道系統當前的內存狀況,同時也能知道系統希望自己如何裁剪,之后APP做出相應的瘦身即可。不過,上面的進程裁剪的優先級是完全根據后臺進程數量來判斷的,但是,不同的ROM可能進行了改造,所以裁剪等級不一定完全準確,比如在開發者模式打開限制后臺進程數量的選項,限制后臺進程數目不超過2個,那么這個時候的裁剪等級就是不太合理的,因為內存可能很充足,但是由于限制了后臺進程的數量,導致裁剪等級過高。因此在使用的時候,最好結合裁剪等級與當前內存數量來綜合考量。

通過“流氓”手段提高oom_adj,降低被殺風險,化身流氓進程

進程優先級的計算Android是有自己的一條準則的,某些特殊場景的需要額外處理進程的oom_adj Android也是給了參考方案的。但是,那對于流氓來說,并沒有任何約束效力。 "流氓"仍然能夠參照oom_adj(優先級)的計算規則,利用其漏洞,提高進程的oom_adj,以降低被殺的風險。如果單單降低被殺風險還好,就怕那種即不想死,又想占用資源的APP,累積下去就會導致系統內存不足,導致整個系統卡頓。

優先級的計算邏輯比較復雜,這里只簡述非緩存進程,因為一旦淪為緩存進程,其優先級就只能依靠LRU來計算,不可控。而流氓是不會讓自己淪為緩存進程的,非緩存進程是以下進程中的一種,并且,優先級越高(數值越小),越不易被殺死:

ADJ優先級 優先級 進程類型
SERVICE_ADJ 5 服務進程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 后臺的重量級進程,system/rootdir/init.rc文件中設置
BACKUP_APP_ADJ 3 備份進程(這個不太了解)
PERCEPTIBLE_APP_ADJ 2 可感知進程,比如后臺音樂播放 ,通過startForeground設置的進程
VISIBLE_APP_ADJ 1 可見進程(可見,但是沒能獲取焦點,比如新進程僅有一個懸浮Activity,其后面的進程就是Visible process)
FOREGROUND_APP_ADJ 0 前臺進程(正在展示是APP,存在交互界面,Foreground process)
  • 第一種提高到FOREGROUND_APP_ADJ

我們從低到高看:如何讓進程編程FOREGROUND_APP_ADJ進程,也就是前臺進程,這個沒有別的辦法,只有TOP activity進程才能是算前臺進程。正常的交互邏輯下,這個是無法實現的,鎖屏的時候倒是可以啟動一個Activity,但是需要屏幕點亮的時候再隱藏,容易被用戶感知,得不償失,所以基本是無解,所以之前傳說的QQ通過一個像素來保活的應該不是這種方案,而通過WindowManager往主屏幕添加View的方式也并未阻止進程被殺,到底是否通過一像素實現進程包活,個人還未得到解答,希望能有人解惑。

  • 第二種,提高到VISIBLE_APP_ADJ或者PERCEPTIBLE_APP_ADJ(不同版本等級可能不同 “4.3 = PERCEPTIBLE_APP_ADJ” 而 “> 5.0 = VISIBLE_APP_ADJ”),就表現形式上看,微博、微等信都可能用到了,而且這種手段的APP一般很難殺死,就算從最近的任務列表刪除,其實進程還是沒有被殺死,只是殺死了Activity等組件。

先看一下源碼中對兩種優先級的定義,VISIBLE_APP_ADJ是含有可見但是非交互Activity的進程,PERCEPTIBLE_APP_ADJ是用戶可感知的進程,如后臺音樂播放等

    // This is a process only hosting components that are perceptible to the
    // user, and we really want to avoid killing them, but they are not
    // immediately visible. An example is background music playback.
    static final int PERCEPTIBLE_APP_ADJ = 2;

    // This is a process only hosting activities that are visible to the
    // user, so we'd prefer they don't disappear.
    static final int VISIBLE_APP_ADJ = 1;

這種做法是相對溫和點的,Android官方曾給過類似的方案,比如音樂播放時后,通過設置前臺服務的方式來保活,這里就為流氓進程提供了入口,不過顯示一個常住服務會在通知欄上有個運行狀態的圖標,會被用戶感知到。但是Android恰恰還有個漏洞可以把該圖標移除,真不知道是不是Google故意的。這里可以參考微信的保活方案:雙Service強制前臺進程保活

startForeground(ID, new Notification()),可以將Service變成前臺服務,所在進程就算退到后臺,優先級只會降到PERCEPTIBLE_APP_ADJ或者VISIBLE_APP_ADJ,一般不會被殺掉,Android的有個漏洞,如果兩個Service通過同樣的ID設置為前臺進程,而其一通過stopForeground取消了前臺顯示,結果是保留一個前臺服務,但不在狀態欄顯示通知,這樣就不會被用戶感知到耍流氓,這種手段是比較常用的流氓手段。優先級提高后,AMS的killBackgroundProcesses已經不能把進程殺死了,它只會殺死oom_adj大于ProcessList.SERVICE_ADJ的進程,而最近的任務列表也只會清空Activity,無法殺掉進程。 因為后臺APP的優先級已經提高到了PERCEPTIBLE_APP_ADJ或ProcessList.VISIBLE_APP_ADJ,可謂流氓至極,如果再占據著內存不釋放,那就是潑皮無賴了,這里有個遺留疑問:startForeground看源碼只會提升到PERCEPTIBLE_APP_ADJ,但是在5.0之后的版本提升到了VISIBLE_APP_ADJ,這里看源碼,沒找到原因,希望有人能解惑。具體做法如下:

public class RogueBackGroundService extends Service {

    private static int ROGUE_ID = 1;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent intent = new Intent(this, RogueIntentService.class);
        startService(intent);
        startForeground(ROGUE_ID, new Notification());
    }
    public static class RogueIntentService extends IntentService {

        //流氓相互喚醒Service
        public RogueIntentService(String name) {
            super(name);
        }

        public RogueIntentService() {
            super("RogueIntentService");
        }

        @Override
        protected void onHandleIntent(Intent intent) {

        }
        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(ROGUE_ID, new Notification());
        }   
       @Override
        public void onDestroy() {
            stopForeground(true); 
            super.onDestroy();
        }
    }
}

不過這個漏洞在Android7.1之后失效了,因為Google加了一個校驗:如果還有Service通過setForeground綁定相同id的Notification,就不能cancelNotification,也就是說還是會顯示通知(在通知列表)。

 private void cancelForegroudNotificationLocked(ServiceRecord r) {
        if (r.foregroundId != 0) {
            // First check to see if this app has any other active foreground services
            // with the same notification ID.  If so, we shouldn't actually cancel it,
            // because that would wipe away the notification that still needs to be shown
            // due the other service.
            ServiceMap sm = getServiceMap(r.userId);
            if (sm != null) {
            <!--查看是不是與該ID 通知綁定的Service取消了了前臺顯示-->
                for (int i = sm.mServicesByName.size()-1; i >= 0; i--) {
                    ServiceRecord other = sm.mServicesByName.valueAt(i);
                    if (other != r && other.foregroundId == r.foregroundId
                            && other.packageName.equals(r.packageName)) {
                        // Found one!  Abort the cancel.
                        <!--如果找到還有顯示的Service,直接返回-->
                        return;
                    }
                }
            }
            r.cancelNotification();
        }
    }

在7.1上,Google PlayStore渠道的微信似乎也放棄了這種保活手段,因為7.1的微信從最近的任務列表刪除是可以殺死進程的,如果采用上述手段是殺不了的。

雙Service守護進程保活(這個也很流氓,不過如果不提高優先級(允許被殺),也算稍微良心)

前文我們分析過Android Binder的訃告機制:如果Service Binder實體的進程掛掉,系統會向Client發送訃告,而這個訃告系統就給進程保活一個可鉆的空子。可以通過兩個進程中啟動兩個binder服務,并且互為C/S,一旦一個進程掛掉,另一個進程就會收到訃告,在收到訃告的時候,喚起被殺進程。邏輯如下下:

雙服務保活.jpg

首先編寫兩個binder實體服務PairServiceA ,PairServiceB,并且在onCreate的時候相互綁定,并在onServiceDisconnected收到訃告的時候再次綁定。

public class PairServiceA extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new AliveBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        bindService(new Intent(PairServiceA.this, PairServiceB.class), mServiceConnection, BIND_AUTO_CREATE);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bindService(new Intent(PairServiceA.this, PairServiceB.class), mServiceConnection, BIND_AUTO_CREATE);
            ToastUtil.show("bind A");
        }
    };

與之配對的B

public class PairServiceB extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new AliveBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        bindService(new Intent(PairServiceB.this, PairServiceA.class), mServiceConnection, BIND_AUTO_CREATE);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bindService(new Intent(PairServiceB.this, PairServiceA.class), mServiceConnection, BIND_AUTO_CREATE);
        }
    };
}

之后再Manifest中注冊,注意要進程分離

    <service android:name=".service.alive.PairServiceA"/>
    <service
        android:name=".service.alive.PairServiceB"
        android:process=":alive"/>

之后再Application或者Activity中啟動一個Service即可。

startService(new Intent(MainActivity.this, PairServiceA.class));

這個方案一般都沒問題,因為Binder訃告是系統中Binder框架自帶的,可能一次性全部殺死所有父子進程會不行,這個沒測試過。不過這個手段也能在一定程度上提高有限級, 最近的任務列表已經不能殺死進程了。原因如下:

此時APP內至少兩個進程A\B ,并且AB相互通過bindService綁定,此時就是互為客戶端,在oom_adj中有這么一種計算邏輯,如果進程A的Service被B通過bind綁定,那么A的優先級可能會受到B的影響,因為在計算A的時候需要先計算B,但是B同樣是A的Service,反過來有需要計算A,如果不加額外的判斷,就會出現死循環,AMS是通過一個計數來標識的:mAdjSeq == app.adjSeq。于是流程就是這樣

  • 計算A:發現依賴B,去計算B
  • 計算B:發現依賴A,回頭計算A
  • 計算A:發現A正在計算,直接返回已經計算到一半的A優先級

上面的流程能保證不出現死循環,并且由于A只計算了一半,所以A的很多東西沒有更新,所以B拿到的A就是之前的數值,比如 curProcState、curSchedGroup:

private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
        boolean doingAll, long now) {
    if (mAdjSeq == app.adjSeq) {
        // This adjustment has already been computed.
        return app.curRawAdj;
    }
    ....
      for (int is = app.services.size()-1;
            is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                    || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
                    || procState > ActivityManager.PROCESS_STATE_TOP);
            is--) {
        ServiceRecord s = app.services.valueAt(is);
       ...
        for (int conni = s.connections.size()-1;
                conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                        || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
                        || procState > ActivityManager.PROCESS_STATE_TOP);
                conni--) {
            ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);
            for (int i = 0;
                    i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
                            || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
                            || procState > ActivityManager.PROCESS_STATE_TOP);
                    i++) {
                ConnectionRecord cr = clist.get(i);

                if (cr.binding.client == app) {
                    // Binding to ourself is not interesting.
                    continue;
                }
                if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
                    ProcessRecord client = cr.binding.client;
                    // 這里會不會出現死循環的問題呢? A需要B的計算、B需要A的計算,這個圓環也許就是為什么
                    // 無法左滑刪除的原因 循環的,
                    <!--關鍵點1 -->
                    int clientAdj = computeOomAdjLocked(client, cachedAdj,
                            TOP_APP, doingAll, now);

                    int clientProcState = client.curProcState;
                    if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
                        clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
                    }
                   <!--關鍵點2-->
                            ...
                    if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
                        if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
                            schedGroup = Process.THREAD_GROUP_DEFAULT;
                        }
                ...        
        }

上面的代碼中:關鍵點1是循環計算的入口,關鍵點2是無法刪除的原因所在,由于A沒及時更新,導致schedGroup = Process.THREAD_GROUP_DEFAULT,反過來也讓A保持schedGroup = Process.THREAD_GROUP_DEFAULT。A B 都無法左滑刪除。

通過START_STICKY與START_REDELIVER_INTENT實現被殺喚醒

通過startService啟動的Service,如果沒用唄stopService結束掉,在進程被殺掉之后,是有可能重新啟動的,實現方式:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_STICKY;//或者START_REDELIVER_INTENT
}

當然,前提是該進程可以被殺掉(無論被AMS還是LMDK),用戶主動殺死(最近任務列表或者退出應用),都一定會通過Binder訃告機制回調:

private final void handleAppDiedLocked(ProcessRecord app,
        boolean restarting, boolean allowRestart) {
    int pid = app.pid;
    boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
    ...
   }

進而調用cleanUpApplicationRecordLocked函數進行一系列清理及通知工作,這里先看Service相關的工作:

  private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
            boolean restarting, boolean allowRestart, int index) {
        ...
         // 這里先出處理service
        mServices.killServicesLocked(app, allowRestart);
        ...
  }   

這里傳入的allowRestart==true,也就說:允許重新啟動Service:

final void killServicesLocked(ProcessRecord app, boolean allowRestart) {

    ...
    ServiceMap smap = getServiceMap(app.userId);
   // Now do remaining service cleanup.
    for (int i=app.services.size()-1; i>=0; i--) {
        ServiceRecord sr = app.services.valueAt(i);
        if (!app.persistent) {
            app.services.removeAt(i);
        }
        ...
        if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
                &ApplicationInfo.FLAG_PERSISTENT) == 0) {
            bringDownServiceLocked(sr);
        } else if (!allowRestart
                || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) {
            bringDownServiceLocked(sr);
        } else {
           <!--關鍵點1 先進行判定,如果有需要將重啟的消息發送到消息隊列等待執行-->
            boolean canceled = scheduleServiceRestartLocked(sr, true);
           // 受時間跟次數的限制 sr.stopIfKilled  
          <!--關鍵點2 二次確認,如果不應該啟動Service,就將重啟Service的消息移除-->
           if (sr.startRequested && (sr.stopIfKilled || canceled)) {
                if (sr.pendingStarts.size() == 0) {
                    sr.startRequested = false;
                    if (!sr.hasAutoCreateConnections()) {
                        bringDownServiceLocked(sr);
                    }
           ...
 }

先看關鍵點1:如果允許重新啟動,并且APP Crash的次數小于兩次,就視圖將為結束的Service重新喚起,其實就是調用scheduleServiceRestartLocked,發送消息,等待喚醒,關鍵點2是二次確認下,是不是需要被喚醒,如果不需要就將上面的消息移除,并進行一定的清理工作,這里的sr.stopIfKilled,其實主要跟onStartCommand返回值有關系:

 void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
        boolean inDestroying = mDestroyingServices.contains(r);
        if (r != null) {
            if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
                r.callStart = true;
                switch (res) {
                    case Service.START_STICKY_COMPATIBILITY:
                    case Service.START_STICKY: {
                        r.findDeliveredStart(startId, true);
                        r.stopIfKilled = false;
                        break;
                    }
                    case Service.START_NOT_STICKY: {
                        r.findDeliveredStart(startId, true);
                        if (r.getLastStartId() == startId) {
                            r.stopIfKilled = true;
                        }
                        break;
                    }
                    case Service.START_REDELIVER_INTENT: {
                        ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
                        if (si != null) {
                            si.deliveryCount = 0;
                            si.doneExecutingCount++;
                            r.stopIfKilled = true;
                        }
                        break;
                    }

所以,如果onStartCommand返回的是Service.START_STICKY,在被殺死后是會重新啟動的,有必要的話,還會重啟進程:

private final boolean scheduleServiceRestartLocked(ServiceRecord r,
        boolean allowCancel) {
    boolean canceled = false;
     ...
     <!--關鍵點1-->
    mAm.mHandler.removeCallbacks(r.restarter);
    mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
    r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
    return canceled;
}

看關鍵點1,其實就是發送一個重新啟動Service的消息,之后就會重新啟動Service。

private class ServiceRestarter implements Runnable {
    private ServiceRecord mService;

    void setService(ServiceRecord service) {
        mService = service;
    }

    public void run() {
        synchronized(mAm) {
            performServiceRestartLocked(mService);
        }
    }
}

再看下幾個標志的意義:

1、 START_STICKY

在運行onStartCommand后service進程被kill后,那將保留在開始狀態,但是不保留那些傳入的intent。不久后service就會再次嘗試重新創建,因為保留在開始狀態,在創建 service后將保證調用onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent

2、 START_NOT_STICKY

在運行onStartCommand后service進程被kill后,并且沒有新的intent傳遞給它。Service將移出開始狀態,并且直到新的明顯的方法(startService)調用才重新創建。因為如果沒有傳遞任何未決定的intent那么service是不會啟動,也就是期間onstartCommand不會接收到任何null的intent。

3、 START_REDELIVER_INTENT

在運行onStartCommand后service進程被kill后,系統將會再次啟動service,并傳入最后一個intent給onstartCommand。直到調用stopSelf(int)才停止傳遞intent。如果在被kill后還有未處理好的intent,那被kill后服務還是會自動啟動。因此onstartCommand不會接收到任何null的intent。

ProcessRecord中一些參數的意義的意義

  • int maxAdj;                 // Maximum OOM adjustment for this process
    
  • int curRawAdj;              // Current OOM unlimited adjustment for this process
    
  • int setRawAdj;              // Last set OOM unlimited adjustment for this process  
    
  • int curAdj;                 // Current OOM adjustment for this process
    
  • int setAdj;                 // Last set OOM adjustment for this process
    

adj主要用來給LMKD服務,讓內核曾選擇性的處理后臺殺死,curRawAdj是本地updateOomAdj計算出的臨時值,setRawAdj是上一次計算出兵設定好的oom值,兩者都是未經過二次調整的數值,curAdj與setAdj是經過調整之后的adj,這里有個小問題,為什么前臺服務進程的oom_adj打印出來是1,但是在AMS登記的curAdj卻是2呢?

 oom: max=16 curRaw=2 setRaw=2 cur=2 set=2
curSchedGroup=-1 setSchedGroup=-1 systemNoUi=false trimMemoryLevel=0
curProcState=4 repProcState=4 pssProcState=-1 setProcState=4 lastStateTime=-37s554ms

AMS傳遞給LMKD服務的adj確實是2,LMKD用2計算出的oom_score_adj=117 (1000oom_adj/17) 也是準確的數值 ,那為什么proc/pid/oom_adj中的數值是1呢?應該是反向取整*導致的,高版本的內核都不在使用oom_adj,而是用oom_score_adj,oom_adj是一個向前兼容。


static void cmd_procprio(int pid, int uid, int oomadj) {
struct proc *procp;
char path[80];
char val[20];

    if (oomadj < OOM_DISABLE || oomadj > OOM_ADJUST_MAX) {
        ALOGE("Invalid PROCPRIO oomadj argument %d", oomadj);
        return;
    }

    // 這里只記錄oom_score_adj
    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
    snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
    writefilestring(path, val);
    <!--use_inkernel_interface = 1-->
     if (use_inkernel_interface)
    return;
    ....
 }

use_inkernel_interface標識其他幾個oom_adj,oom_score跟隨 oom_score_adj變化。oom_adj=(oom_score_adj*17/1000),取整的話,正好小了1;看如下解釋:

The value of /proc/<pid>/oom_score_adj is added to the badness score before oom_adj;

For backwards compatibility with previous kernels, /proc/<pid>/oom_adj may also
be used to tune the badness score.  Its acceptable values range from -16
(OOM_ADJUST_MIN) to +15 (OOM_ADJUST_MAX) and a special value of -17
(OOM_DISABLE) to disable oom killing entirely for that task.  Its value is
scaled linearly with /proc/<pid>/oom_score_adj.

oom_adj的存在是為了和舊版本的內核兼容,并且隨著oom_score_adj線性變化,如果更改其中一個,另一個會自動跟著變化,在內核中變化方式為:

  • 寫oom_score_adj時,內核里都記錄在變量 task->signal->oom_score_adj 中;
  • 讀oom_score_adj時,從內核的變量 task->signal->oom_score_adj 中讀取;
  • 寫oom_adj時,也是記錄到變量 task->signal->oom_score_adj 中,會根據oom_adj值按比例換算成oom_score_adj。
  • 讀oom_adj時,也是從內核變量 task->signal->oom_score_adj 中讀取,只不過顯示時又按比例換成oom_adj的范圍。

所以,就會產生如下精度丟失的情況:

# echo 9 > /proc/556/oom_adj
# cat /proc/556/oom_score_adj
  529
# cat /proc/556/oom_adj
  8

這也是為什么Android中明明算出來的oom_adj=1(2),在proc/pid/oom_adj總記錄的確實0(1)。

  • int curSchedGroup;          // Currently desired scheduling class
    
  • int setSchedGroup;          // Last set to background scheduling class
    

curSchedGroup與setSchedGroup是AMS管理進程的一個參考,定義在ProcessList.java中,從名字上看與任務調度有關系,答案也確實如此,取值有如下三種,不同版本略有不同,這里是7.0,

// Activity manager's version of Process.THREAD_GROUP_BG_NONINTERACTIVE
static final int SCHED_GROUP_BACKGROUND = 0;
// Activity manager's version of Process.THREAD_GROUP_DEFAULT
static final int SCHED_GROUP_DEFAULT = 1;
// Activity manager's version of Process.THREAD_GROUP_TOP_APP
static final int SCHED_GROUP_TOP_APP = 2;

AMS只能殺死后臺進程,只有setSchedGroup==ProcessList.SCHED_GROUP_BACKGROUND的進程才被AMS看做后臺進程,才可以被殺死,否則AMS無權殺死。

     <!--參考代碼1-->
  if (app.waitingToKill != null && app.curReceivers.isEmpty()
                    && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
                app.kill(app.waitingToKill, true);
                success = false;
            } 
    
   <!--參考代碼2-->    
    // Kill the running processes.
    for (int i = 0; i < procsToKill.size(); i++) {
        ProcessRecord pr = procsToKill.get(i);
        if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                && pr.curReceivers.isEmpty()) {
            pr.kill("remove task", true);
        } else {
            // We delay killing processes that are not in the background or running a receiver.
            pr.waitingToKill = "remove task";
        }
    }

以上兩個場景:場景一是AMS計算oomAdj并清理進程 ,場景二的代表:從最近的任務列表刪除進程。

  • int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
    
  • int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
    
  • int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
    
  • int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
    

ProcState 主要是為AMS服務,AMS依據procState判斷進程當前的狀態以及重要程度,對應的值位于ActivityManager.java中,主要作用是:決定進程的緩存等級以及緩存進程的生死。

<!--參考代碼1-->
switch (app.curProcState) {
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                        mNumCachedHiddenProcs++;
                        numCached++;
                        if (numCached > cachedProcessLimit) {
                            app.kill("cached #" + numCached, true);
                        }
                        break;
                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                        if (numEmpty > ProcessList.TRIM_EMPTY_APPS
                                && app.lastActivityTime < oldTime) {
                            app.kill("empty for "
                                    + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
                                    / 1000) + "s", true);
                        } else {
                            numEmpty++;
                            if (numEmpty > emptyProcessLimit) {
                                app.kill("empty #" + numEmpty, true);
                            }
                        }
                        break;
                    default:
                        mNumNonCachedProcs++;
                        break;
            }
            
<!--參考代碼2-->
if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
                            || app.systemNoUi) && app.pendingUiClean) {
                        final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
                        if (app.trimMemoryLevel < level && app.thread != null) {
                            try {
                                app.thread.scheduleTrimMemory(level);
                            } catch (RemoteException e) {
                            }
                        }
                        app.pendingUiClean = false;
                    }

總結

所有流氓手段的進程保活,都是下策,建議不要使用,本文只是分析實驗用。當APP退回后臺,優先級變低,就應該適時釋放內存,以提高系統流暢度,依賴流氓手段提高優先級,還不釋放內存,保持不死的,都是作死。

Android后臺殺死系列之一:FragmentActivity及PhoneWindow后臺殺死處理機制
Android后臺殺死系列之二:ActivityManagerService與App現場恢復機制
Android后臺殺死系列之三:LowMemoryKiller原理(4.3-6.0)
Android后臺殺死系列之四:Binder訃告原理
Android后臺殺死系列之五:Android進程保活-自“裁”或者耍流氓

作者:看書的小蝸牛
原文鏈接: Android進程保活-自“裁”或者耍流氓

**僅供參考,歡迎指正 **

參考文檔

谷歌文檔Application
Android四大組件與進程啟動的關系
Android 7.0 ActivityManagerService(8) 進程管理相關流程分析(2) updateOomAdjLocked
Android 7.0 ActivityManagerService(9) 進程管理相關流程分析(3) computeOomAdjLocked 精
Android代碼內存優化建議-OnTrimMemory優化 精
微信Android客戶端后臺保活經驗分享
按"Home"鍵回到桌面的過程
Android low memory killer 機制
應用內存優化之OnLowMemory&OnTrimMemory

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容