從Android進程優先級開始談APP保活的意義

1、APP爲何保活?

        Android應用互相喚醒的狀況是中國特點,國外由於有 Google Play 的評價系統和基本的審覈機制,不會有國內這麼瘋狂的流氓式設計。這樣作的好處一個是方便收集用戶信息,瞭解用戶習慣,優化產品,再者給用戶推送消息須要APP退出前臺時可以保活,對於IM應用來說更是硬性需求。因此有些你沒裝過或者周圍沒人用的 APP ,隨隨便便都能日活上千萬。html

       

2、保活手段

      當前業界的Android進程保活手段主要分爲 黑、白、灰 三種,其大體的實現思路以下:java

黑色保活:

 不一樣的app進程,用廣播相互喚醒(包括利用系統提供的廣播進行喚醒)linux

場景1:開機,網絡切換、拍照、拍視頻時候,利用系統產生的廣播喚醒appandroid

場景2:接入第三方SDK也會喚醒相應的app進程,如微信sdk會喚醒微信,支付寶sdk會喚醒支付寶。由此發散開去,就會直接觸發了下面的 場景3api

場景3:假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你打開任意一個阿里系的app後,有可能就順便把其餘阿里系的app給喚醒了。(只是拿阿里打個比方,其實BAT系都差很少)緩存

沒錯,咱們的Android手機就是一步一步的被上面這些場景給拖卡機的。bash

針對場景1,估計Google已經開始意識到這些問題,因此在最新的Android N取消了 ACTION_NEW_PICTURE(拍照),ACTION_NEW_VIDEO(拍視頻),CONNECTIVITY_ACTION(網絡切換)等三種廣播。微信

白色保活:

       啓動前臺Service網絡

白色保活手段很是簡單,就是調用系統api啓動一個前臺的Service進程,這樣會在系統的通知欄生成一個Notification,用來讓用戶知道有這樣一個app在運行着,哪怕當前的app退到了後臺。這樣的目的也很容易理解,就是提升進程的優先級。讓Linux在內存不足的時候不會優先被殺掉,從某種意義上來講這算是最不流氓的一種保活方式了。
app

灰色保活:

     利用系統的漏洞啓動前臺Service

  灰色保活,這種保活手段是應用範圍最普遍。它是利用系統的漏洞來啓動一個前臺的Service進程,與普通的啓動方式區別在於,它不會在系統通知欄處出現一個Notification,看起來就如同運行着一個後臺Service進程同樣。這樣作帶來的好處就是,用戶沒法察覺到你運行着一個前臺進程(由於看不到Notification),但你的進程優先級又是高於普通後臺進程的

3、進程的優先級

在Android系統中,進程的優先級影響着如下三個因素:

  • 當內存緊張時,系統對於進程的回收策略
  • 系統對於進程的CPU調度策略
  • 虛擬機對於進程的內存分配和垃圾回收策略

系統對於進程的優先級有以下五個分類:

  1. 前臺進程
  2. 可見進程
  3. 服務進程
  4. 後臺進程
  5. 空進程

前臺進程 

指正在與用戶進行交互的應用進程,該進程數量較少,是最高優先級進程,系統通常不會終止該進程,而判斷爲前臺進程的因素有如下這些 

 進程中包含處於前臺的正與用戶交互的activity; 

 進程中包含與前臺activity綁定的service;

 進程中包含調用了startForeground()方法的service;

 進程中包含正在執行onCreate(), onStart(), 或onDestroy()方法的service;

 進程中包含正在執行onReceive()方法的BroadcastReceiver. 

可視進程

 能被用戶看到,但不能根據根據用戶的動做作出相應的反饋, 因素 進程中包含可見但不處於前臺進程的activity(如:activity執行onPause()時處於可見狀態,但並不處於前臺進程中) 該進程有一個與可見/前臺的activity綁定數據的service  

服務進程 

沒有可見界面仍在不斷的執行任務的進程,除非在可視進程和前臺進程緊缺資源(如:內存資源)纔會被終止 因素 包含除前臺進程和可視進程的service外的service的進程 

