Android进程保活关键点:
1.提供进程优先级,降低进程被杀死的概率
2.在进程被杀死后,进行拉活
/***********************************************进程优先级*********************************************************/
重要性自左到右依次递减
前台进程--->可见进程--->服务进程--->后台进程---->空进程前台进程:
处于该状态下的进程表示其当前正在与用户交互
可见进程:
1.某个进程持有一个Activity且该Activty并非位于前台但仍能被用户所看到
2.某个进程持有一个Service并且这个Service和一个可见(或前台)的Activity绑定
服务进程:
某个进程中运行着一个Service且该Service是通过startService()启动也就是说没有与任何Activity绑定且并不属于上述的两种进程状态
后台进程:
当Activity隐藏到后台但未退出时,从代码的逻辑上来讲就是该Activity的onStop被调用但onDestory未被执行的状态
空进程:
某个进程不包含任何活跃的组件时该进程就会被置为空进程
/***********************************************进程回收策略*********************************************************/
红色部分代表比较容易被杀死的 Android 进程(OOM_ADJ>=4)
绿色部分表示不容易被杀死的 Android 进程
其他表示非 Android 进程(纯 Linux 进程)。
在 Lowmemorykiller 回收内存时会根据进程的级别优先杀死 OOM_ADJ 比较大的进程,对于优先级相同的进程则进一步受到进程所占内存和进程存活时间的影响。
总结:减少进程被杀死概率无非就是想办法提高进程优先级,减少进程在内存不足等情况下被杀死的概率
/********************************************************************************************************/后台常驻策略:
1.轻量化进程:尽量让我们的后台进程做更少的事情,及时释放资源
1.Service里处理更复杂的逻辑,务必尽量多地使用弱引用或软引用
2.尽量多地去置空一些不必要的引用并在需要的时候再赋值
3.Service本身也提供了onTrimMemory方法来告诉我们系统何时需要释放掉不必要的资源
2.被杀后重启:
1.多开一个进程,让这个进程轮询检查目标进程是否存活,死了的话将其拉起,同时目标进程也需要做一个轮询检查守护进程是否存活
2.通过AlarmManager和系统广播来在一定条件下唤醒逝去的进程,死了的话也将其拉起,相互唤醒
/*********************************************************************************************************/
后台进程常驻的实现:
1.进程提权(adj值越小的进程越不容易被杀死)(支付宝曾经以这样的方式让自己的后台进程常驻)
1.Service的一个功能startForeground,不过需要发送一个Notification
a.Android 4.3以前我们可以通过构造一个空的Notification,这时通知栏并不会显示我们发送的Notification
b.从4.3开始谷歌不再允许构造空的Notification,如果你想将应用置为前台那么请发送一个可见的Notification以告知用户你的应用进程依然在后台运行
实现:启动另一个Service同时也让其发送一个Notification使自己置为前台,并且这个Notification的标志值也跟上面的一样,然后再把它取消掉再停止掉这个Service的前台显示
实例:通过CancelService干掉了前台显示需要的Notification,但是,请大家查看一下当前进程的adj值,你就会发现,我们DaemonService所在的进程竟然还是可见进程
缺点:这个方法有个小小的bug,在一些手机上,发送前台通知会唤醒设备并点亮屏幕,这样会很耗电而且在电量管理界面系统还会统计到你的进程点亮屏幕的次数,不是很好
2.Activity提权
方案设计思想:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。
通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1。
适用场景:本方案主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(一般为5分钟以内)内会杀死后台进程,已达到省电的目的问题。
适用版本:适用于所有的 Android 版本。
1.Service重启(onStartCommand方法中返回不同的值可以告知系统让系统在Service因为资源吃紧被干掉后可以在资源不紧张时重启)
1.START_STICKY
2.START_STICKY_COMPATIBILITY
3.START_NOT_STICKY
4.START_REDELIVER_INTENT
注:一般情况下,作为一个后台常驻的Service,个人建议是尽量不要传递Intent进来,避免有时候逻辑不好处理;虽然Service默认情况下是可以被系统重启的,但是在某些情况or某些定制ROM上会因为各种原因而失效,因此我们不能单靠这个返回值来达到进程重启的目的。
2.进程守护(AB两个进程,A进程里面轮询检查B进程是否存活,没存活的话将其拉起,同样B进程里面轮询检查A进程是否存活,没存活的话也将其拉起,而我们的后台逻辑则随便放在某个进程里执行即可)
实现:(有些时候,我们会使用一个更为纯净的进程来作为守护进程而非借助Service,你可以使用C层fork,也可以直接Java创建一个新的进程,不依赖Android环境的进程唯一的好处是可以做到更轻量)
实例:使用Java的方式创建进程有两种途径,一是通过Runtime;二是通过ProcessBuilder,后者提供了更多的选择,因此爱哥一般都会选择后者,使用ProcessBuilder创建进程的过程也很简单,三部曲:构建环境变量、指定用户目录、执行命令
1.采用直接调用adb命令的方式启动Service组件(因为某些原因又可能会限制应用对adb的使用甚至不对应用提供,为了保险起见最好提供第二种以代码启动Service组件的方式)
注:每次我们在Service中轮询启动进程时有可能重复启动,所以在此之前我们还应该在启动之前做一次判断进程是否已经被启动
3.Receiver触发(静态注册一系列广播,所有处于STOPPED状态的应用都不可以接收到系统广播)
注:深度定制的ROM一旦我们的应用被重置为STOPPED则再也无法接受到相应的系统广播除非再次启动一下应用清除掉STOPPED标识
4.AlarmManager or JobScheduler循环触发(使用AlarmManage间隔一定的时间来检测并唤醒进程)
方案设计思想:Android5.0 以后系统对 Native 进程等加强了管理,Native 拉活方式失效。系统在 Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作。
实现:
1.在4.4以前的版本,我们只需通过AlarmManage的setRepeating方法即可达到目的
2.从4.4开始并不能精确地重复启动,也就是不能像setRepeating那样,setExact只能被唤醒一次;解决重复精确方法:每次通过AlarmManager唤醒时都发送一个广播,在这个广播里我们处理一些必要的逻辑,尔后又设置一次AlarmManager,如此往复循环,实质就是对广播做一个递归以达到目的
注:设置的Alarm在应用退出后发现过不了多久居然就没了,特别是在某些深度定制的系统上;如果应用被置为STOPPED状态就再也无法接收到广播
5.与系统Service捆绑
最BUG的NotificationListenerService(需要你手动在“设置-提示音和通知-通知使用权限”中打开),用户手动勾上,绝大部分手机都不会杀掉该进程,即便重启设备开机,它也会首先被启动
实现:只需新建一个Service并使其与NotificationListenerService在同一进程下,那么我们的这个Sefvice不就一样不死了
缺点:需要欺骗用户手动去开启通知权限,需要给用户一个合理的理由
6.so注入系统进程(这种方式去做进程常驻需要获得root权限,而且不容易控制,尝试了多种不同的方法去注入提权,都不稳定)
7.KFC外带全家桶(以全家桶的方式去相互唤醒相互拉活是目前来说最稳定最安全的方式)(目前来说最稳定最安全的方式,各大牛逼点的应用都有类似行为)
实现:通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。
方案适用范围:该方案的有效程度除与系统广播一样的因素外,主要受如下因素限制:
反编译分析过的第三方应用的多少
第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发。
8.利用账号同步机制拉活
方案设计思想:Android 系统的账号同步机制会定期同步账号进行,该方案目的在于利用同步机制进行进程的拉活方案适用范围:该方案适用于所有的 Android 版本,包括被 forestop 掉的进程也可以进行拉活。最新 Android 版本(Android N)中系统好像对账户同步这里做了变动,该方法不再有效。
9.Native进程拉活主要思想:利用 Linux 中的 fork 机制创建 Native 进程,在 Native 进程中监控主进程的存活,当主进程挂掉后,在 Native 进程中立即对主进程进行拉活。主要原理:在 Android 中所有进程和系统组件的生命周期受 ActivityManagerService 的统一管理。而且,通过 Linux 的 fork 机制创建的进程为纯 Linux 进程,其生命周期不受 Android 的管理。方案实现挑战:挑战一:在 Native 进程中如何感知主进程死亡。要在 Native 进程中感知主进程是否存活有两种实现方式:1.在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,档主进程不存活时进行拉活。该方案的很大缺点是不停的轮询执行判断逻辑,非常耗电2.在主进程中创建一个监控文件,并且在主进程中持有文件锁。在拉活进程启动后申请文件锁将会被堵塞,一旦可以成功获取到锁,说明主进程挂掉,即可进行拉活。由于 Android 中的应用都运行于虚拟机之上,Java 层的文件锁与 Linux 层的文件锁是不同的,要实现该功能需要封装 Linux 层的文件锁供上层调用。挑战二:在 Native 进程中如何拉活主进程通过 Native 进程拉活主进程的部分代码如下,即通过 am 命令进行拉活。通过指定“--include-stopped-packages”参数来拉活主进程处于 forestop 状态的情况。挑战三:如何保证 Native 进程的唯一。从可扩展性和进程唯一等多方面考虑,将 Native 进程设计层 C/S 结构模式,主进程与 Native 进程通过 Localsocket 进行通信。在Native进程中利用 Localsocket 保证 Native 进程的唯一性,不至于出现创建多个 Native 进程以及 Native 进程变成僵尸进程等问题方案适用范围:该方案主要适用于 Android5.0 以下版本手机。该方案不受 forcestop 影响,被强制停止的应用依然可以被拉活,在 Android5.0 以下版本拉活效果非常好。对于 Android5.0 以上手机,系统虽然会将native进程内的所有进程都杀死,这里其实就是系统“依次”杀死进程时间与拉活逻辑执行时间赛跑的问题,如果可以跑的比系统逻辑快,依然可以有效拉起。记得网上有人做过实验,该结论是成立的,在某些 Android 5.0 以上机型有效。
思维导图:
实现实例:
1.使用AlarmManager,双进程守护,MarsDaemon第三方库,AppWiget保活http://download.csdn.net/detail/nuobabijie730/9781813
2.使用Activity提权,notification提权,定时器,系统广播保活http://download.csdn.net/detail/nuobabijie730/9781823
文章参考:
【腾讯Bugly干货分享】Android 进程保活招式大全 https://segmentfault.com/a/1190000006251859