Android 進程保活相關技術的認識與理解

  1. Android 進程優先級介紹
  2. Android 進程回收策略介紹
  3. Android 目前系統保活策略
  4. 項目中使用進程保活代碼

1、Android進程等級分級和等級介紹
Android 系統將盡量長時間地保持應用的進程,但是系統運行內存是有限的,所以爲了 新建進程或者運行更重要的進程,最終還是需要清除舊進程來回收內存。

所以分區進程重要程度,系統會根據進程的運行組件和組件的狀態,將每一個進程放入“重要性層級結構”,必要時系統會首先清除掉重要性最低的進程,在內存不足時,再清除重要性稍低的進程,以此類推,以回收系統資源。

進程重要性分級

	前臺進程
	可見進程
	服務進程
	後臺進程
	空進程

前臺進程:用戶當前操作所必需的進程,只有在內部不足以支它們同時運行。系統纔會終止它們。

  • 用戶正在交互的Activity(已調用 onResume())
  • Service,綁定在用戶正在交互的Activity
  • 正在前臺運行的Service(服務已經調用 startForeground())
  • 正在執行生命週期的Service(onCreat,onStart,onDestory)
  • 正在執行 onReceive 方法的廣播 BrocadcastReceiver

可見進程:沒有任何前臺組件,但是仍然會影響用戶在屏幕上所見內容的進程。

  • 不在前臺,但是用戶仍然可見(已調用 onPause())例如沒dialog覆蓋
  • 綁定在可見 Activity的Service

服務進程:沒有任何前臺組件,但是通過它們執行一些用戶關心的操作(音樂播放,網絡下載)

  • 正在運行 startService() 方法啓動的服務

後臺服務:後臺進程對用戶體驗沒有直接影響,系統可以隨時終止它們

  • 對用戶不可見的Activity的進程,(已調用 onstop方法)

空進程:保留這種進程的唯一作用是用做緩存,縮短下次在運行組件所需的啓動時間

  • 不含任何活動組件的進程

2、Android 進程回收策略
衆所周知,Android是基於Linux系統的。在Android進程回收策略中,Android進程與Linux進程根據OOM_ADJ閾值進行區分:

OOM_ADJ >= 4:比較容易被殺死的進程
OOM_ADJ 0 ~ 3:不容易被殺死的進程
OOM_ADJ < 0 :純Linux進程,非Android進程

當Android系統察覺設備內存不足時,會按照閾值從大到小殺死進程。
具體的oom_adj值的意義我們可以查看AOSP中的com.android.server.am.ProcessList 文件(其中本人添加了一些中文註釋):

/** 
 1. Activity manager code dealing with processes. 
 */  
final class ProcessList {  
    ...  
  
    // OOM adjustments for processes in various states:  
  
    // Adjustment used in certain places where we don't know it yet.  
    // (Generally this is something that is going to be cached, but we  
    // don't know the exact value in the cached range to assign yet.)  
    // 未知進程,通常是用作緩存  
    static final int UNKNOWN_ADJ = 16;  
  
    // This is a process only hosting activities that are not visible,  
    // so it can be killed without any disruption.  
    // 擁有不可視的Activity的進程,可以不影響影響用戶的情況下殺掉  
    static final int CACHED_APP_MAX_ADJ = 15;  
    static final int CACHED_APP_MIN_ADJ = 9;  
  
    // The B list of SERVICE_ADJ -- these are the old and decrepit  
    // services that aren't as shiny and interesting as the ones in the A list.  
    // 一些舊的服務進程  
    static final int SERVICE_B_ADJ = 8;  
  
    // This is the process of the previous application that the user was in.  
    // This process is kept above other things, because it is very common to  
    // switch back to the previous app.  This is important both for recent  
    // task switch (toggling between the two top recent apps) as well as normal  
    // UI flow such as clicking on a URI in the e-mail app to view in the browser,  
    // and then pressing back to return to e-mail.  
    // 用戶使用的前一個進程  
    static final int PREVIOUS_APP_ADJ = 7;  
  
    // This is a process holding the home application -- we want to try  
    // avoiding killing it, even if it would normally be in the background,  
    // because the user interacts with it so much.  
    // 主界面進程  
    static final int HOME_APP_ADJ = 6;  
  
    // This is a process holding an application service -- killing it will not  
    // have much of an impact as far as the user is concerned.  
    // 持有應用服務的進程  
    static final int SERVICE_ADJ = 5;  
  
    // This is a process with a heavy-weight application.  It is in the  
    // background, but we want to try to avoid killing it.  Value set in  
    // system/rootdir/init.rc on startup.  
    // 重量級應用進程  
    static final int HEAVY_WEIGHT_APP_ADJ = 4;  
  
    // This is a process currently hosting a backup operation.  Killing it  
    // is not entirely fatal but is generally a bad idea.  
    // 執行備份操作的進程  
    static final int BACKUP_APP_ADJ = 3;  
  
    // 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.  
    // 擁有用戶僅可見、不可交互的Activity的進程  
    static final int VISIBLE_APP_ADJ = 1;  
  
    // This is the process running the current foreground app.  We'd really  
    // rather not kill it!  
    // 前臺運行的進程  
    static final int FOREGROUND_APP_ADJ = 0;  
  
    // This is a system persistent process, such as telephony.  Definitely  
    // don't want to kill it, but doing so is not completely fatal.  
    // 系統常駐進程  
    static final int PERSISTENT_PROC_ADJ = -12;  
  
    // The system process runs at the default adjustment.  
    // 系統進程  
    static final int SYSTEM_ADJ = -16;  
  