後臺進程

 一般系統中有大量的後臺進程,終止後臺進程不會影響用戶體驗,隨時爲優先級更高的進程騰出資源而被終止,優先回收長時間沒用使用過的進程。 因素 包含不在前臺或可視進程的activity的進程,也就是已經調用onStop()方法後的activity 

空進程 

爲提升總體系統性能,系統會保存已經完成生命週期的應用程序 ,存在與內存當中,也就是緩存,爲下次的啓動更加迅速而設計。

4、內存閾值

上面是進程的分類,進程是怎麼被殺的呢?系統出於體驗和性能上的考慮,app在退到後臺時系統並不會真正的kill掉這個進程,而是將其緩存起來。打開的應用越多,後臺緩存的進程也越多。在系統內存不足的狀況下,系統開始依據自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內存來供給須要的app, 這套殺進程回收內存的機制就叫 Low Memory Killer。那這個不足怎麼來規定呢,那就是內存閾值,咱們可使用cat /sys/module/lowmemorykiller/parameters/minfree來查看某個手機的內存閾值。


注意這些數字的單位是page。 1 page = 4 kb.上面的六個數字對應的就是(MB): 72,90,108,126,144,180,這些數字也就是對應的內存閥值,內存閾值在不一樣的手機上不同,一旦低於該值,Android便開始按順序關閉進程. 所以Android開始結束優先級最低的空進程,即當可用內存小於180MB(46080*4/1024)。

讀到這裏,你或許有一個疑問,假設如今內存不足,空進程都被殺光了,如今要殺後臺進程,可是手機中後臺進程不少,難道要一次性所有都清理掉?固然不是的,進程是有它的優先級的,這個優先級經過進程的adj值來反映,它是linux內核分配給每一個系統進程的一個值,表明進程的優先級,進程回收機制就是根據這個優先級來決定是否進行回收,adj值定義在com.android.server.am.ProcessList類中,這個類路徑是${android-sdk-path}\sources\android-23\com\android\server\am\ProcessList.java。

oom_score_adj

對於每個運行中的進程,Linux內核都經過proc文件系統暴露這樣一個文件來容許其餘程序修改指定進程的優先級:

/proc/[pid]/oom_score_adj。(修改這個文件須要root權限)

這個文件容許的值的範圍是:-1000 ~ +1000之間。值越小,表示進程越重要。

當內存很是緊張時,系統便會遍歷全部進程,以肯定哪一個進程須要被殺死以回收內存,此時便會讀取oom_score_adj 這個文件的值。關於這個值的使用,在後面講解進程回收的的時候,咱們會詳細講解。

PS:在Linux 2.6.36以前的版本中,Linux 提供調整優先級的文件是/proc/[pid]/oom_adj。這個文件容許的值的範圍是-17 ~ +15之間。數值越小表示進程越重要。 這個文件在新版的Linux中已經廢棄。

但你仍然可使用這個文件,當你修改這個文件的時候,內核會直接進行換算,將結果反映到oom_score_adj這個文件上。

Android早期版本的實現中也是依賴oom_adj這個文件。可是在新版本中,已經切換到使用oom_score_adj這個文件。oom_adj的值越小,進程的優先級越高,普通進程oom_adj值是大於等於0的,而系統進程oom_adj的值是小於0的,咱們能夠經過cat /proc/進程id/oom_adj能夠看到當前進程的adj值。



看到adj值是0,0就表明這個進程是屬於前臺進程,咱們按下Back鍵,將應用至於後臺,再次查看



adj值變成了8,8表明這個進程是屬於不活躍的進程,你能夠嘗試其餘狀況下,oom_adj值是多少,可是每一個手機的廠商可能不同,oom_adj值主要有這麼幾個,能夠參考一下。




lowmemorykiller機制

OOM全稱Out Of Memory,是Linux當中,內存保護機制的一種。該機制會監控那些佔用內存過大,尤爲是瞬間很快消耗大量內存的進程,爲了防止內存耗盡而內核將該進程殺掉。

