1. 程式人生 > >Android程序回收機制和保活方案

Android程序回收機制和保活方案

1 Low Memory Killer機制

在Android系統中,程序的生命週期都是由系統來控制的。出於體驗和效能上的考慮,即使對一個App進行Home鍵還是Back鍵退出的操作,系統並不會真正殺掉該App的程序,它的程序依然存在於記憶體之中。因為這樣在下次要啟動這個App時就能更加快速。隨著系統執行的時間增長,開啟的App越來越多,記憶體中的程序也就會越來越多,這樣系統的可用記憶體就會越來越少。在系統記憶體減少到達一個閾值時,系統就會開始根據程序的優先順序來進行回收機制殺掉一部分程序來釋放出記憶體供後面需要啟動的App使用。這套回收記憶體的機制就叫Low Memory Killer。Android的Low Memory Killer機制是基於Linux核心的OOM

(Out Of Memory)規則改進的。

2 記憶體閾值

上面提到當系統記憶體減少到達一個閾值時就開始回收程序,這個記憶體閾值在不同的手機上會不一樣,我們可以通過adb命令:adb shell cat /sys/module/lowmemorykiller/parameters/minfree來檢視它(高版本的系統需要許可權):

可見輸出了6個值,這些值的單位是Page,1Page = 4KB,所以上面6個值換算成MB的話應該是:72、90、108、126、144、180。也就是說,當系統記憶體減少到達180MB時,系統就會開始結束優先順序最低的程序,當減少到達144MB時,系統又會開始結束對應優先順序較低的程序,由此類推。

3 oom_adj值

剛才提到回收程序是根據程序的優化級來決定,這個優先順序其實是Linux核心分配給每個系統程序的一個值,我們叫它做oom_adj值。oom_adj值定義在\sources\android-23\com\android\server\am\ProcessList.java類中。此值越大,程序的優先順序越低,越容易被回收,普通應用程序的oom_adj值是>=0,而系統程序的oom_adj值是<0的。要檢視程序的oom_adj值,可以使用adb命令:adb sehll cat /proc/程序id/oom_adj。下圖中我對一個普通的應用進行了三次檢視,三次的值分別是:0,6,8,第一次是打開了一個Activity,第二次是按下的Home鍵,而第三次是按下了Back鍵。

4 程序劃分

程序的優先順序從高到低可分為5種,分別是:Foreground process、Visible process、Service process、Background process、Empty process。所以它們的oom_adj值也是從小到大。

Foreground process

前臺程序,就是使用者正在使用中的應用當前的程序,例如:正在與使用者互動的Activity所在的程序; 一個與正在互動的Activity繫結的Service所在的程序; 一個呼叫了startForeground的前臺Service所在的程序,等。一般地前臺程序是不會被系統回收殺死的。

Visible process

可見程序,就是使用者正在使用,但不能直接操作的程序,例如:不在前臺但仍可見的Activity,像已呼叫了onPause()回撥的Activity,等。一般情況下,系統不會殺死可見程序,除非要在資源吃緊的情況下。

Service process

服務程序,就是沒有介面一直在後臺工進的程序,例如:通過startService()啟動的Service正在後臺中工作的所在程序。當系統記憶體不足以維持所有前臺程序和可見程序同時執行的情況下,服務程序就會被殺死。

Background process

後臺程序,就是使用者按了Home鍵或Back鍵後,應用完全進入了後臺,但還在執行的程序。這類程序是隨時都會被系統殺死的。

Empty process

空程序,某個程序不包含任何活躍元件時就會被置為空程序,此類程序已經處於完全無用狀態。

5 程序保活方案

程序保活是很多應用開發者都希望對自己App做的事情,目前除了手機廠商對像微信、QQ之類的大哥級App設定了防被殺白名單之外,是沒有其他有效的技術手段能保證一個App程序一直活著不會被殺。如果真的這樣的黑科技的話,那麼所有的App都使用上了,這些App都在後臺幹些不安份的事情以及佔用著記憶體不釋放,那麼我們的手機將會變得非常的恐怖了。

5.1 提升程序優先順序

