原文链接: http://melove.net/blog/2017/03/android-daemon-service-1488942411000.html
在我们进行应用开发时,会遇到上级的各种需求,其中有一条 刚需:后台保活
,更有甚者:
我要我们的应用永远活在用户的手机后台不被杀死
—— 这都 TM 的扯淡
除了系统级别的应用能持续运行,所有三方程序都有被杀死的那一天!当然QQ/微信/陌陌
等会好一些,因为他们已经深入设备的心
;
我们能做的只是通过各种手段尽量让我们的程序在后台运行的时间长一些,或者在被干掉的时候,能够重新站起来,而且这个也不是每次都有效的,也是不能在所有的设备的上都有效的;要做到后台进程保活,我们需要做到两方便:
- 提高进程优先级,降低被回收或杀死概率
- 在进程被干掉后,进行拉起
要实现实现上边所说,通过下边几点来实现,首先我们需要了解下进程的优先级划分:
#进程的优先级
Process Importance
记录在ActivityManager.java
类中:
|
|
#进程回收机制
了解进程优先级之后,我们还需要知道一个进程回收机制的东西;这里参考AngelDevil
在博客园上的一篇文章:
详情参考:【Android Low Memory Killer】
Android
的Low Memory Killer
基于Linux
的OOM
机制,在Linux
中,内存是以页面为单位分配的,当申请页面分配时如果内存不足会通过以下流程选择bad进程来杀掉从而释放内存:
alloc_pages -> out_of_memory() -> select_bad_process() -> badness()
在Low Memory Killer
中通过进程的oom_adj
与占用内存的大小决定要杀死的进程,oom_adj
越小越不容易被杀死;
Low Memory Killer Driver
在用户空间指定了一组内存临界值及与之一一对应的一组oom_adj
值,当系统剩余内存位于内存临界值中的一个范围内时,如果一个进程的oom_adj
值大于或等于这个临界值对应的oom_adj
值就会被杀掉。
下边是表示Process State
(即老版本里的OOM_ADJ
)数值对照表,数值越大,重要性越低,在新版SDK中已经在android
层去除了小于0的进程状态
|
|
Process State
(即老版本的OOM_ADJ
)与Process Importance
对应关系,这个方法也是在ActivityManager.java
类中,有了这个关系,就知道可以知道我们的应用处于哪个级别,对于我们后边优化有个很好地参考
|
|
一般情况下,设备端进程被干掉有一下几种情况
进程结束场景 | 结束方式 | 影响范围 |
---|---|---|
Android 系统自身内存回收机制 | Low Memory Killer | Process State 数值从大到小 |
第三方管理程序清理进程 无 Root 权限 | killBackgroundProcess | Process State 数值大于6进程 |
第三方管理程序清理进程 有 Root 权限 | force-stop or Kill | 除当前前台进程外所有非系统进程 |
Rom 清除进程(用户手动清理) | force-stop or Kill | 所有非系统进程 |
用户手动强制结束 | force-stop | 第三方应用以及非 System 进程 |
由以上分析,我们可以可以总结出,如果想提高我们应用后台运行时间,就需要提高当前应用进程优先级,来减少被杀死的概率
#守护进程的实现
分析了那么多,现在对Android自身后台进程管理,以及进程的回收也有了一个大致的了解,后边我们要做的就是想尽一切办法去提高应用进程优先级,降低进程被杀的概率;或者是在被杀死后能够重新启动后台守护进程
#1.模拟前台进程
第一种方式就是利用系统漏洞,使用startForeground()
将当前进程伪装成前台进程,将进程优先级提高到最高(这里所说的最高是服务所能达到的最高,即1);
这种方式在7.x
之前都是很好用的,QQ、微信、IReader、Keep 等好多应用都是用的这种方式实现;因为在7.x 以后的设备上,这种伪装前台进程的方式也会显示出来通知栏提醒,这个是取消不掉的,虽然Google
现在还没有对这种方式加以限制,不过这个已经能够被用户感知到了,这种方式估计也用不了多久了
下边看下实现方式,这边这个VMDaemonService
就是一个守护进程服务,其中在服务的onStartCommand()
方法中调用startForeground()
将服务进程设置为前台进程,当运行在 API18 以下的设备是可以直接设置,API18 以上需要实现一个内部的Service
,这个内部类实现和外部类同样的操作,然后结束自己;当这个服务启动后就会创建一个定时器去发送广播,当我们的核心服务被干掉后,就由另外的广播接收器去接收我们守护进程发出的广播,然后唤醒我们的核心服务;
|
|
当我们启动这个守护进程的时候,就可以使用以下adb
命令查看当前程序的进程情况(需要adb shell
进去设备),
为了等下区分进程优先级,我启动了一个普通的后台进程,两外两个一个是我们启动的守护进程,一个是当前程序的核心进程,可以看到除了后台进程外,另外两个进程都带有isForeground=true
的属性:
|
|
然后我们可以用下边的命令查看ProcessID
|
|
有了ProcessID
之后,我们可以根据这个ProcessID
获取到当前进程的优先级状态Process State
,对应Linux
层的oom_adj
可以看到当前核心进程的级别为0
,因为这个表示当前程序运行在前台 UI 界面,守护进程级别为1
,因为我们利用漏洞设置成了前台进程,虽然不可见,但是他的级别也是比较高的,仅次于前台 UI 进程,然后普通后台进程级别为4
;当我们退到后台时,可以看到核心进程的级别变为1
了,这就是因为我们利用startForeground()
将进程设置成前台进程的原因,这样就降低了进程被系统回收的概率了;
|
|
可以看到这种方式确实能够提高进程优先级,但是在一些国产的设备上还是会被杀死的,比我我测试的时候小米点击清空最近运行的应用进程就别干掉了;当把应用加入到设备白名单里就不会被杀死了,微信就是这样,人家直接装上之后就已经在白名单里了,我们要做的就是在用户使用中引导他们将我们的程序设置进白名单,将守护进程和白名单结合起来,这样才能保证我们的应用持续或者
#2.JobScheduler机制唤醒
Android系统在5.x以上版本提供了一个JobSchedule
接口,系统会根据自己实现定时去调用改接口传递的进程去实现一些操作,而且这个接口在被强制停止后依然能够正常的启动;不过在一些国产设备上可能无效,比如小米;
下边是 JobServcie 的实现:
|
|
我们要做的就是在需要的时候调用JobSchedule
的schedule
来启动任务;剩下的就不需要关心了,JobSchedule
会帮我们做好,下边就是我这边实现的启动任务的方法:
|
|
#3.系统 Service START_STICKY 机制重启
在实现Service
类时,将onStartCommand()
返回值设置为START_STICKY
,利用系统机制在Service
挂掉后自动拉活;不过这种方式只适合比较原生一些的系统,像小米,华为等这些定制化比较高的第三方厂商,他们都已经把这些给限制掉了;
|
|
这种方式在以下两种情况无效:
Service
第一次被异常杀死后会在5
秒内重启,第二次被杀死会在10
秒内重启,第三次会在20
秒内重启,一旦在短时间内Service
被杀死达到5
次,这个服务就不能再次重启了;- 进程被取得
Root
权限的管理工具或系统工具通过fores-top
方式停止掉,无法重启; - 一些定制化比较高的第三方系统也不适用
#4.其他保活方式
- 利用 Native 本地进程,这个主要使用到 jni 调用底层实现,而且在 Android 5.x 以后对这个限制也比较高,不适用了,暂时不研究
- 集成第三方SDK互相唤醒,这个只要正常集成了第三方的SDK,并使用了他们对应的服务,当一个设备安装的多个应用都集成了某一个第三方SDK时,启动任意一个 app 都会唤醒其他的 app,不过这个在一些新版的国内厂商系统也是做了限制,这种方式并没有什么效果
- 一像素的 Activity 方式(流氓方式),经测试一些手机系统无法检测到解锁和锁屏,不确定是否系统修改了解锁或者锁屏的广播,还是禁用了这些广播,因此此方式无效;
#结语
事事没有绝对,万物总有一些漏洞,就算上边的那些方式不可用了,后边肯定还会出现其他的方式;我们不能保证我们的应用不死,但我们可以提高存活率;
其实最好的方式还是把程序做好,让程序本身深入人心,别人喜欢你了,就算你被干掉了,他们也会主动的把你拉起来,然后把你加入他们的白名单,然后我们的目的就实现了不是 ? ~