關於進程保活的一切


oom_adj值越小優先級越高,比如native進程的adj值爲負值(各系統不同),對於這個adj值的進程來說,系統根本不會動它一分一毫,實質上當進程的adj值去到2時系統就很少會因爲其它原因而去殺死它

一、各版本後臺相關機制更新

(一)Android 9.0

1)前臺權限

針對 Android 9 或更高版本並使用前臺服務的應用必須請求 FOREGROUND_SERVICE 權限。 這是普通權限,因此,系統會自動爲請求權限的應用授予此權限。

2)應用待機羣組

系統將動態分配各個應用至不同分組。系統或會通過利用機器學習預加載的應用,從而預測各個應用的使用概率,然後將它們編配至相應的羣組中。若設備中沒有安裝此類系統應用,在默認情況下,系統會根據應用的近期使用情況進行等級劃分。應用活躍度越高,所處分組的優先級就越高,也就相應地更容易獲取設備資源。尤其是,應用所處的的羣組決定了其所安排的job,觸發AlarmManager以及接受高優先級FCM的頻率。這些限制僅在非充電狀態下才有效;當設備充電時,應用並不會受到系統限制。

1、活躍 (Active)
  1. 應用啓動了一個Activity;
  2. 應用正在運行前臺服務;
  3. 另一個前臺應用通過SyncAdapter與前臺應用的ContentProvider關聯該應用 ;
  4. 用戶點擊了應用的推送。

在job、AlarmManager、FCM信息的資源調用上無任何系統限制。

2、工作 (Working set)

應用的運行頻率很高,但目前並未處於“活躍”狀態;或被間接使用的應用。
在job、AlarmManager部分限制

3、常用 (Frequent)

經常使用但不是每天使用的應用,如打卡應用。
在job、AlarmManager部分限制,接受的高優先性FCM消息也有數量上限

4、極少 (Rare)

應用的使用頻率很低
在job、AlarmManager、FCM信息的資源調用上受嚴格限制,網絡訪問能力也會受到影響

5、從未使用(Never)

安裝但是從未運行過的應用會被歸到“從未使用”羣組中。 系統會對這些應用施加極強的限制。

3)後臺限制

當系統監測到應用消耗過多資源時,系統會通知並詢問用戶是否需要限制該應用的後臺活動。
目前有以下兩種情況會觸發系統發送此通知:

  • 頻繁使用喚醒鎖 (wake locks):屏幕關閉後,局部喚醒鎖 (Partial wake lock) 連續開啓 1 小時;
  • 過多的後臺服務:當應用目標 API 等級低於 26,且運行過多後臺服務。
4)省電模式改進

比如:在AOSP構建上存在以下系統限制:

  • 應用將更容易進入待機模式,系統不會一直等到應用處於“空閒”狀態才採取行行動;
  • 不論目標API等級爲何,所有應用都會受到後臺執行限制;
  • 屏幕關閉後,位置服務可能被禁用;
  • 處於後臺的應用不能訪問網絡。

(二)Android 8.0

1)後臺 Service 限制

在後臺運行的服務在幾分鐘內會被stop掉,推薦可使用AlarmManager、SyncAdapter、JobScheduler代替後臺服務

2)不再允許後臺應用創建後臺Service

Android 8.0 有一項複雜功能:系統不允許後臺應用創建後臺 Service。 因此,Android 8.0 引入了一種全新的方法,即 startForegroundService(),以在前臺啓動新 Service。 在系統創建 Service 後,應用有五秒的時間來調用該 Service 的 startForeground() 方法以顯示新 Service 的用戶可見通知。 如果應用在此時間限制內未調用 startForeground(),則系統將停止此 Service 並聲明此應用爲 ANR。
在很多情況下,您的應用都可以使用 JobScheduler 作業替換後臺服務。

3)
  • 應用清單中不再能註冊隱式廣播
  • 清單仍能註冊顯式廣播
  • 可使用 Context.registerReceiver 註冊顯式和隱式廣播
  • 需要簽名權限的廣播不受限制

JobScheduler 仍然爲這些首先的廣播提供了功能上的補充。

(三)Android 7.0

1)doze優化

在6.0的基礎上進行了優化,把其中的限制分爲了兩步進行:

  1. 未插電、屏幕關閉一段時間後進入低功耗模式的第一階段:關閉應用網絡訪問、推遲作業和同步(這裏官網上寫的是插電狀態,但是根據上下文以及6.0中的說明,應當是未插電狀態)
  2. 進入低電耗模式後設備處於靜止狀態達到一定時間:開始實施餘下的低功耗限制
2)移除了三項隱式廣播

