进程保活方案学习

进程保活方案

进程保活主要有两个方案

  • 提高进程优先级,降低死亡几率
  • 在进程被杀死后进行拉活

进程为什么会死亡

从Linux kernel 2.6.11开始,内核提供了进程的OOM控制机制。当系统出现内存不足的情况时,内核可以根据进程的oom_adj值,
来选择杀死一些进程,以回收内存。Android系统正式基于这一原理进行进程管理。

这里的OOM不是我们应用程序内的oom异常,而是整个系统内存不足

内存管理乃至进程管理都是kernel内核层直接管理地,但是oom_adj的值确是由Framework层中的AMS来管理更新的。我们知道Android的四大组件都是由AMS直接管理的,所以AMS能够悉知四大组件的生命周期,当发生可能会影响进程优先级的事件时,AMS计算出相应改变后的进程oom_adj值,同时修该oom_adj的配置参数,当内存紧张kernel进行内容回收时,则会根据对应的oom_adj优先回收oom_adj值高的进程。这个过程可由下图表示

当然oom_adj只是影响进程被回收的一个因素,除此之外进程占内存和进程存活时间也是比较重要的因素,这里就不一一赘述。

提高进程优先级

Android中各个adj对应的进程状态如下图所示

image

正常而言我们的后台程序是在9-15之间出于一个比较危险的级别,减少进程被杀死概率一是想办法提高进程优先级,减少进程在内存不足等情况下被杀死的概率,同时减小内存的占用也会在oom_adj相同的情况下占据优势,不会优先被回收。提高进程优先级的偏方主要如下

1像素Activity

监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。

通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1,主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(一般为5分钟以内)内会杀死后台进程,已达到省电的目的问题。

具体实现如下

//OnePixelActivity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    window.setGravity(Gravity.LEFT or Gravity.TOP)
    window.attributes.x = 0
    window.attributes.y = 0
    window.attributes.height = 1 //设置1像素点
    window.attributes.width = 1 //设置1像素点
    finishReceiver = FinishReceiver() //设置结束广播
    val filter = IntentFilter()
    filter.addAction(ACTION_FINISH)
    LocalBroadcastManager.getInstance(this).registerReceiver(finishReceiver, filter)
}

//接受锁屏广播
fun register(context : Context) {
    screenReceiver = ScreenReceiver()
    var filter = IntentFilter()
    filter.addAction(Intent.ACTION_SCREEN_OFF)
    filter.addAction(Intent.ACTION_SCREEN_ON)
    context.registerReceiver(screenReceiver,filter)
    mContext = WeakReference(context)
}

//根据广播开启和结束OnePixelActivity
override fun onReceive(context: Context?, intent: Intent?) {
    when(intent?.action){
        Intent.ACTION_SCREEN_OFF->{
            var onePixelIntent = Intent(context,OnePixelActivity::class.java)
            onePixelIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            context?.startActivity(onePixelIntent)
        }
        Intent.ACTION_SCREEN_ON->{
            mContext.get()?.sendBroadcast(Intent(OnePixelActivity.ACTION_FINISH))
        }
        else->{}
    }
}

前台服务

服务进程优先级虽然高于后台进程,但是还是处于比较危险的状态,绑定前台notification之后,可以把优先级提高到可见进程,极大减小进程被杀死的可能性,但是这样用户可以在通知栏看到我们的notification,那有没有两全齐美的方法呢?当然有!

API18以前直接传入一个空的Notifacation即可

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    startForeground(NOTIFICATION_ID, Notifacation())
    return super.onStartCommand(intent, flags, startId)
}

API18之后系统修复这个漏洞,但是道高一次魔高一丈,在我们的service绑定Notifacation之后,实现一个内部 Service,其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service的结束,Notification 将会消失。进程优先级不回变。

 class InnerService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForeground(111, generateNotification(this))
        stopForeground(true)
        stopSelf()
        return super.onStartCommand(intent, flags, startId)
    }
}

进程拉活

广播拉活

接受各种系统广播和第三方广播,原理很简单,但是拉活时间不确定,如何获取第三方广播也是一个问题,应该算是一种不是很稳定的手段。

native进程拉活

原理就是通过 JNI fork出一个 c 进程,c 进程监控主进程是否存活,主要通过管道和文件监控的方式实现监控,发现主进程死后,通过调起一个 service 将主进程拉活。

但是由于5.0以后ActivityManagerService会这样处理的:

Process.killProcessQuiet(app.pid);  
Process.killProcessGroup(app.info.uid, app.pid);

同时杀死进程和进程fork的子进程,所以方法已经无效了。加上本人jni辣鸡,这里就不展示了。

JobSheduler拉活

JobSheduler是Android5.0之后提供的API,JobScheduler API允许开发者在符合某些条件时创建执行在后台的任务。基本上就是我们可以把耗时任务扔给JobSheduler,当条件满足,我们的任务就会执行,本意是好的,但是却被用来干坏事

//使用SystemService获取JobScheduler对象
var jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
var jobId = 111 //唯一jobId
//制定
var name = ComponentName(context.applicationContext, KeepAliveJobService::class.java)
jobScheduler.schedule(JobInfo.Builder(jobId, name)
        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)//任务网络要求
        .setPeriodic(30000)//周期性执行,不能与下面俩个同时使用
//      .setMinimumLatency(3000)//任务最少开始的时延
//      .setOverrideDeadline(50000)//到达deadline即使条件不满足也会执行
        .setRequiresCharging(false)//是否需要正在充电
        .setRequiresDeviceIdle(false)//是否需要手机闲置
        .setRequiresBatteryNotLow(true)//是否手机电量高
        .setRequiresStorageNotLow(false)//是否需要手机存储不低
        .setPersisted(true)//任务是否需要被持久化(重启后继续)
        .build())

我们的JobService

class KeepAliveJobService: JobService() {
    override fun onStopJob(params: JobParameters?): Boolean {

       return false
    }

    override fun onStartJob(params: JobParameters?): Boolean {
        startService()//启动我们的服务
        return true
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return START_STICKY
    }

}

后记

保活的手段比较多,但是需要大家合理使用,共同维护一个Android生态,同时学习进程保活也算是从另一角度去看Android的进程管理。

参考

  • Android进程调度之adj算法
  • Android LowMemoryKiller原理分析
  • Android进程保活招式大全

你可能感兴趣的:(进程保活方案学习)