當Kernel遇到OOM的時候,能夠有2種選擇:

1) 產生kernelpanic(死機)

2) 啓動OOM killer,選擇一個或多個「合適」的進程,幹掉那些選擇中的進程,從而釋放內存。

在Linux中,系統就是經過算分去殺死進程的。至於分數的值,就是這3個參數的值。簡單來講,系統是這樣進行算分的:算分主要分2部分,一部分是系統打分,主要根據該進程的內存使用狀況(oom_score),另外一部分是用戶大份額,就是oom_score_adj。每一個進程的實際得分是綜合這2個參數的值的。而oom_adj只是一箇舊的接口參數,在普通的linux系統中和oom_score_adj是差很少的(可是在android中是頗有用的)。

在Android中,及時用戶退出當前應用程序後,應用程序仍是會存在於系統當中,這是爲了方便程序的再次啓動。可是這樣的話,隨着打開的程序的數量的增長,系統的內存就會不足,從而須要殺掉一些進程來釋放內存空間。至因而否須要殺進程以及殺什麼進程,這個就是由Android的內部機制LowMemoryKiller機制來進行的。

Andorid的Low Memory Killer是在標準的linux lernel的OOM基礎上修改而來的一種內存管理機制。當系統內存不足時,殺死沒必要要的進程釋放其內存。沒必要要的進程的選擇根據有2個:oom_adj和佔用的內存的大小。oom_adj表明進程的優先級,數值越高,優先級月低,越容易被殺死;對應每一個oom_adj均可以有一個空閒進程的閥值。Android Kernel每隔一段時間會檢測當前空閒內存是否低於某個閥值。假如是,則殺死oom_adj最大的沒必要要的進程,若是有多個,就根據oom_score_adj去殺死進程,,直到內存恢復低於閥值的狀態。

LowMemoryKiller的值的設定,主要保存在2個文件之中,分別是/sys/module/lowmemorykiller/parameters/adj與/sys/module/lowmemorykiller/parameters/minfree。adj保存着當前系統殺進程的等級,minfree則是保存着對應的閥值。他們的對應關係以下:



舉個例子說明一下上表,噹噹前系統內存少於55296×4K(即216MB)時,Android就會找出當前oom_adj≥9的進程,根據進程的等級,先把oom_adj數值最大的進程給殺掉,釋放他的內存,當他們的oom_adj相等時,就對比他們的oom_score_adj,而後oom_score_adj越大,也越容易殺掉。


在這裏,也許有人會問,爲何採用LowMemoryKiller而不用OOM呢?咱們來對比一下二者,就能夠得出答案了。



使用LowMemoryKiller可使系統內存較低時,調出進程管理器結束沒必要要的人進程釋放空間。在安卓中,若是等到真正的OOM時,也許進程管理器就已經無法啓動了。

lowMemorykiller的運行原理

上面其實已經說過,LowMemoryKiller是對多個內存閥值的控制來選擇殺進程的。可是,這些閥值是怎樣聯繫在一塊兒的呢?下面就個人理解,簡單說一下其運行的原理。

首先,LowMemoryKiller是隨着系統的啓動而啓動的。當前主要的LowMemoryKiller的代碼主要在\system\core\lmkd的目錄下,以前的代碼\kernel\drivers\staging\android\lowmemorykiller.c已經再也不使用。

LowMemoryKiller在系統啓動的時候就已經由init進程一併啓動了。LowMemoryKiller啓動就是,就會不斷監測系統的運行狀況和內存狀況,當內存少於minfree限定的閥值的時候,lowMemoryKiller遍歷當前進程的oom_score_adj,把大於對應閥值的進程進行kill操做。例如,在當前設置中,當系統內存少於315M時,系統就會自動把進程中oom_score_adj的值少於1000的殺掉,當系統內存少於216時,系統就會自動把進程中oom_score_adj的值少於529的殺掉,如此類推。

至於oom_adj和oom_score_adj是由誰去控制並寫入的呢?

