进程保活方案
进程保活主要有两个方案
- 提高进程优先级,降低死亡几率
- 在进程被杀死后进行拉活
进程为什么会死亡
从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对应的进程状态如下图所示
正常而言我们的后台程序是在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进程保活招式大全