在Android 4.4及以后的系统中,应用能否常驻内存,一直以来都是相当头疼的事情,尤其移动端IM、消息推送这类应用,为了保证“全时在线”的概念,真是费尽了心思。
虽然APP常驻内存对于用户来说比较”恶心”,但是在诸如IM和消息推送这类场景来说,APP的常驻内存却尤其重要,而且很多时候用户也会要求APP能够保证长久运行。
因为Android机型太多太杂,以及各厂商定制ROOM的差异,Android应用保活没有一劳永逸和万能的方法。
常见的Android应用保活方法
1)监听广播方式:
通过监听一些全局的静态广播,比如开机广播、解锁屏广播、网络状态广播等,来启动应用的后台服务。目前,在高版本的Android系统中已经失效,因为高版本的Android系统规定应用必须在系统开机后运行一次才能监听这些系统广播,一般而言,应用被系统杀死后,基本无法接收系统广播。
2)提高Service的优先级:
以前提高Service优先级方法很多,比如onStartCommand返回START_STICKY使系统内存足够的时候Service能够自动启动、弹出通知、配置service的优先级等,这些方式只能在一定程度上缓解service被立马回收,但只要用户一键清理或者系统回收照样无效。
3)全局定时器:
还有一种方法就是在设置一种全局定时器,定时检测启动后台服务,但这种方法目前也已经无效,因为应用只要被系统杀死,全局定时器最后也只成了摆设。
4)应用中的双service拉起:
经过测试,只要当前应用被杀,任何后台service都无法运行,也无法自行启动。
5)应用中的双进程拉起:
这种方式就是传说中的使用NDK在底层fork出一个子进程,来实现与父进程之间的互拉。在Android4.x还是非常有效的,但是高版本的Android系统的系统回收策略已经改成进程组的形式了,如果系统要回收一个应用,必然会杀死同属于一个进程组的所有进程,因此最后导致双进程无法拉起。
剖析Android的Service
Service的特征类似于Activity,其区别是它没有交互界面,且可长时间运行在后台,即使它所属的应用已经退出,Service仍然可以继续在后台运行。Service无法自行启动,访问者启动它的方式分为两种,即startService(绑定式)和bindService (非绑定式),相关介绍如下。
startService:
即非绑定式。访问者使用这种方式启动service后,被启动的service将不受访问者控制,也无法与访问者进行数据通信,它会无限地运行下去,必须调用stopSelf()方法或者其他组件(包括访问者)调用stopService()方法来停止。它的生命周期:onCreate->onStartCommand()->……>onDestory(),其中,onCreate用于初始化工作,多次调用startService启动同一个服务,onCreate方法只会被调用一次,onStartCommand会被调用多次,onDestory在销毁时也只会被调用一次。即时通讯开发
bindService:
即绑定式。访问者使用这种方式启动service后,被启动的service受访问者的控制,访问者将通过一个IBinder接口的对象与被绑定的service进行通信,并且可以通过unbindService()方法随时关闭service。一个service可以同时被多个访问者绑定,只有当多个访问者都主动解除绑定关系之后,系统才会销毁service。它的生命周期:onCreate->onBind->….>onUnbind->onDestory,其中,onBind用于返回一个通信对象(IBinder)给访问者,访问者可以通过该IBinder对象调用service的相关方法。当多个访问者绑定同一个service时,onCreate只会被调用一次,onBind、unOnbind方法会被调用与访问者数目相关的次数,onDestory在销毁时只会被调用一次。
有一点需要注意的是:如果一个service被startService启动,然后其他组件再执行bindService绑定该service,service的onCreate不会再被回调,而是直接回调onBind方法;当该组件unBindService时,service的onUnbind方法被回调,但是service不会被销毁,直到自己调用stopSelf方法或者其他组件调用stopService方法时才会被销毁。
理解AIDL和远程Service调用
接上一节。bindService绑定方式启动service分为两种:本地service和远程service。
本地service是指绑定启动的service实现在它所属的应用进程中,其他组件访问者与本地service之间的通信是通过IBinder接口对象实现的;远程service是指绑定启动的service实现在其他应用进程中,也就是另一个APP中,他们之间的通信则是通过IBinder接口的代理对象实现的,而这个代理对象必须通过AIDL方式来构造。
AIDL(Android Interface DefinitionLanguage,即接口描述语言)使用用于约束两个进程之间通讯的规则,可以实现Android终端上两个不同应用(进程)之间的通信(IPC)。
AIDL实现的步骤非常简单,阐述如下:
1)在A应用创建IPerson.aidl接口文件(注意AIDL语言的编写规则,这里不详解);
2)将创建的IPerson.aidl接口文件拷贝到B应用中。
packagecom.person.aidl
interfaceIPerson{
String getName();
intgetAge();
然后:我们只需要保存.aidl文件,编译器就会自动生成所需的IPerson.java文件,保存在gen目录下,该文件包含一个内部类IPerson.Stub,它实际上继承于Binder对象,即充当通信所需的IBinder代理对象。
这里有几点需要注意,将会影响两进程之间是否能够通讯成功:
1)接口名与aidl文件名相同;
2)应用A、应用B中的.aidl文件必须相同,并且它们所属的包名也必须要一样。
先来看看单进程守护如何实现保活
我们先分析一下绑定方式启动Service流程。
以某Android应用(以下简称应用X)中的守护Service启动保活助手A的守护Service为例:
1)当应用X中的Service通过bindService绑定保活助手A的Service时,保活助手A会回调onBind方法返回一个IPerson.Stub的代理对象给应用X;
2)当应用XService中的onServiceConnected被回调时,说明应用X绑定保活助手A的Service成功;
3)当解绑时,应用X中的onServiceDisconnected和保活助手A中的onUnbind被调用。
有了前面的基础,接下来就分析如何在不同的应用进程中创建守护service,通过检测彼此的绑定情况来唤醒彼此:
1)当应用X绑定保活助手A时(浅绿色字体),如果保活助手A被系统杀死,应用X的onServiceDisConnected被回调,我们可以在该方法中执行bindService方法再次尝试绑定唤醒保活助手A;
2)当保活助手A绑定应用X时(橙色字体),如果应用X被系统杀死,保活助手A的onServiceDisconnected被回调,我们可以在该方法中执行bindService方法再次尝试绑定唤醒应用X。
至此,经过这种双重绑定守护来到达应用保活的目的。
新型双进程守护的保活实践
从程度上来说,单进程守护方式差不多可以满足应用长时间保活的要求,虽说让人感觉有点怪异,但实现起来也不是很难,效果明显。
但是,随着进一步的测试,我连续两次强杀应用X,应用X就无法启动了,这是因为当应用X“体积”较大,启动前需要加载诸如大量的静态变量或者Application类中的变量等,导致启动较慢,当我第一次强杀应用X时,保活助手A是执行绑定启动应用X保活服务的,但我继续第二次强杀应用X时,保活助手A可能还未与应用X绑定,最终导致保活助手A无法检查应用X的绑定状态而失效。
双进程守护:针对单进程守护出现的问题,当应用X“体积”较大时,我们可以采用双进程守护,即实现两个保活助手,它们彼此双向绑定来对应用X进行守护。我们采用“环”的形式来进行互拉,无论谁被杀死,只要系统杀掉剩余的任何一个进程,最后活着的进程都能够将其他被杀进程拉起来。当然,这里还有个小技巧,为了防止两个保活助手进程同时被系统杀死,我这里采取高低优先级的方式来解决。
原创文章,作者:Carrie001128,如若转载,请注明出处:https://blog.ytso.com/270538.html