在之前的文章《如何保证Service在后台不被杀死?》中,笔者分析了为什么要保活Service、Service的几种保活方法和Service保活的意义。今天的这篇文章就更进一步,讲解进程保活的方法和意义。
(1)什么是进程保活?
拿我们的手机应用程序QQ来说,我们只要最近打开过QQ一次,那么QQ就可以一直在后台运行而不会自动关闭。这对于所有应用程序来说,都是梦寐以求的事,试想下,你的应用程序可以一直运行在用户的手机中,不会被关闭,那么这个应用程序的用户日活量将达到一个怎么样恐怖的数值。(当然了,这总归是梦想,假若所有程序的进程都是不会消亡的,那么Android平台的流畅性也就无从谈起)
我们知道,每个应用程序在打开的时候,都会自动创建一个Linux进程和一个UI主线程,我们今天的主角就是如何保证一个Linux进程存活。
(2)进程真的可以永久存活吗?
这肯定是不行的,正如笔者在分析Service保活的时候,所说的一样,没有真正意义上的保活方式,所有的应用程序进程都会被停止,我们能做的就是“伪不死”,即是:
1)利用各种方式尽量保证进程存活
2)在该进程被关停之后立刻将这个进程重新启动。
前面的内容中,我们分析了进程保活的意义和进程保活的真实状况。那么接下来,我们就来进一步分析,为什么进程会被终止掉?知道了这个缘由,我们才有应对的方法。
(1)进程回收机制
在Android中,即使当用户退出应用程序之后,应用程序的进程也还是存在于系统中,这样是为了方便程序的再次启动,但是这样的话,随着打开的程序数量的增加,系统的内存会变得不足,就需要杀掉一部分进程以释放内存空间。至于是否需要杀死一些进程和哪些进程需要被杀死,是通过Low Memory Killer机制来进行判定的,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生(看到这里是不是很熟悉,这个Kiiler经常出现在内存溢出,内存泄漏的处理场景中)。
采取的判定标准就是该进程的oom_adj值的大小。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。我们需要记住以下三点:
a. 进程oom_adj值越大,越容易被终止;
b. 普通应用程序进程的oom_adj>=0,系统应用进程的oom_adj才可能<0;
c. 进程oom_adj值不是固定不变的,随着用户的操作,每个应用进程的优先级都会发生变化,进程对应的oom_adj值也会因此改变。
(2)进程优先级与oom_adj对应关系
“前台可见进程服务于后台空进程”,——既描述了线程的种类,也描述了线程的优先级:前台进程>可见进程>服务进程>后台进程>空进程。具体分析,可参见笔者之前的文章《Android进程和线程详解》。
下面列一张表,表里的内容展示了进程类型和oom_adj值的一般对应关系:
红色部分代表比较容易被杀死的 Android 进程(OOM_ADJ>=4),绿色部分表示不容易被杀死的 Android 进程,其他表示非 Android 进程(纯 Linux 进程)。在 Lowmemorykiller 回收内存时会根据进程的级别优先杀死 OOM_ADJ 比较大的进程,对于优先级相同的进程则进一步受到进程所占内存和进程存活时间的影响。
上面的内容分析了,线程被终止的原因,那么接下来就来具体实现线程保活了。
(1)利用系统Service机制保活
修改onStartCommand(..)方法的返回值为START_STICKY可实现停止服务之后重启服务。代码如下:
@Override
public int onStartCommand(Intent intent, int flags, int startId){
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
// return START_REDELIVER_INTENT;
}
(2)利用系统发出的广播保活
下面,列举一些常用于保活进程的系统广播:
(3)利用第三方应用广播拉活
该方案总的设计思想与接收系统广播类似,不同的是该方案为接收第三方 Top 应用广播。通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。
(4)为应用启动一个前台服务
调用系统api启动一个前台的Service进程,在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,如果当前的应用退到了后台,仍可运行。
(5)利用系统的漏洞来启动一个前台服务(最常见)
这种利用系统漏洞创建的前台服务与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。实现代码如下:
1)API < 18,启动前台Service时直接传入new Notification();
//API < 18 ,此方法能有效隐藏Notification上的图标
@Override
public int onStartCommand(Intent intent, int flags, int startId){
if (Build.VERSION.SDK_INT < 18) {
startForeground(GRAY_SERVICE_ID, new Notification());
}
return super.onStartCommand(intent, flags, startId);
}
2)API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理。
//给 API >= 18 的平台上用的灰色保活手段
@Override
public static class GrayInnerService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(GRAY_SERVICE_ID, new Notification());
stopForeground(true);
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
}
总结(正确姿势:略过笔者bb 看总结):本文实际上是写给“心怀不轨”的开发者看的,这部分开发者想保活他们的应用程序,和他们的用户形影不离。于是,他们必须掌握Java进程/线程相关知识、Java回收机制、进程优先级、和保活进程的常见方法。其实,真的来说,保活的进程过多,会拖慢Android平台的流畅性,对用户来说,最直观的感觉,就是手机变卡,这也是安卓手机一直被诟病的所在。
引用笔者在《如何保证Service在后台不被杀死?》结尾时所说的话:“以上的方法实际上都只是做到了“伪不死”,并不会真的不死,用户手动强制停止,你一点办法都没有,所有的操作都是.in vain 所以做应用的人要在“实现服务”和“用户体验”之间,权衡清楚,毕竟任何应用的主人都是使用者。”