雖然沒有什麼有效的手段可使App程序不會被殺,但是使App的程序等級提升沒那麼容易被殺還是有些辦法的。

方法一、使用前臺Service

我們知道,通過系統API的startForeground方法可啟動一個前臺的Service,也就是啟動了一個常駐的通知欄。因為前面程序劃分中也提過,一個呼叫了startForeground的前臺Service所在的程序就是最高級別的前臺程序,在一般情況下都不會被輕易地殺死,所以曾經App保活黨在前臺Service上沒少下功夫。

情況一、在過去Android API 19之前,可以直接呼叫startForeground並傳入一個new Nofification()便能啟動一個使用者完全看不到沒有感知的常駐通知欄,不過漏洞終究是漏洞,在Android API 19及以後系統版本中若使用該方法會在通知欄中出現“XXX正在執行”的通知欄,再也不是無感知了。

情況二、在Android API 25之前,又出了新的辦法,就是同時啟動兩個id相同的前臺Service,然後在後啟動的Service執行stopForeground來停止。這樣做又再一次可以神不知鬼不覺地啟動了一個前臺Service了,不過天網恢恢,此辦法在Android API 25及以後系統版本中又再次失效了。

情況三、與其偷偷摸摸暗地裡做事,不如光明正大幹一回。像當下一些天氣類App、音樂類App、安全類App,它們都是使用startForeground明著出現一個常駐通知欄,並帶些自身功能,就是要告訴使用者,我其實還活著,我在通知欄這裡為你幹事。其實主要你的App有足夠的理由常駐通知欄,這種方法是最直接最有效的保活方法。

方法二、鎖屏一畫素Activity

該方法是監聽系統熄屏和亮屏廣播,在熄屏的時候啟動一個透明且1個畫素的Activity,使得在熄屏狀態時還能假裝App在前臺,App所在程序變成了最高優先順序了,然後在亮屏時再將這個Activity退出。這種方法也避開了一些國內手機在鎖屏後一段時間後對後臺程序的優化以達到優化效能和省電的機制。筆者在多個版本的原生系統虛擬機器上執行是可行,但未在實際開發中使用過,以及未在國內流行手機廠商Rom中驗證過,貌似國內有部分手機系統中已經將熄屏和亮屏廣播給閹割了。順便一提,只聽說手機QQ有在使用此方法!

方法三、迴圈播放無聲音樂

顧名思義,該方法就是耍流氓地在後臺某個時機初始化MediaPlayer物件去播放一段無聲的音樂,但據說會在某些國內手機上鎖屏介面可能會出現音樂播放器介面。不管怎麼樣,該方法是非常不建議去使用的,因為就算在播放音樂時程序優先順序被提高了,但會拖累了記憶體使用與手機的耗電,往往就會適得其反地被第三方安全應用或系統安全應用檢測出來然後殺掉,也會使使用者反感。

5.2 程序復活

還有另外一種保活的方式,就是在App程序死掉後,通過別的手段重新啟動。這些手段目前也有很多,我們來看看。

方法一、帳戶同步

Android系統本身提供了帳戶同步功能,任何第三方App都可以通過此功能將自己的資料在一定時間內同步到伺服器中去。然而在同步資料是通過我們自己程式碼來實現的,所以在同步資料時會把已結束掉的App再次拉活起來。此做法的過程我們在前面的文章《Android裡帳戶同步的實現》中有詳細介紹過,但由於國內手機廠商的各種訂製和閹割,經驗證在某部分手機上是通不通的。

方法二、JobScheduler

Android5.0後提供了一個叫JobScheduler作業排程器的功能。它一種可讓系統在某個時刻某個特定條件下批處理一些App的任務請求的機制。所以同樣的也可以將已經結束的App在系統觸發此機制時可再次拉活起來。此做法的過程我們在前面的文章《Android裡JobScheduler的實現》中有詳細介紹過,也是由於國內手機廠商的各種訂製和閹割,並不是所有廠商手機系統都能夠實現這種拉活機制,像小米手機上是驗證行不通的。

方法三、雙程序守護

