android:越来越难实现的进程保活

目录

  • 一、简介
  • 二、进程被杀死的原因
    • 1.Android手机的进程回收策略
    • 2.killBackgroundProcesses杀死进程
    • 3.force-stop或kill杀死进程
  • 三、进程保活及分析
    • Ⅰ.提升进程优先级的技术手段
      • 1.像素悬浮层
      • 2.将Service设置为前台服务
      • 3.定制化
    • Ⅱ.进程死后,拉活进程
      • 1.在service的onstart方法里返回 START_STICK
      • 2.添加Manifest文件属性值为android:persistent=“true”
      • 3.覆写Service的onDestroy方法
      • 4.监听一堆系统静态广播
      • 5.监听第三方应用的静态广播
      • 6.AlarmManager唤醒
      • 7.账户同步,定时唤醒
      • 8.双服务守护
      • 9.多APP间互相拉起
      • 10.Native进程拉起
      • 11.双进程守护
      • 12.JobSchedule机制拉活

一、简介

android中进程保活应用场景还是比较多的,音乐播放、视频缓存等等。最近比较火的健康运动类app(说的就是我们)需要后台获取定位信息以及其他操作也需要进程保活。进程保活在5.0版本之前还是比较好做的,基本上都是加一个守护进程,主进程死了拉起来就可以了。但是随着android的逐渐完善进程保活越来越不好实现了。下面是道长看到的一个博客,比较全面的分析了进程保活的手段。

二、进程被杀死的原因

Android进程被杀死的场景如下:
android:越来越难实现的进程保活_第1张图片

从上面的进程被杀死的场景分析,被杀死的原因如下:

1.Android手机的进程回收策略

Android的内存回收主要靠LowMemoryKiller 完成,Low Memorry Killer的机制主要是通过进程的oom_adj和oom_score来进行内存的处理的,关于 OOM_ADJ的说明如下:
android:越来越难实现的进程保活_第2张图片

  • 每一个进程都有一个oom_adj值,取值范围[-17,15]。
  • 每一个进程都有一个oom_score值,它是根据oom_adj计算出一个值,分数越大越容易被杀死。
  • 内存紧张时,LMK基于oom_adj和oom_score值来决定是否要回收一个进程。
  • oom_adj值越小,越不容易被杀死。
    当 oom_adj 的值大于等于4时是比较容易被杀死的 Android进程,0-3表示不容易被杀死的Android进程,小于0的为 非Android 进程(纯 Linux 进程)尤其是-17的 native 进程不受系统管理不会被系统杀死
  • 查看oom_adj和oom_score方法:

cat proc/pid/oom_adj
cat proc/pid/oom_score

所以结合Android 的进程回收机制,若是想要不被杀死或者减少被杀死的可能性,就需要提升进程优先级,降低在内存不足被系统回收的可能性。

2.killBackgroundProcesses杀死进程

ActivityManager的killBackgroundProcesses方法,可以立即杀死与指定包相关联的所有后台进程,这与内核杀死那些进程回收内存是一样的,但这些进程如果在将来某一时刻需要使用,会重新启动。该方法需要权限Android.permission.KILL_BACKGROUND_PROCESSES。源码解释如下:
android:越来越难实现的进程保活_第3张图片

3.force-stop或kill杀死进程

force-stop或kill杀死进程在5.0以后做了优化,可以从源码进行分析。5.0以下源码如下:
android:越来越难实现的进程保活_第4张图片

通过 pid 杀死进程,因此通过主进程 fork 出来的 c 进程是不会被杀死的,但是在5.0及以上源码发现,通过主进程 fork 出来的子进程也会被杀死了,5.0以上源码如下:
android:越来越难实现的进程保活_第5张图片

通过源码可以看到通过 uid 杀死进程,因此 fork 出来的子进程也同样会被杀死。
分析了以上被杀死的场景分析,我们得出两种技术方案:

  • Ⅰ. 提升进程优先级(降低被杀死的概率)
  • Ⅱ.进程杀死后,拉活进程

三、进程保活及分析

Ⅰ.提升进程优先级的技术手段

Android 系统将尽量长时间地保持应用进程,但是手机的内存有限,不可能无限的创建进程。最终需要清除旧进程来回收内存。为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统首先清除重要性最低的进程,然后清除重要性稍低一级的进程,依此类推。
以进程的重要性划分5级:前台进程(Foreground process)、可见进程(Visible process)、服务进程(Service process)、后台进程(Background process)、空进程(Empty process)

  • 前台进程
    用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:

1.托管用户正在交互的Activity(已调用Activity的onResume()方法)
2.托管某个Service,后者绑定到用户正在交互的 Activity
3.托管正在“前台”运行的Service(服务已调用startForeground())
4.托管正执行一个生命周期回调的Service(onCreate()、onStart()或onDestroy())
5.托管正执行其onReceive()方法的BroadcastReceiver

通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

  • 可见进程
    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:

1.托管不在前台、但仍对用户可见的Activity(已调用其onPause()方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
2.托管绑定到可见(或前台)Activity 的Service。

可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

  • 服务进程
    正在运行已使用startService()方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

  • 后台进程
    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的onStop()方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅Activity文档。

  • 空进程
    不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。
前台进程的重要性最高,依次递减,空进程的重要性最低,下面分别来阐述每种级别的进程,进程的回收策略如上,进程的介绍可详见:
http://www.jianshu.com/p/8a95f1f82ede?utm_source=desktop&utm_medium=timeline

1.像素悬浮层

监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。注意该 Activity 需设计成用户无感知。通过该方案,可以使进程的优先级在屏幕锁屏时间由4提升为最高优先级1,主要解决第三方应用及系统管理工具在检测到锁屏事件后一段时间(一般为5分钟以内)内会杀死后台进程,已达到省电的目的问题
实现方案:
首先定义 Activity,并设置 Activity 的大小为1像素:
android:越来越难实现的进程保活_第6张图片
其次,从 AndroidManifest 中通过如下属性,排除 Activity 在 RecentTask 中的显示:
android:越来越难实现的进程保活_第7张图片
最后,控制 Activity 为透明:
android:越来越难实现的进程保活_第8张图片
Activity 启动与销毁时机的控制:
android:越来越难实现的进程保活_第9张图片

2.将Service设置为前台服务

Android 中 Service 的优先级为4,通过 setForeground 接口可以将后台 Service 设置为前台 Service,使进程的优先级由4提升为2,从而使进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。
从 Android2.3 开始调用 setForeground 将后台 Service 设置为前台 Service 时,必须在系统的通知栏发送一条通知,也就是前台 Service 与一条可见的通知时绑定在一起的。对于不需要常驻通知栏的应用来说,该方案虽好,但却是用户感知的,无法直接使用。
通过实现一个内部 Service,在 KeepLiveService 和其内部 Service 中同时发送具有相同 ID 的 Notification,然后将内部 Service 结束掉。随着内部 Service 的结束,Notification 将会消失,但系统优先级依然保持为2。

具体实现方案:
android:越来越难实现的进程保活_第10张图片

android:越来越难实现的进程保活_第11张图片

3.定制化

向手机厂商申请定制,把app的进程加入系统白名单。当然这个方法尽管可以达到最优效果,但是并不是每一个app能够办到。

Ⅱ.进程死后,拉活进程

1.在service的onstart方法里返回 START_STICK

主要的几个返回值:

1.START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。
2.START_STICKY:系统就会重新创建这个服务并且调用onStartCommand()方法,但是它不会重新传递最后的Intent对象,这适用于不执行命令的媒体播放器(或类似的服务),它只是无限期的运行着并等待工作的到来。
3.START_NOT_STICKY:直到接受到新的Intent对象,才会被重新创建。这是最安全的,用来避免在不需要的时候运行你的服务。
4.START_REDELIVER_INTENT:系统就会重新创建了这个服务,并且用最后的Intent对象调。等待中的Intent对象会依次被发送。这适用于如下载文件。

具体实现方案:
将 Service 设置为 START_STICKY,利用系统机制在 Service 挂掉后自动拉活:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        return START_STICKY;//异常结束自启动
    }

如下两种情况无法拉活:

  1. Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起。

  2. 进程被取得 Root 权限的管理工具或系统工具通过 forestop 停止掉,无法重启。因此force close和一些清理软件很容易就清理掉!

2.添加Manifest文件属性值为android:persistent=“true”

app.persistent = true不仅仅标志着此apk不能轻易的被kill掉,亦或在被kill掉后能够自动restart,并且还涉及到了进程的优先级。将被设置为CORE_SERVER_ADJ,此值为-12,而核心进程init的值为-16。当前正在前台运行的进程的值为0。如果应用能设置这个属性,那就真的可以做到保活,因为他真的可以杀不死,像系统的keyguard进程,media进程,且这些进程的adj都是负数,代表了前台activity黑屏了他们也不会死。但是这个属性需要系统shareuid,然后编译不过,因为需要系统签名!
因此,要弄这个属性需获取两个关键的信息:

1.在apk的AndroidManifest.xml文件中设置android:persistent=true
2.此apk需要放入到system/app目录下,成为一个systemapp

最主要的是让程序成为系统程序,这个可以做到吗?如果你技术过硬是可以尝试下的!首先弄个自动root的apk,加壳放到程序中,然后程序运行的时候,自动运行自动root的apk,获取root权限,然后在native层中,使用命令的方式把程序移到system/app目录下,成为系统程序!

3.覆写Service的onDestroy方法

在设置里面的正在运行,注意是正在运行里面,点击关闭,会走onDestroy回调方法,你在这里可以把自己启动起来。注意是正常关闭的时候是会自己启动起来,可是使用第三方的清理软件360,root过的360,force close这些来搞,压根不会走到onDestory的方法

4.监听一堆系统静态广播

在发生特定系统事件时,系统会发出响应的广播,通过在 AndroidManifest 中“静态”注册对应的广播监听器,即可在发生响应事件时拉活。常用的用于拉活的广播事件包括:
android:越来越难实现的进程保活_第12张图片

但是有如下问题:

  1. 广播接收器被管理软件、系统软件通过“自启管理”等功能禁用的场景无法接收到广播,从而无法自启。

  2. 系统广播事件不可控,只能保证发生事件时拉活进程,但无法保证进程挂掉后立即拉活。

5.监听第三方应用的静态广播

与接收系统广播类似,不同的是该方案为接收第三方应用广播。通过反编译第三方应用,如:手机QQ、微信、支付宝、UC浏览器等找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。
有效程度除与系统广播一样的因素外, 第三方应用的广播属于应用私有,当前版本中有效的广播,在后续版本随时就可能被移除或被改为不外发。

6.AlarmManager唤醒

主要是实现也一个监听开机的广播,和一个周期性的闹钟,不过比较致命的是耗电量是很高的

7.账户同步,定时唤醒

android系统里有一个账户系统,系统定期唤醒账号更新服务,同步的事件间隔是有限制的,最短1分钟,利用同步机制进行进程的拉活。难点:需要手动设置账户,你如何骗你的用户给你手动设置账户完了之后不卸载你,必须联网
添加账号和设置同步周期的代码如下:
android:越来越难实现的进程保活_第13张图片

该方案需要在 AndroidManifest 中定义账号授权与同步服务。
android:越来越难实现的进程保活_第14张图片

理论上使用与所有 Android 系统,也能解决 force-stop 的拉活,但是前提是需要联网状态下

注:最新 Android 版本(Android N)中系统好像对账户同步这里做了变动,该方法不再有效。

8.双服务守护

这个是android里面一个特性,跨进程bind一个service之后,如果被bind的service挂掉,bind他的service会把他拉起来!可以考虑使用远程服务来实现,可是还是不能保活进程,可以被杀掉!

9.多APP间互相拉起

比较常见的就是家族 app 之间互相调起,你监听到我死了,我把你拉起,之间互相拉活对方

10.Native进程拉起

原理就是通过 JNI fork出一个 c 进程,c 进程监控主进程是否存活,主要通过管道和文件监控的方式实现监控,发现主进程死后,通过调起一个 service 将主进程拉活

具体的实现方案:
android:越来越难实现的进程保活_第15张图片

android:越来越难实现的进程保活_第16张图片
此方案存在两个问题:

  1. 如果直接使用 JNI fork 出一个子进程,这样会存在一个内存较大问题,也就是主版占用多大内存,native 进程也占用多大内存,因此需要去写一个独立的 c 进程来降低内存大小

  2. 上面讲到5.0以上的系统会将根据 uid 杀进程,因此 native 进程也会被杀死,因此在5.0以上此方案失效的,

11.双进程守护

顾名思义开启两个 native 进程进行保活,当一个 native 进程被杀死后,另一个 native 进行拉起,具体实现方案可参考:http://blog.csdn.net/marswin89/article/details/50916631

12.JobSchedule机制拉活

Android5.0 以后系统对 Native 进程等加强了管理,Native 拉活方式失效。系统在 Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作。

具体实现方案:
android:越来越难实现的进程保活_第17张图片

通过创建一个系统任务JobInfo,添加到 JobSchedule 中,通过系统调度任务来实现拉活,force-stop 也能实现拉活。但是存在很多兼容性问题,目前在华为6.0以上不生效,以及在7.0系统上不能实现,根据官方文档说是在 Dos 安全模式下,系统调度也会被杀死。

原文链接:https://www.jianshu.com/p/8330a73771ca

你可能感兴趣的:(android-优化)