进程保活攻略 —— 技术积累

  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的注册广播

猜你喜欢

转载自blog.csdn.net/qq_30974087/article/details/83620192