雙程序守護簡單說就是在你的App中起動兩個不同程序,在其中一個程序被殺掉的時候,另外的程序會立即檢測到,然後再次把被殺的程序啟動起來。這種辦法在Android5.0之前或許是比較流行的保活手段,但在後來高版本的Android系統中,系統回收策略已經改成程序組的形式了,那就是說,如果系統要回收一個程序,必然會殺死同屬於該App的所有程序,因此雙程序守護也只能是一個歷史性漏洞,只可以在低版本手機上使用。

方法四、 監聽系統廣播

通過監聽一些全域性的系統廣播,比如開機廣播、解鎖屏廣播、充電狀態變化廣播等,來啟動一個App的後臺服務在過去不失為一個好辦法。在Android 3.1開始,廣播機制加入了兩個標記位,分別是FLAGE_INCLUDE_STOPPED_PACKGES(包含已經停止的應用,這個時候廣播會發送給已停止的應用) 和 FLAG_EXCLUDE_STOPPED_PACKAGES(不包含已經停止的應用,這個時候廣播不會發送給已停止的應用),用來控制廣播是否要對處於停止狀態的應用起作用,而系統廣播預設是加上了FLAG_EXCLUDE_STOPPED_PACKAGES標誌,這樣做是為了防止廣播無意間或者在不必要時調起停止執行的應用。在原生系統中,當App初始啟動後就會被認為是非停止狀態,那麼就是說只要啟動過一次後就能無論程序是否存在都能接收到系統廣播。但是請別開心得太早,在國內大多廠商手機系統中還是行不通。例如像小米長按Back鍵強行停止App後,它是一定收不到系統廣播的。

方法五、家族應用相互喚醒

這個很好理解,就是同一個公司中有開發了多款App,而各款App都做了相互拉活的介面,哪個App在做事的同時,順便檢測一下自家公司的其它App是否也在該臺手機上有安裝,是否程序沒有啟動,如果是就通過約定好的介面將其拉活。

方法六、推送拉活

像MiPush、HuaweiPush這類訊息推送服務,它建立了從雲端到手機端的訊息推送通道。它們在針對各自家的Rom表現也是相當出色的,通常在被強制停止的App中是無法接收到廣播的,但是它們在各自系統中因為傳送廣播時利用了FLAGE_INCLUDE_STOPPED_PACKGES標記,所以也能夠給已停止的App傳送廣播和拉活,但在別人的系統裡就有點差強人意了,在HuaweiPush在非EMUI系統上還得必須先安裝一個華為移動服務。

5.3 授予系統許可權

授予系統許可權簡單說就是讓使用者心甘情願為你的App授予一些系統穩定性許可權。比如大多數手機中的“自啟動許可權“、小米手機中的”神隱模式加白“、華為中的“受保護應用許可權”、Vivo手機中的“清理白名單“”後臺高耗電清理白名單“、Oppo手機中的”後臺凍結”,等。當你的App開啟了對應這類許可權後,就會大大增加不會被殺的機率,是很好的保活手段。還有一個所有手機都有的”通知讀取許可權“,經過驗證,開啟此許可權的App它的程序優化級會被提高,也是一個很好的保活手段。還有一個最強的保活手段,就是讓使用者在多工切換中,將你的App鎖定,這樣系統的一鍵清理後臺任務也就不會對你的App進行清理了。

6 總結

上面介紹了不少目前或過去在國內App開發中比較流行的保活方案。其實說句實在話,與其費盡周折使用各利擦邊球式的保活小手段瞞天過海一時,還不如安安份份地做好產品體驗和效能優化。當你的App有足夠的理由常駐通知欄或者說有足夠的理由要在後臺執行的時候,只要稍加一些小引導使用者自然會給你的App授予系統的穩定性許可權。這才是長治久安的好辦法!做好了效能優化,就算系統要隨時回收資源,也沒那麼快輪到你的App,因為首先要殺也是殺佔用記憶體大的、高耗電的App。而且並不是所有使用者都是願意讓不相關的App任其在後臺常駐的,有時候我們為了私慾做得過多時反而會適得其反地讓使用者反感。所以說,所有的歪門邪道的保活都是耍流氓的,我們還是應該尊重使用者的意願,養成良好的開發信仰。