    // Special code for native processes that are not being managed by the system (so  
    // don't have an oom adj assigned by the system).  
    // 爲native進程保留,他們不被系統管理  
    static final int NATIVE_ADJ = -17;  
  
    ...  
}  

Android 進程被殺死情況:
①觸發系統進程管理機制回收(Lowmemorykiller):這種方法會按照閾值從大到小進行清理
②被沒有進行Root的第三方應用殺死(使用killBackgroundProcess方法):這種方法只能殺死OOM_ADJ爲4以上的進程
③被進行Root的第三方應用殺死(使用force-stop或者kill):理論上來說可以殺死所有進程,但一般只會清理非系統關鍵進程和非前臺可見進程
④廠商的殺進程功能(force-stop或者kill):理論上來說可以殺死所有進程,包括Linux原生進程
⑤用戶主動“強行停止”進程(force-stop):只能停用第三方和非system/phone進程應用(停用system進程應用會造成Android系統重啓)
我們可以通過adb shell命令實時查看這個adj值。

adb shell

ps | grep <關鍵字>

USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
root      1     0     812    668   ffffffff 00000000 S /init
root      2     0     0      0     ffffffff 00000000 S kthreadd

adb shell

cat /proc/PID/oom_adj

cat命令執行後,會得到一個adj整數數值。

3. Android 目前系統保活策略

A: Service的onStartCommand函數返回START_STICKY

START_STICKY是官方提供的參數,意思是當service被內存回收了,系統會對service進行重啓。面對360等內存回收,並沒什麼作用。

B:在service 的onDestory裏面重啓服務

onDestroy()方法只有在service正常停止的時候纔會被調用,面對上述回收的第二與第三種方法沒有效果。

C:守護線程相互監聽

AB兩個進程,A進程裏面輪詢檢查B進程是否存活,沒存活的話將其拉起,同樣B進程裏面輪詢檢查A進程是否存活,沒存活的話也將其拉起,而我們的後臺邏輯則隨便放在某個進程裏執行即可。

這種方法面對回收的時候,其實作用也不大,而且很消耗性能。另外,也有人提到過使用兩個native進程監控,那種方法沒試過。

D: AlarmManager or JobScheduler循環觸發

public class AlarmService extends Service {


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


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        initAlarm(this);
        Log.d("czh", "AlarmService onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    private void initAlarm(Context context) {
        Intent intent = new Intent(context, AlarmReceiver.class);
        intent.setAction("repeating");
        PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);

        //開始時間
        long firsTime = SystemClock.elapsedRealtime();
        AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
        am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firsTime, 5 * 1000, sender);
    }
}

PendingIntent.getBroadcase的註冊廣播

E:與系統service綁定

論Android應用進程長存的可行性 一文中,提到用NotificationListenerService代替普通service,從而達到保活的作用。 原理是沒有問題,在小米4上親測過後,發現並沒什麼用。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class SimulateNotificationService extends NotificationListenerService {

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        super.onNotificationPosted(sbn);
    }


    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        super.onNotificationRemoved(sbn);
    }
}

<service android:name=".service.SimulateNotificationService"
        android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
        android:process=":test5">

        <intent-filter>
            <action android:name="android.service.notification.NotificationListenerService" />
        </intent-filter>


    </service>

F:監聽系統Receiver保活

使用Receiver來檢測目標進程是否存活不失爲一個好方法,靜態註冊一系列廣播,什麼開機啓動、網絡狀態變化、時區地區變化、充電狀態變化等等等等,這聽起來好像很6,而且在大部分手機中都是可行的方案,但是對於深度定製的ROM,是的,又是深度定製,你沒有看錯,而且代表性人物還是魅族、小米,這兩個業界出了名的喜歡“深度定製”系統。
自從Android 3.1開始系統對我們的應用增加了一種叫做STOPPED的狀態,什麼叫STOPPED?就是安裝了之後從未啓動過的,大家可能經常在網上看到對開機廣播的解釋,說要想應用正確接收到開機廣播那麼就得先啓動一下應用,這個說法的技術支持就來源於此,因爲自Android 3.1後所有的系統廣播都會在Intent添加一個叫做FLAG_EXCLUDE_STOPPED_PACKAGES的標識,說白了就是所有處於STOPPED狀態的應用都不可以接收到系統廣播。

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

這種方法面對第二種回收方式有效,但是面對小米之類的後臺回收,還是無能爲力。

public class NotificationService extends Service {


    private final static int SERVICE_ID = 1001;

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


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT < 18) {
            startForeground(SERVICE_ID, new Notification());
        } else {
            Intent innerIntent = new Intent(this, InnerService.class);
            startService(innerIntent);
            startForeground(SERVICE_ID, new Notification());
        }


        return super.onStartCommand(intent, flags, startId);
    }


    /**
     * 給 API >= 18
     */
    public static class InnerService extends Service {

        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            startForeground(SERVICE_ID, new Notification());
            stopForeground(true);
            stopSelf();
            return super.onStartCommand(intent, flags, startId);
        }

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

    }

}

I:不同的app進程,用廣播相互喚醒
如果你手機安裝了各種app,或者應用了各種第三方代sdk,即可互相喚醒。
假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你打開任意一個阿里系的app後,有可能就順便把其他阿里系的app給喚醒了。

這個方法針對內存回收的三種方式均有效,只要有一個活着,其他的就會活下來。

這篇文章是東拼西湊出來的,目的是爲了加深印象,方面自己之後的學習。
分別引用:
安卓筆記俠

Android後臺進程保活策略彙總

關於android中PendingIntent.getBroadcase的註冊廣播

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