目前市面上的应用,貌似除了微信和手Q都会比较担忧被用户或者系统(厂商)杀死问题。本文对 Android 进程拉活进行一个总结。html
Android 进程拉活包括两个层面:android
A. 提供进程优先级,下降进程被杀死的几率浏览器
B. 在进程被杀死后,进行拉活缓存
本文下面就从这两个方面作一下总结。微信
1. 进程的优先级网络
Android 系统将尽可能长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终须要清除旧进程来回收内存。 为了肯定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每一个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,而后是清除重要性稍低一级的进程,依此类推,以回收系统资源。socket
进程的重要性,划分5级:ide
前台进程的重要性最高,依次递减,空进程的重要性最低,下面分别来阐述每种级别的进程工具
1.1. 前台进程 —— Foreground processui
用户当前操做所必需的进程。一般在任意给定时间前台进程都为数很少。只有在内存不足以支持它们同时继续运行这一万不得已的状况下,系统才会终止它们。
A. 拥有用户正在交互的 Activity(已调用 onResume())
B. 拥有某个 Service,后者绑定到用户正在交互的 Activity
C. 拥有正在“前台”运行的 Service(服务已调用 startForeground())
D. 拥有正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
E. 拥有正执行其 onReceive() 方法的 BroadcastReceiver
1.2. 可见进程 —— Visible process
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持全部前台进程同时运行而必须终止,不然系统不会终止这些进程。
A. 拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
B. 拥有绑定到可见(或前台)Activity 的 Service
1.3. 服务进程 —— Service process
尽管服务进程与用户所见内容没有直接关联,可是它们一般在执行一些用户关心的操做(例如,在后台播放音乐或从网络下载数据)。所以,除非内存不足以维持全部前台进程和可见进程同时运行,不然系统会让服务进程保持运行状态。
A. 正在运行 startService() 方法启动的服务,且不属于上述两个更高类别进程的进程。
1.4. 后台进程 —— Background process
后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 一般会有不少后台进程在运行,所以它们会保存在 LRU 列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。若是某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,由于当用户导航回该 Activity 时,Activity 会恢复其全部可见状态。
A. 对用户不可见的 Activity 的进程(已调用 Activity的onStop() 方法)
1.5. 空进程 —— Empty process
保留这种进程的的惟一目的是用做缓存,以缩短下次在其中运行组件所需的启动时间。 为使整体系统资源在进程缓存和底层内核缓存之间保持平衡,系统每每会终止这些进程。
A. 不含任何活动应用组件的进程
详情参见:
http://developer.android.com/intl/zh-cn/guide/components/processes-and-threads.html
2. Android 进程回收策略
Android 中对于内存的回收,主要依靠 Lowmemorykiller 来完成,是一种根据 OOM_ADJ 阈值级别触发相应力度的内存回收的机制。
关于 OOM_ADJ 的说明以下:
其中红色部分表明比较容易被杀死的 Android 进程(OOM_ADJ>=4),绿色部分表示不容易被杀死的 Android 进程,其余表示非 Android 进程(纯 Linux 进程)。在 Lowmemorykiller 回收内存时会根据进程的级别优先杀死 OOM_ADJ 比较大的进程,对于优先级相同的进程则进一步受到进程所占内存和进程存活时间的影响。
Android 手机中进程被杀死可能有以下状况:
综上,能够得出减小进程被杀死几率无非就是想办法提升进程优先级,减小进程在内存不足等状况下被杀死的几率。
3. 提高进程优先级的方案
3.1. 利用 Activity 提高权限
3.1.1. 方案设计思想
监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。
经过该方案,可使进程的优先级在屏幕锁屏时间由4提高为最高优先级1。
3.1.2. 方案适用范围
适用场景: 本方案主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(通常为5分钟之内)内会杀死后台进程,已达到省电的目的问题。
适用版本: 适用于全部的 Android 版本。
3.1.3. 方案具体实现
首先定义 Activity,并设置 Activity 的大小为1像素:
其次,从 AndroidManifest 中经过以下属性,排除 Activity 在 RecentTask 中的显示:
最后,控制 Activity 为透明:
Activity 启动与销毁时机的控制:
3.2. 利用 Notification 提高权限
3.2.1. 方案设计思想
Android 中 Service 的优先级为4,经过 setForeground 接口能够将后台 Service 设置为前台 Service,使进程的优先级由4提高为2,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的几率大大下降。
3.2.2. 方案实现挑战
从 Android2.3 开始调用 setForeground 将后台 Service 设置为前台 Service 时,必须在系统的通知栏发送一条通知,也就是前台 Service 与一条可见的通知时绑定在一块儿的。
对于不须要常驻通知栏的应用来讲,该方案虽好,但倒是用户感知的,没法直接使用。
3.2.3. 方案挑战应对措施
经过实现一个内部 Service,在 LiveService 和其内部 Service 中同时发送具备相同 ID 的 Notification,而后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。
3.2.4. 方案适用范围
适用于目前已知全部版本。
3.2.5. 方案具体实现
4. 进程死后拉活的方案
4.1. 利用系统广播拉活
4.1.1. 方案设计思想
在发生特定系统事件时,系统会发出响应的广播,经过在 AndroidManifest 中“静态”注册对应的广播监听器,便可在发生响应事件时拉活。
经常使用的用于拉活的广播事件包括:
4.1.2. 方案适用范围
适用于所有 Android 平台。但存在以下几个缺点:
1) 广播接收器被管理软件、系统软件经过“自启管理”等功能禁用的场景没法接收到广播,从而没法自启。
2) 系统广播事件不可控,只能保证发生事件时拉活进程,但没法保证进程挂掉后当即拉活。
所以,该方案主要做为备用手段。
4.2. 利用第三方应用广播拉活
4.2.1. 方案设计思想
该方案总的设计思想与接收系统广播相似,不一样的是该方案为接收第三方 Top 应用广播。
经过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,以及友盟、信鸽、个推等 SDK,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将咱们的应用拉活。
4.2.2. 方案适用范围
该方案的有效程度除与系统广播同样的因素外,主要受以下因素限制:
1) 反编译分析过的第三方应用的多少
2) 第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改成不外发。
这些因素都影响了拉活的效果。
4.3. 利用系统Service机制拉活
4.3.1. 方案设计思想
将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活:
4.3.2. 方案适用范围
以下两种状况没法拉活:
4.4. 利用Native进程拉活
4.4.1. 方案设计思想
主要思想:利用 Linux 中的 fork 机制建立 Native 进程,在 Native 进程中监控主进程的存活,当主进程挂掉后,在 Native 进程中当即对主进程进行拉活。
主要原理:在 Android 中全部进程和系统组件的生命周期受 ActivityManagerService 的统一管理。并且,经过 Linux 的 fork 机制建立的进程为纯 Linux 进程,其生命周期不受 Android 的管理。
4.4.2. 方案实现挑战
挑战一:在 Native 进程中如何感知主进程死亡。
要在 Native 进程中感知主进程是否存活有两种实现方式:
封装 Linux 文件锁的代码以下:
Native 层中堵塞申请文件锁的部分代码:
挑战二:在 Native 进程中如何拉活主进程。
经过 Native 进程拉活主进程的部分代码以下,即经过 am 命令进行拉活。经过指定“—include-stopped-packages”参数来拉活主进程处于 forestop 状态的状况。
挑战三:如何保证 Native 进程的惟一。
从可扩展性和进程惟一等多方面考虑,将 Native 进程设计层 C/S 结构模式,主进程与 Native 进程经过 Localsocket 进行通讯。在Native进程中利用 Localsocket 保证 Native 进程的惟一性,不至于出现建立多个 Native 进程以及 Native 进程变成僵尸进程等问题。
4.4.3. 方案适用范围
该方案主要适用于 Android5.0 如下版本手机。
该方案不受 forcestop 影响,被强制中止的应用依然能够被拉活,在 Android5.0 如下版本拉活效果很是好。
对于 Android5.0 以上手机,系统虽然会将native进程内的全部进程都杀死,这里其实就是系统“依次”杀死进程时间与拉活逻辑执行时间赛跑的问题,若是能够跑的比系统逻辑快,依然能够有效拉起。记得网上有人作过实验,该结论是成立的,在某些 Android 5.0 以上机型有效。
4.5. 利用 JobScheduler 机制拉活
4.5.1. 方案设计思想
Android5.0 之后系统对 Native 进程等增强了管理,Native 拉活方式失效。系统在 Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操做。
在本项目中,我对 JobScheduler 进行了进一步封装,兼容 Android5.0 如下版本。封装后 JobScheduler 接口的使用以下:
4.5.2. 方案适用范围
该方案主要适用于 Android5.0 以上版本手机。
该方案在 Android5.0 以上版本中不受 forcestop 影响,被强制中止的应用依然能够被拉活,在 Android5.0 以上版本拉活效果很是好。
仅在小米手机可能会出现有时没法拉活的问题。
4.6. 利用帐号同步机制拉活
4.6.1. 方案设计思想
Android 系统的帐号同步机制会按期同步帐号进行,该方案目的在于利用同步机制进行进程的拉活。添加帐号和设置同步周期的代码以下:
该方案须要在 AndroidManifest 中定义帐号受权与同步服务。
4.6.2. 方案适用范围
该方案适用于全部的 Android 版本,包括被 forestop 掉的进程也能够进行拉活。
最新 Android 版本(Android N)中系统好像对帐户同步这里作了变更,该方法再也不有效。
5. 其余有效拉活方案
经研究发现还有其余一些系统拉活措施可使用,但在使用时须要用户受权,用户感知比较强烈。
这些方案包括:
这些方案须要结合具体产品特性来搞。
上面全部解释这些方案都是考虑的无 Root 的状况。
其余还有一些技术以外的措施,好比说应用内 Push 通道的选择: