本篇文章是後臺殺死系列的最後一篇,主要探討一下進程的保活,Android自己設計的時候是很是善良的,它但願進程在不可見或者其餘一些場景下APP要懂得主動釋放,但是Android低估了」貪婪「,尤爲是不少國產APP,只但願索取來提升本身的性能,無論其餘APP或者系統的死活,致使了很嚴重的資源浪費,這也是Android被iOS詬病的最大緣由。本文的保活手段也分兩種:遵紀守法的進程保活與流氓手段換來的進程保活。javascript
聲明:堅定反對流氓手段實現進程保活 堅定反對流氓進程保活 堅定反對流氓進程保活 「請告訴產品:沒法進入白名單」html
LowmemoryKiller會在內存不足的時候掃描全部的用戶進程,找到不是過重要的進程殺死,至於LowmemoryKiller殺進程夠不夠狠,要看當前的內存使用狀況,內存越少,下手越狠。在內核中,LowmemoryKiller.c定義了幾種內存回收等級以下:(也許不一樣的版本會有些不一樣)java
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):(其餘版本原理大同小異)android
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會先找優先級低的進程,若是多個進程優先級相同,就優先殺死內存佔用高的進程,這樣就爲咱們提供了兩種進程包活手段:git
不過大多數狀況下,Android對於進程優先級的管理都是比較合理,即便某些場景須要特殊手段提升優先級,Android也是給了參考方案的,好比音頻播放,UI隱藏的時候,須要將Sevice進程設置成特定的優先級防止被後臺殺死,好比一些備份的進程也須要一些特殊處理,可是這些都是在Android容許的範圍內的,因此絕大多數狀況下,Android是不建議APP本身提升優先級的,由於這會與Android自身的的進程管理相悖,換句話說就是耍流氓。這裏先討論第二種狀況,經過合理的釋放內存下降被殺的風險,地主不想被殺,只能交公糧,自裁保身,不過這裏也要看自裁的時機,何時瘦身比較划算,O(∩_∩)O哈哈~!這裏就牽扯到有一個onTrimeMemory函數,該函數是一個系統回調函數,主要是Android系統通過綜合評估,給APP一個內存裁剪的等級,好比當內存還算充足,APP退回後臺時候,會收到TRIM_MEMORY_UI_HIDDEN等級的裁剪,就是告訴APP,釋放一些UI資源,好比大量圖片內存,一些引入圖片瀏覽緩存的場景,可能就更加須要釋放UI資源,下面來看下onTrimeMemory的回調時機及APP應該作出相應處理。編程
OnTrimMemory是在Android 4.0引入的一個回調接口,其主要做用就是通知應用程序在不一樣的場景下進行自我瘦身,釋放內存,下降被後臺殺死的風險,提升用戶體驗,因爲目前APP的適配基本是在14之上,因此沒必要考慮兼容問題。在APP中能夠在Application或者Activity中直接覆蓋OnTrimMemory函數以響應系統號召:緩存
public class LabApplication extends Application {
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
//根據level裁減內存
}
}複製代碼
onTrimeMemory支持不一樣裁剪等級,好比,APP經過HOME建進入後臺時,其優先級(oom_adj)就發生變化,從未觸發onTrimeMemory回調,這個時候系統給出的裁剪等級通常是TRIM_MEMORY_UI_HIDDEN,意思是,UI已經隱藏,UI相關的、佔用內存大的資源就能夠釋放了,好比大量的圖片緩存等,固然,還會有其餘不少場景對應不一樣的裁剪等級。所以,須要弄清楚兩個問題:微信
先看下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進程 |
其意義以下:app
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源碼來講:
與之對應的關鍵點12,是確立前臺RUNNING進程(也不必定是前臺顯示)的裁剪等級。
以後就真正開始裁剪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個,那麼這個時候的裁剪等級就是不太合理的,由於內存可能很充足,可是因爲限制了後臺進程的數量,致使裁剪等級太高。所以在使用的時候,最好結合裁剪等級與當前內存數量來綜合考量。
關於進程優先級的計算,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進程,也就是前臺進程,這個沒有別的辦法,只有TOP activity進程才能是算前臺進程。正常的交互邏輯下,這個是沒法實現的,鎖屏的時候卻是能夠啓動一個Activity,可是須要屏幕點亮的時候再隱藏,容易被用戶感知,得不償失,因此基本是無解,因此以前傳說的QQ經過一個像素來保活的應該不是這種方案,而經過WindowManager往主屏幕添加View的方式也並未阻止進程被殺,究竟是否經過一像素實現進程包活,我的還未獲得解答,但願能有人解惑。
先看一下源碼中對兩種優先級的定義,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的微信從最近的任務列表刪除是能夠殺死進程的,若是採用上述手段是殺不了的。
前文咱們分析過Android Binder的訃告機制:若是Service Binder實體的進程掛掉,系統會向Client發送訃告,而這個訃告系統就給進程保活一個可鑽的空子。能夠經過兩個進程中啓動兩個binder服務,而且互爲C/S,一旦一個進程掛掉,另外一個進程就會收到訃告,在收到訃告的時候,喚起被殺進程。邏輯以下下:
首先編寫兩個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框架自帶的,除非一次性所有殺死全部父子進程,這個沒測試過。
還有一些比較常見的進程保活手段是經過註冊BroadcastReceiver來實現的好比:
另外也能依靠Service的自啓動特性,經過onStartCommand的START_STICKY來實現,相比上面的不死,這些算比較柔和的啓動了,畢竟這兩種都是容許後臺殺死的前提下啓動的:
public class BackGroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
}複製代碼
全部流氓手段的進程保活,都是下策,建議不要使用,本文只是分析實驗用。當APP退回後臺,優先級變低,就應該適時釋放內存,以提升系統流暢度,依賴流氓手段提升優先級,還不釋放內存,保持不死的,都是做死。
Android 後臺殺死系列之一:FragmentActivity 及 PhoneWindow 後臺殺死處理機制
Android後臺殺死系列之二:ActivityManagerService與App現場恢復機制 Android後臺殺死系列之三:後臺殺死原理LowMemoryKiller(4.3-6.0)
Android後臺殺死系列之四:Binder訃告原理
僅供參考,歡迎指正
谷歌文檔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