在應用清單靜態註冊這幾項廣播將不再有效:

  • CONNECTIVITY_ACTION
  • ACTION_NEW_VIDEO
  • ACTION_NEW_PICTURE

JobScheduler 中提供了網絡相關API對CONNECTIVITY_ACTION進行補充。

(四)Android 6.0

1)doze模式

當設備未連接至電源,且長時間處於閒置狀態時,系統會將應用進入Doze。
限制:

  • 暫停訪問網絡。
  • 系統將忽略 wake locks。
  • AlarmManager 推遲到下一維護時段。如果您需要設置在低電耗模式下觸發的鬧鈴,請使用setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。 一般情況下,使用 setAlarmClock() 設置的鬧鈴將繼續觸發 — 但系統會在這些鬧鈴觸發之前不久退出doze模式。
  • 系統不執行 Wi-Fi 掃描。
  • 系統不允許運行同步適配器。
  • 系統不允許運行 JobScheduler。

如何解除限制: 加入低功耗模式白名單, 可以豁免對網絡和wake locks的限制

(五)Android 5.0

  • Android 5.0 以上,系統殺進程以 uid 爲標識,通過殺死整個進程組來殺進程,因此 native 進程也躲不過系統的法眼
  • 添加 JobScheduler

二、進程保活 6.0以下

1)監聽廣播方式

通過監聽一些全局的靜態廣播,比如開機廣播、解鎖屏廣播、網絡狀態廣播等,來啓動應用的後臺服務。目前,在高版本的Android系統中已經失效,因爲高版本的Android系統規定應用必須在系統開機後運行一次才能監聽這些系統廣播,一般而言,應用被系統殺死後,基本無法接收系統廣播。

2)提高Service的優先級

以前提高Service優先級方法很多,比如onStartCommand返回START_STICKY使系統內存足夠的時候Service能夠自動啓動、彈出通知、配置service的優先級等,這些方式只能在一定程度上緩解service被立馬回收,但只要用戶一鍵清理或者系統回收照樣無效。

3)全局定時器

還有一種方法就是在設置一種全局定時器,定時檢測啓動後臺服務,但這種方法目前也已經無效,因爲應用只要被系統殺死,全局定時器最後也只成了擺設。

4)應用中的雙service拉起

經過測試,只要當前應用被殺,任何後臺service都無法運行,也無法自行啓動。

5)應用中的雙進程拉起

使用NDK在底層fork出一個子進程,來實現與父進程之間的互拉。在Android4.x還是非常有效的,Android 5.0 以上,系統殺進程以 uid 爲標識,通過殺死整個進程組來殺進程,因此最後導致雙進程無法拉起。

6)單進程守護

當應用X綁定保活助手A時(淺綠色字體),如果保活助手A被系統殺死,應用X的onServiceDisConnected被回調,我們可以在該方法中執行bindService方法再次嘗試綁定喚醒保活助手A;
當保活助手A綁定應用X時(橙色字體),如果應用X被系統殺死,保活助手A的onServiceDisconnected被回調,我們可以在該方法中執行bindService方法再次嘗試綁定喚醒應用X。
兩次強殺應用X,應用X就無法啓動了,這是因爲當應用X“體積”較大,啓動前需要加載諸如大量的靜態變量或者Application類中的變量等,導致啓動較慢,當我第一次強殺應用X時,保活助手A是執行綁定啓動應用X保活服務的,但我繼續第二次強殺應用X時,保活助手A可能還未與應用X綁定,最終導致保活助手A無法檢查應用X的綁定狀態而失效。

7)雙進程守護

針對單進程守護出現的問題,當應用X“體積”較大時,我們可以採用雙進程守護,即實現兩個保活助手,它們彼此雙向綁定來對應用X進行守護。我們採用“環”的形式來進行互拉,無論誰被殺死,只要系統殺掉剩餘的任何一個進程,最後活着的進程都能夠將其他被殺進程拉起來。當然,這裏還有個小技巧,爲了防止兩個保活助手進程同時被系統殺死,我這裏採取高低優先級的方式來解決。
注: 當上述兩個進程長時間運行在後臺時還是有可能被系統殺死,以致無法實現保活的目的。當時猜想,系統在回收進程時很可能是按順序回收的,當這兩個進程順序比較接近,或者說內存中可能就只有這兩個進程,那麼系統在回收的時候一次性將其幹掉了。爲了緩解這種情況,我採取了一種高低優先級的方式來儘量保證系統不會同一時間回收兩個進程,只要有了這個時間差,兩個進程就能夠實現互相啓動保活的目的。

三、進程保活 6.0~8.0

  • 降低omm_adj值,儘量保證進程不被系統殺死
  • 進程被殺死後,通過其他方式將進程復活