在系統當中,oom_adj和oom_score_adj是由ActivityManagerService去控制的,上層應用的啓動都離不開AcitivityManagerService的調用與分配資源。有關oom_adj與oom_score_adj會在之後分析ActivityManagerService的時候加入相對詳細的論述。在這裏就不詳細說明。

有關Minfree的值的寫入,其實能夠找到不少個地方,可是在最開始(還在用lowmemorykiller.c)的時候,是能夠在lowmemorykiller.c中設置的。可是如今已經不用lowmemorykiller.c了,因此相對設置的地方也不同了。經查找驗證,Minfree的閥值控制,是由ActivictyManagerService和lowmemorykiller一併控制寫入的。

5、經常使用的保活套路


前臺服務

這種大部分人都瞭解,聽說這個微信也用過的進程保活方案,這方案實際利用了Android前臺service的漏洞。
原理以下
對於 API level < 18 :調用startForeground(ID, new Notification()),發送空的Notification ,圖標則不會顯示。
對於 API level >= 18:在須要提優先級的service A啓動一個InnerService,兩個服務同時startForeground,且綁定一樣的 ID。Stop 掉InnerService ,這樣通知欄圖標即被移除

相互喚醒 

相互喚醒的意思就是,假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你打開任意一個阿里系的app後,有可能就順便把其餘阿里系的app給喚醒了。這個徹底有可能的。此外,開機,網絡切換、拍照、拍視頻時候,利用系統產生的廣播也能喚醒app,不過Android N已經將這三種廣播取消了。


粘性服務&與系統服務捆綁

這個是系統自帶的,onStartCommand方法必須具備一個整形的返回值,這個整形的返回值用來告訴系統在服務啓動完畢後,若是被Kill,系統將如何操做,這種方案雖然能夠,可是在某些狀況or某些定製ROM上可能失效

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_REDELIVER_INTENT;
}
複製代碼
  • START_STICKY
    若是系統在onStartCommand返回後被銷燬,系統將會從新建立服務並依次調用onCreate和onStartCommand(注意:根據測試Android2.3.3如下版本只會調用onCreate根本不會調用onStartCommand,Android4.0能夠辦到),這種至關於服務又從新啓動恢復到以前的狀態了)。

  • START_NOT_STICKY
    若是系統在onStartCommand返回後被銷燬,若是返回該值,則在執行完onStartCommand方法後若是Service被殺掉系統將不會重啓該服務。

  • START_REDELIVER_INTENT
    START_STICKY的兼容版本,不一樣的是其不保證服務被殺後必定能重啓。               


  • JobSheduler

    是做爲進程死後復活的一種手段,native進程方式最大缺點是費電, Native 進程費電的緣由是感知主進程是否存活有兩種實現方式,在 Native 進程中經過死循環或定時器,輪訓判斷主進程是否存活,當主進程不存活時進行拉活。其次5.0以上系統不支持。 可是JobSheduler能夠替代在Android5.0以上native進程方式,這種方式即便用戶強制關閉,也能被拉起來,代碼以下:

    JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public class MyJobService extends JobService {
        @Override
        public void onCreate() {
            super.onCreate();
            startJobSheduler();
        }
    
        public void startJobSheduler() {
            try {
                JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
                builder.setPeriodic(5);
                builder.setPersisted(true);
                JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
                jobScheduler.schedule(builder.build());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        @Override
        public boolean onStartJob(JobParameters jobParameters) {
            return false;
        }
    
        @Override
        public boolean onStopJob(JobParameters jobParameters) {
            return false;
        }
    }複製代碼

    開啓一個像素的Activity

    聽說這個是手Q的進程保活方案,基本思想,系統通常是不會殺死前臺進程的。因此要使得進程常駐,咱們只須要在鎖屏的時候在本進程開啓一個Activity,爲了欺騙用戶,讓這個Activity的大小是1像素,而且透明無切換動畫,在開屏幕的時候,把這個Activity關閉掉,因此這個就須要監聽系統鎖屏廣播. 

    相關文章
    相關標籤/搜索