让app 的service常驻其实是很流氓的做法,但是需求摆在那里。。。
但是要清除一点:想百分百保活service在当前是无法做到的,只能降低service被杀死的概率,曾经看了多少篇网上大神的牛逼博客,从各个层面分析如何让service不被杀死或者被杀死后重启,特别是从Android系统底层分析,觉得特别牛逼,但是尝试之后没有啥用,以前做service保活总是一个劲的钻研android系统层的方式来保活service,实际上后来发现这是有点不对了,因为会涉及到一些android系统的兼容问题,有时候在这款手机上运行良好,但是到另一款手机就不能正常运行了,后来就去寻找从应用层来降低service被杀死的概率,个人觉得从应用层降低service被杀死的概率会比较稳定
论Service为什么会被杀死
android对于进程的杀死是有优先级的:
1. 前台进程,也就是前台activity
2. 可见进程,就是一个透明的activity下面覆盖着的那个activity就属于可见的,但是这个透明的activity才属于前台
3. 服务进程,也就是service
4. 后台进程,一般情况下app按下home键后就变成后台进城了
5. 空进程
android系统给这些进程设置了oom_adj值,oom_adj值越小表示进程的优先级越高:
名称 | oom_adj | 解释 |
---|---|---|
FOREGROUD_APP | 0 | 前台进程 |
VISIBLE_APP | 1 | 可见进程 |
SECONDARY_SERVER | 2 | 服务进程 |
HIDDEN_APP | 7 | 后台进程 |
EMPTY_APP | 15 | 空进程 |
如上的oom_adj值可能会根据不同手机系统有所不同,但是只要是oom_adj越小,进程优先级越高,进程越不容易被杀死,当android系统的内存紧张的时候就会根据oom_adj来回收进程,因此app在按下home键后被回收是正常的,因为app按下home键盘后就编程后台进程了,而后台进程的优先级比服务进程还要低,我们项目的app在按下home键盘被系统回收的主要原因有以下几种:
1. 我们的APP进程启动后占用内存太大了,基本是打开APP就,然后到应用程序管理里面一看,有60到70MB,现在已经飙升到80MB
2. 有些android深度定制系统的杀死策略太严格,进程清理的太彻底,导致我们APP的service很容易被杀杀杀,基本是手机一锁屏,过几分钟之后立刻就被杀死了
3. 我们app的进程优先级太低,按下home键后没做任何处理,占的内存又很高,所以被回收的概率也很高
综上app进程被杀死的根本原因:系统内存出现不足时,会被Android的low memory killer杀掉
针对以上问题,几种有效的service保活方案
最近一直在做公司的推送android端sdk,我们是把tcp连接写在了Java层,然后开一个service来维护这个tcp长连接,那么问题来了,要让提升消息的可达率就必须保证tcp长连接一直存在,这里涉及到两点保活,一种是service保活,一种是tcp长连接保活。
service保活
比较古老的做法有两种:
1. 一种是用c在底层fork一个进程出来定时扫描,采用am命令启动service,这种方式挺耗电的,而且有android系统的兼容问题,有些android系统是改过的,所以会导致c程序运行失效,例如我遇到过的华为手机很多机型就不兼容,而且还有很严重的耗电问题
2. 一种是fork一个进程出来,然后把fork出来的进程和app主进程建立一条本地长连接来监听两个进程之间的状态,一旦其中一个进程断掉之后另一个进程就能检测到tcp连接断掉了,然后采用am命令拉起service,这种可能没有第一种耗电,但是依然存在这android系统兼容性问题
3. 上述做法在android系统是5.0以下的手机运行良好,因为就算是手机一键清理的时候也只是清理app主进程,而不会清理fork出来的进程,所以service会被成功拉起;而在android5.0以上的手机中,一旦系统一键清理,或者系统后台自动清理,那么会杀死跟app进程有关的进程组,也包括fork出来的进程,所以android5.0以上的手机这个fork进程就貌似是一个鸡肋,增加了耗电,增加了内存消耗,甚至增加了service会被系统清理的概率,而且旧的android系统在系统清理的时候据说是只会清理java进程,不会清理c进程,但是新的android系统全名提升了安全性,会彻底清理app相关的所有进程
4. 现在守护进程已经渐渐无效了,所以一些专业做推送服务的公司已经把守护进程这个保活手段早早的去掉了,人家用的是更加高端有效的做法:TCP长连接多路复用,后面在介绍
在应用层,我尝试过的,了解到的有效的service保活手段有以下几种
1. 在onDestroy方法里面重启service,或者发个广播出来触发启动service,这个要求service在被杀死的时候如果有调用onDestroy()方法,那么service就能被重启,在魅族5.0系统上,我进入到应用管理,找到正在运行的应用,点击停止,这时候service是会跑onDestroy()方法的,我亲自测试过
2. onStartCommand中的flag设置成START_STICKY,或者直接return START_STICKY,这个有些旧版的手机在手机一键清理后service被杀死后会重启起来,但是在一些国产定制机,例如小米,华为,魅族等手机就无效了,在android源码中注释如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
说的是如果service在已经被启动的时候被停止,那么系统将保持service的启动状态,但是不会把原先的intent传递进来,然后系统会尝试这重新创建这个service,因为此时service是在启动状态,所以它一定会在被创建后调用onStartCommand()方法,但是由于intent没有被保存下载,所以里面的参数intent有可能为null,所以要做下判断,这个模式可以保证service被杀死后重新启动起来,而且避免在任意时间被停止,就如后台音乐播放;但是有一点要生命:这个仅仅是针对android原生系统,对于定制的android系统如果有对这方面做过优化,那这个也将无效,而且如果是应用进程被直接杀死,这个方法也无效
3. 把进程做拆分,分成app进程和push进程,其实就是把push进程做到最小,push进程尽量不做业务逻辑处理,只做数据转发和接收,这样push进程占用的内存就变小了,被回收的几率自然也下降
4. 提升app进程,push进程的优先级;一般app进程里面会有一个常驻的service,push进程也会有一个维护长连接的service,把这两个service都设置成前台进程,采用startForeground(id, new Notification()),但是在sdk version > 18的时候通知栏会默认显示一个应用通知,这个是非常不友好的,有些用户就会反馈说通知栏有哥这个,表示不爽等等。。。通过一些渠道了解到,利用android系统的一个bug可以解决上述问题:在Service里面建一个InnerService,这个InnerService必须是static类型的,否则无法启动这个内部service,然后启动InnerService,同时把Service和InnerService都设置成前台service,并绑定同一个id,然后再关闭掉InnerService,这时候通知栏的显示就没了,记住只是stop InnerService而不是stopForeground(),此时service依然是前台进程,亲测有效,下面来展示以下测试的过程和结果:
外层service的部分代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
进入adb命令行:
未设置前台service的app进程:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
app界面在前台
- 1
- 2
- 3
- 1
- 2
- 3
app界面在后台(按下home键)
- 1
- 2
- 3
- 1
- 2
- 3
app界面在后台(按返回键)
- 1
- 2
- 3
- 1
- 2
- 3
设置前台service的app进程:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
app界面在前台
- 1
- 2
- 3
- 1
- 2
- 3
app界面在后台(按下home键)
- 1
- 2
- 3
- 1
- 2
- 3
app界面在后台(按返回键)
- 1
- 2
- 3
- 1
- 2
- 3
通过以上两种对比可知,app进程设置了前台service的app进程在退到后台后依然保持oom_adj = 2的进程优先级,相当于app退到后台后仍然是可视进程,而没有设置前台service的app进程的oom_adj = 7编程了后台进程
设置前台service后app的push进程
- 1
- 2
- 3
- 1
- 2
- 3
app界面在前台
- 1
- 2
- 3
- 1
- 2
- 3
app界面在后台(按下home键)
- 1
- 2
- 3
- 1
- 2
- 3
app界面在后台(按返回键)
- 1
- 2
- 3
- 1
- 2
- 3
对比微信:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
可以看到我们app的进程优先级已经和微信一样了,进程优先级提升了,其实已经在很大程度上降低了app进程或者push进程被杀死的概率了
5. 守护Service,开一个Service单独运行在独立的进程中,在守护service里面定时去start app进程的service,而app进程里面的service也定时去start守护service;或者在这两个service之间维护一条tcp连接也可以做到实时检测
6. 采用bind service加上start service,因为bing service之后即使stop service那service也还是存在,不会调用onDestroy,只有等service被解绑之后才用调用onDestroy,有时候你到应用程序里面去停止了进程的service,列表里面看运行中的程序是找不到service了,但是实际上service还是在运行的,过一会儿就又刷出来了
7. 捕获第三方推送的广播接收,在app程序中添加一个静态广播专门用来接收第三方的广播,收到广播后就唤醒Service,例如捕获小米推送,个推,但是有些第三方的广播是有权限限制的,因此不是所有的第三方广播都能被收到
8. 监听系统静态广播,开机自启广播,网络变换广播,USB接入和拔出的广播,系统屏幕解锁广播,这几个广播都是静态注册的广播;但是这种方式也是不可靠的,因为有些手机在程序停止运行之后连静态广播都不能收到了。。。增加广播监听只是在一定程度上降低service被杀后重新拉起的概率,但是系统的静态广播在app被杀死后是无法收到的
9. 监听第三方的静态自定义广播,静态自定义广播是有可能可以被接收的,只要发送方按照一定的方式发送就行,具体可以参考:如何在app被杀死的情况下仍然可接收静态自定义广播,这边有个前提就是第三方推送发送广播的时候必须要按照指定方式发送才可行,优点是无需接入第三方推送,只是监听他们的广播即可,很轻量级
10. 接入第三方推送服务,但是我们不用他们的推送,我们还是用我们自己的推送,不过我们可以捕捉到他们的一些广播,service启动的action等来让它们来触发拉起我们的程序,比如说集成友盟推送,当启动uc的时候可能会发一些广播,通过action启动一些service,我们就专门监听这种广播,监听这些action就能成功的拉起我们的服务,这种办法是通过第三方推送来触发我们的推送服务,人家是专门做推送的,更专业,那我们就直接用呗,达到不使用他们的推送,但是们的app也加入了互相唤醒的app 的行列中去了
11. 市面上的推送服务现在大多使用长连接多路复用来保证app互相唤醒,就是一台设备商有多个app集成了一种推送,那么只会有一个宿主app会保持跟服务器之间的长连接,其他app都是共享这条长连接,宿主app就负责路由各个app的推送消息,当有一个app存活,其他app都被杀死,这是后有消息过来,那么这个活着的app就会把死掉的app唤醒,而app之间的互相唤醒是采用action来start service的方式唤醒的,我了解到友盟推送和个推都是这么做的;但是长连接多路复用的保活效果是建立在用户量上面的,用户量大了,集成推送的app多了,唤醒几率就越大,唤醒效果就越好,例如一些高频使用的app,uc,支付宝等,用户可能频繁的点开这些app,那么就会经常唤醒其他app,长连接多路复用因为做到了连接共享所以更加省电,省内存;但是的但是,道高一尺,魔高一丈,嘴型android 6.0系统已经禁止app互相唤醒了。。。而且去掉了网络切换静态广播
12. 其实最最根本的解决方案还是把app直接加入手机白名单~,绝对百分百不会被杀死,亲测有效,有些人认为为啥微信消息接收那么即时,微信不会被手机清理,因为微信已经牛逼到手机厂商自动把微信和QQ加入了手机白名单,小米系统更是直接把微信app的相关进程设置成守护进程:
小米4c手机进入adb命令行:
输入dumpsys activity
app的push进程设置前台service后sync.push的进程优先级是vis,可视进程
而微信app的push进程com.tencent.mm:push的进程优先级的prcp,protect process保护进程,可能是小米手机自定义的进程优先级别
下面几种是不知道有没有效果的service保活手段:
1. 配置文件里面加入android:persistent=”true”提升进程优先级,但是这个貌似只有对系统应用才有效,具体原理不说了,自己百度
2. 设置service的action优先级:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这种效果我感觉微乎其微,即使不设置也没啥影响
TCP长连接保活:
- 心跳包保持长连接畅通,定时发心跳包是为了避免NAT,把发送心跳包的代码写到维护长连接的Service里面,每次发心跳包就start service,因为心跳包是用闹钟实现的,有可能service死了,但是系统闹钟还是在正常执行的,这时候就能把维护长连接的service拉起,然后只要心跳包一超时就立马把连接断开重连,这是为了避免无效连接,关于无效连接请看TCP长连接的一些坑
- 冗余心跳包,手机锁屏解锁,网络切换,USB接入,拔出,app帐户登录都发一个心跳包,长连接请求超时也补发一个心跳包
- 断线重连策略,采用时间间隔递增,第一次断开马上连上,连接失败隔2s再重连,重连失败再隔4s再重连,以此类推,至于时间间隔可以自己指定比较适用的算法计算出来,这时候也会遇到要是网络闪断的情况会导致连接连伤后又马上断开,然后马上重连成功后连接再次断开,这样也会导致短时间内重连多次,这时候可以对重连也做连接间隔的递增策略,如果检测到上次连接成功的时间在1s之内那么就采用时间间隔递增重连,如果上次重连在比较久之前了,那么就不采用时间间隔递增重连,直接正常重连
- 心跳包周期策略,当前我们的app只对wifi网络和数据网络指定了不同的心跳包,没有很详细的心跳包策略,但其实后期可以指定详细的心跳包策略来进一步提升tcp连接的稳定性
- 在手机休眠的时候采用系统闹钟可以短暂唤醒cpu执行任务,例如利用系统闹钟来执行周期心跳,在网络数据返回的时候,也会唤醒cpu,所以不需要wakelock,因为从其他渠道了解到:
a. Alarm唤醒后,有足够的cpu时间来发包,所以不用wakelock
b. 网络回包可唤醒机器(但是在api level >= 23就关闭了所有网络),而且小米4c手机锁屏后过一会儿也会自动禁止网络,导致所有连接全部失效,小米4c手机的这种情况是我亲测的,确实有这种问题 - 跨进程通信在我们的sdk里面是采用aidl和广播实现,但是aidl有时候会有问题,在service被杀死的时候会爆出DeadObjectException,就是表示service已经被杀死了,但是可能程序依然在执行aidl的方法,而且aidl代码冗余,广播的话传输的数据大小有限制,可以考虑采用Socket和ServerSocket来实现进程间的通信,采用TCP连接实现进程间的通信可以实现两个进城之间互相监听,只要有一个进程死了,另一个进程马上就能收到反馈,这时候可以做相关操作,例如再把死掉的进程拉起来等
针对不同定制系统的问题:
1. 小米MIUI和魅族flyme是两个比较深android定制系统,超级坑超级坑,系统清理策略太特么严格了,一清全死,怎么起都起不来,秒杀一切推送,针对这种机型我们只能说,可以的话默默的加入白名单吧,亲,要不你就去跟手机厂商谈合作,要不你就用他们家的推送,例如MiPush
2. 三星手机倒是还好,基本按照以上的做法在大部分5.0一下的系统都可以在很大程度上保持service常驻,因为三星的android系统是比较纯净的
3. 华为的手机系统也是定制的,不过也不知道为啥,我们的c守护进程在其他手机上跑得好好的,在华为手机上跑超级耗电,而且无法start service,因为这个c程序是网上大牛写的,不是我写的,我也找不到原因所在,甚至于在华为某些机型上面会导致超级耗电,要不就是导致手机超级卡顿,要重启才能使用,所以华为的定制机我们是把守护进城去掉了,所以我们的service变得非常脆弱
4. 市面上的主流手机就是小米,三星,华为,魅族,vivo,oppo了,但是当我们专门针对这些进行做适配的时候,版本正式发布了,你会发现反馈问题的用户往往都不是使用这些手机。。。都是使用什么联想,htc等等好坑的手机,有广播发送需要一两分钟才能收到
5. 根据手机渠道来分版本发布,小米版,魅族版等等
经过测试采用上述方式后,只要不使用手机自身的一键清除,按下home键后service常驻情况后台的机型:
1. 小米4C——4.4Android系统——service可以常驻(但是守护进程无效,因为小米的一键清理太彻底)
2. 魅族MX5——5.0Android系统——service在手机锁屏后几分钟就被杀死并且无法重启(守护进程无效,魅族一键清理更彻底)
3. 华为荣耀6,6plus——5.0Android系统——service在手机锁屏后马上就被杀死并无法重启(守护进程无效,守护进程导致严重耗电,守护进程无法正常运行)
4. 华为很旧的机型—–4.4Android系统——service常驻后台,就算被杀死或者手机一键清理也会被守护进程拉起(守护进程有效,守护进程基本不会被杀死)
5. 华为meta7——4.4Android系统——service常驻后台(未知守护进程是否有效,待测)
6. 三星部分机型service可以常驻,但是部分机型service无法常驻并且守护进程无效,具体待测
7. vivo和oppo的手机上service基本无法常驻,死的一干二净明明白白,丝毫没有任何挣扎的痕迹
8. android5.0以上系统守护进程据说是无效,但是我没有亲测,因为5.0以下的android系统只是杀死App主进程,但是5.0以上的android系统是杀死跟App有关的所有进程组,所以game over。。。
9. 但是最最特么坑爹的是居然能看到确实有些app可以在各种各样的机型上保持service常驻。。。我也是醉了,虽然有些是跟厂商合作,但是我觉的应该还是有其他办法能再次提升service常驻的概率,至少保持按下home键后手机锁屏service不被杀死吧,看到这里要是有大神知道的是什么方式的请指导指导,分享分享,给小弟一个方向,多谢
不同的andorid系统版本:
1. android5.0开始系统做的越来越安全了,5.0以下start service可以隐式启动,就是不设置intent的package,但是5.0开始就必须要设置了
2. 最近刚出的android N已经把静态广播网络变换广播去掉了。。。这个对我们来说实在是巨大的打击,编程了只能动态注册。。。
问题解答
华为和oppo好坑爹啊,现在做的一个及时通信软件,在它们手机上一锁屏,很快就会挂掉
========华为对app后台运行做的很严格,一般需要打开后台保护开关,或者集成华为推送来打辅助~oppo的话暂时无解咯
看了好几篇博主的文章,学习到很多,谢谢,但是这种方法,我在华为P8上面测试发现没有效果,测试环境是EMUI4.0.1,对应android6.0,内部类stopSelf后仍然在通知里能看到,测试发现主包名包含alarm或者clock,锁屏后是默认不杀死的,但是手动杀死后(点击方块出现多任务,删掉该app图片),也无法重启,如果谷歌在中国,国内android开发省多少心.
===========手动杀死是没办法做到重启的。另外android6.0系统应该是不会有通知栏的提示的,我之前试过是这样的,android7.0就会有通知栏提示了;华为手机即便是用如上方法,如果保护开关不打开,锁屏后几分钟内app仍然会被全杀;android6.0的华为手机,可以用后台偷偷播放音乐来做到进程不被杀死,我在另外一篇文章有提到,但是即便是这样,在锁屏后一段时间后,app的网络访问权限也会被禁用,所以进程不死,但是无法访问网络;所以做IM进程保活分为两部分,进程保活,TCP长连接保活,缺一不可