進程在內存中時活動主要有五種狀態: 前臺進程、可見進程、服務進程、後臺進程、空進程,這幾種狀態的進程優先級由高到低,oom_adj值由低到高(在ProcessList定義)

優先級 進程狀態 oom_adj 特性與場景
1 前臺進程 0 正在與用戶交互,除深度定製ROM或系統錯誤情況,系統不會殺死該進程;
1)正在交互(onResume),1像素保活原理;
2)與正在交互的Activity綁定的Service;
3)startForeground啓動的前臺進程,前臺Service保活原理;
4)正在執行生命週期某個方法的Service,播放無聲音樂保活原理5)BroadcastReceiver執行onReceive
2 可見進程 1 進程可見但不能交互
1)進程持有onPause的Activity;
2)進程持有與可見Service綁定的Service
3 服務進程 5 進程運行着由startService啓動且不與任何Activity綁定的Service,且不屬於前臺進程和可見進程
4 後臺進程 6 進程持有一個Activity在onStop並未onDestroy
5 空進程 9-25 該狀態下的進程不包含任何活躍的組件,他只是作爲緩存的形式存在,以加快進程的啓動速度

(一)防殺策略

1)開啓前臺Service

通過使用 startForeground()方法將當前Service置於前臺來提高Service的優先級,API大於18時 startForeground()方法需要彈出一個可見通知,可以開啓另一個Service將通知欄移除;在onStartCommand方法中返回START_STICKY,作用是當Service進程被kill後,系統會嘗試重新創建這個Service;在onDestory方法中重新啓動自己

2)監聽鎖屏廣播,製造1像素
SinglePixelActivity通過getWindow設置屬性;onDestroy中重啓自己;
android:launchMode="singleInstance" 
android:excludeFromRecents="true"//用於控制SinglePixelActivity不在最近任務列表中顯示
android:finishOnTaskLaunch="false" //用於標記當用戶再起啓動應用(TASK)時是否關閉已經存在的Activity的實例,false表示不關閉
android:configChanges="keyboardHidden|orientation|screenSize|navigation|keyboard" //防止Activity重啓
android:theme 設置窗體背景顏色爲透明、沒有邊框、不包含標題欄、懸浮、切換無動畫、禁用預覽動畫、不允許背景變暗、自定義TitleBar時去掉多餘的陰影等
業務Activity,android:launchMode="singleTask",註冊監聽鎖屏廣播,接到鎖屏廣播,將自身切換到可見模式(啓動1像素)  
2)循環播放一段無聲音頻

同1),啓動Service並播放音頻

(二)復活策略

1)使用JobScheduler

JobScheduler是谷歌在Android 5.0引入的一個能夠執行某項任務的API,它允許APP在將來達到一定條件時執行指定的任務。通常情況下,即使APP被強制停止,預定的任務仍然會被執行。

首先在一個實現了JobService的子類的onStartJob方法中執行這項任務,使用JobInfo的Builder方法來設定條件並和實現了JobService的子類的組件名綁定,然後調用系統服務JobScheduler的schedule方法。這樣,即便在執行任務之前應用程序進程被殺,也不會導致任務不會執行,因爲系統服務JobScheduler會使用bindServiceAsUser的方法把實現了JobService的子類服務啓動起來,並執行它的onStartJob方法。
需要BIND_JOB_SERVICE權限

具體代碼在文章末尾

Doze:即休眠、打盹之意。是谷歌在Android M(6.0)提出爲了延長電池使用壽命的一種節能方式,它的核心思想是在手機處於屏幕熄滅、不插電或靜止不動一段時間後,手機會自動進入Doze模式。處於Doze模式的手機將停止所有非系統應用的WalkLocks、網絡訪問、鬧鐘、GPS/WIFI掃描、JobSheduler活動。當進入Doze模式的手機屏幕被點亮、移動或充電時,會立即從Doze模式恢復到正常,系統繼續執行被Doze模式"冷凍"的各項活動。Doze模式不會殺死進程,只是停止了進程相關的耗電活動,使其進入"休眠"狀態。至Android N(即Android 7.0)後,谷歌進一步對Doze休眠機制進行了優化,休眠機制的應用場景和使用規則進行了擴展。Doze在Android 6.0中需要將手機平行放置一段時間才能開啓,在7.0中則可隨時開啓。

  • 對於Android 5.0:JobSheduler的喚醒是非常有效的;
  • 對於Android 6.0:雖然谷歌引入了Doze模式,但通常很難真正進入Doze模式,所以JobSheduler的喚醒依然有效;
  • 對於Android 7.0:JobSheduler的喚醒會有一定的影響,我們可以在電池管理中給APP開綠色通道,防止手機Doze模式後阻止APP使用JobSheduler功能。
2)推送SDK
3)第三方應用互相喚醒

四、進程保活8.0以上

(一)優雅的實現保活

從 Android 6.0 開始,系統爲了省電增加了休眠模式,系統待機一段時間後,會殺死後臺正在運行的進程。但系統會有一個後臺運行白名單,白名單裏的應用將不會受到影響,在原生系統下,通過:「設置」 - 「電池」 - 「電池優化」 - 「未優化應用」,可以看到這個白名單。

五、附錄

JobScheduler的具體實現

/**JobService,支持5.0以上forcestop依然有效  */ 
@TargetApi(21)  
public class AliveJobService extends JobService {  
    private final static String TAG = "KeepAliveService";  
    // 告知編譯器,這個變量不能被優化  
    private volatile static Service mKeepAliveService = null;  
   
    public static boolean isJobServiceAlive(){  
        return mKeepAliveService != null;  
    }  
   
    private static final int MESSAGE_ID_TASK = 0x01;  
   
    private Handler mHandler = new Handler(new Handler.Callback() {  
        @Override 
        public boolean handleMessage(Message msg) {  
            // 具體任務邏輯  
            if(SystemUtils.isAPPALive(getApplicationContext(), Contants.PACKAGE_NAME)){  
                Toast.makeText(getApplicationContext(), "APP活着的", Toast.LENGTH_SHORT)  
                        .show();  
            }else{  
                Intent intent = new Intent(getApplicationContext(), SportsActivity.class);  
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
                startActivity(intent);  
                Toast.makeText(getApplicationContext(), "APP被殺死,重啓...", Toast.LENGTH_SHORT)  
                        .show();  
            }  
            // 通知系統任務執行結束  
            jobFinished( (JobParameters) msg.obj, false );  
            return true;  
        }  
    });  
   
    @Override 
    public boolean onStartJob(JobParameters params) {  
        if(Contants.DEBUG)  
            Log.d(TAG,"KeepAliveService----->JobService服務被啓動...");  
        mKeepAliveService = this;  
        // 返回false,系統假設這個方法返回時任務已經執行完畢;  
        // 返回true,系統假定這個任務正要被執行  
        Message msg = Message.obtain(mHandler, MESSAGE_ID_TASK, params);  
        mHandler.sendMessage(msg);  
        return true;  
    }  
   
    @Override 
    public boolean onStopJob(JobParameters params) {  
        mHandler.removeMessages(MESSAGE_ID_TASK);  
        if(Contants.DEBUG)  
            Log.d(TAG,"KeepAliveService----->JobService服務被關閉");  
        return false;  
    }  
}
/**JobScheduler管理類,單例模式,執行系統任務 */ 
public class JobSchedulerManager {  
    private static final int JOB_ID = 1;  
    private static JobSchedulerManager mJobManager;  
    private JobScheduler mJobScheduler;  
    private static Context mContext;  
   
    private JobSchedulerManager(Context ctxt){  
        this.mContext = ctxt;  
        mJobScheduler = (JobScheduler)ctxt.getSystemService(Context.JOB_SCHEDULER_SERVICE);  
    }  
    public final static JobSchedulerManager getJobSchedulerInstance(Context ctxt){  
        if(mJobManager == null){  
            mJobManager = new JobSchedulerManager(ctxt);  
        }  
        return mJobManager;  
    }  
    @TargetApi(21)  
    public void startJobScheduler(){  
        // 如果JobService已經啓動或API<21,返回  
        if(AliveJobService.isJobServiceAlive() || isBelowLOLLIPOP()){  
            return;  
        }  
        // 構建JobInfo對象,傳遞給JobSchedulerService  
        JobInfo.Builder builder = new JobInfo.Builder(JOB_ID,new ComponentName(mContext, AliveJobService.class));  
        // 設置每3秒執行一下任務  
        builder.setPeriodic(3000);  
        // 設置設備重啓時,執行該任務  
        builder.setPersisted(true);  
        // 當插入充電器,執行該任務  
        builder.setRequiresCharging(true);  
        JobInfo info = builder.build();  
        //開始定時執行該系統任務  
        mJobScheduler.schedule(info);  
    }  
    @TargetApi(21)  
    public void stopJobScheduler(){  
        if(isBelowLOLLIPOP())  
            return;  
        mJobScheduler.cancelAll();  
    }  
    private boolean isBelowLOLLIPOP(){  
        // API< 21  
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;  
    }  
}
<--! AliveJobService需要BIND_JOB_SERVICE權限-->  
<service 
         android:name=".service.AliveJobService" 
          android:permission="android.permission.BIND_JOB_SERVICE"/>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章