Android进程保活的前生今世

Android 保活的前生今世

目前,很少有在听过说保活,目前的Android 系统相比之前的 4.4 5.0 好的不是一星半点,保活这个话题,一直是Android争议的一个话题,谁都想自己的app 能够一直在后台跑着,从产品的角度,想法是好的,从用户的角度,这个是非常不合理的。没人愿意自己手机上有一些流氓软件一直常驻后台。这方便觉得ios 系统确实比较人性化,即使是一些想要常驻后台的app ,系统也在特定的条件下,提供了方法restore,

我知道的一个例子 ios 的蓝牙就是托管给系统,当app 被杀死以后,再次使用蓝牙,当有数据交互的时候,系统会吧app 拉起来 ,之前项目中ios 的同事和我聊过,让人非常羡慕的功能

系统为什么会杀死进程

可能有点废话,如果让app 一直存在后台 长期运行,用户手机电量,流量都消耗非常大,当系统内存紧张时候,还会导致app 卡顿等一系列问题。

系统如何杀死进程

Android 5.0 之前

App 内部通过 native 方式 fork 出来的进程是不受系统管控的,系统在杀 App 进程的时候,只会去杀 App 启动的 Java 进程。因此诞生了一大批“毒瘤”,他们通过 fork native 进程,在 App 的 Java 进程被杀死的时候通过 am命令拉起自己从而实现永生。

那时候的 Android 可谓是魑魅横行,群魔乱舞,系统根本管不住应用,因此长期以来被人诟病耗电、卡顿。

Android 5.0 以上

系统杀进程以 uid 为标识,通过杀死整个进程组来杀进程,因此 native 进程也躲不过系统的法眼

Android 6.0

引入了待机模式(doze),一旦用户拔下设备的电源插头,并在屏幕关闭后的一段时间内使其保持不活动状态,设备会进入低电耗模式,在该模式下设备会尝试让系统保持休眠状态。

Android 7.0

加强了之前鸡肋的待机模式(不再要求设备静止状态),同时对开启了 Project Svelte。Project Svelte 是专门用来优化 Android 系统后台的项目,在 Android 7.0 上直接移除了一些隐式广播,App 无法再通过监听这些广播拉起自己。

Android 8.0

进一步加强了应用后台执行限制:一旦应用进入已缓存状态时,如果没有活动的组件,系统将解除应用具有的所有唤醒锁。另外,系统会限制未在前台运行的应用的某些行为,比如说应用的后台服务的访问受到限制,也无法使用 Mainifest 注册大部分隐式广播。

Android 9.0

进一步改进了省电模式的功能并加入了应用待机分组,长时间不用的 App 会被打入冷宫。另外,系统监测到应用消耗过多资源时,系统会通知并询问用户是否需要限制该应用的后台活动。

保活的原理

通常我们认为的保活就主要就两个方面

  • 提高进程的优先级
  • 进程被杀死以后能够再次唤醒

进程的类别

提高进程的优先级,首先的知道Android 下主要被分为哪些进程

前台进程

用户目前执行操作所需的进程。在不同的情况下,进程可能会因为其所包含的各种应用组件而被视为前台进程。如果以下任一条件成立,则进程会被认为位于前台:

  • 它正在用户的互动屏幕上运行一个 Activity(其 onResume() 方法已被调用)。
  • 它有一个 BroadcastReceiver 目前正在运行(其 BroadcastReceiver.onReceive() 方法正在执行)。
  • 它有一个 Service 目前正在执行其某个回调(Service.onCreate()、Service.onStart() 或 Service.onDestroy())中的代码。

系统中只有少数此类进程,而且除非内存过低,导致连这些进程都无法继续运行,才会在最后一步终止这些进程。通常,此时设备已达到内存分页状态,因此必须执行此操作才能使用户界面保持响应。

可见进程

正在进行用户当前知晓的任务,因此终止该进程会对用户体验造成明显的负面影响。在以下条件下,进程将被视为可见:

  • 它正在运行的 Activity 在屏幕上对用户可见,但不在前台(其 onPause() 方法已被调用)。举例来说,如果前台 Activity 显示为一个对话框,而这个对话框允许在其后面看到上一个 Activity,则可能会出现这种情况。
  • 它有一个 Service 正在通过 Service.startForeground()(要求系统将该服务视为用户知晓或基本上对用户可见的服务)作为前台服务运行。
  • 系统正在使用其托管的服务实现用户知晓的特定功能,例如动态壁纸、输入法服务等。

相比前台进程,系统中运行的这些进程数量较不受限制,但仍相对受控。这些进程被认为非常重要,除非系统为了使所有前台进程保持运行而需要终止它们,否则不会这么做。

服务进程

包含一个已使用 startService() 方法启动的 Service。虽然用户无法直接看到这些进程,但它们通常正在执行用户关心的任务(例如后台网络数据上传或下载),因此系统会始终使此类进程保持运行,除非没有足够的内存来保留所有前台和可见进程。

已经运行了很长时间(例如 30 分钟或更长时间)的服务的重要性可能会降位,以使其进程降至下文所述的缓存 LRU 列表。这有助于避免超长时间运行的服务因内存泄露或其他问题占用大量内存,进而妨碍系统有效利用缓存进程。

缓存进程

包含多个Activity实例,但是都不可见(处于onStop()且已返回)。

系统如有内存需要,可随意杀死

进程的介绍可见 官方文档

进程回收策略

Android 中对于内存的回收,主要依靠 Lowmemorykiller 来完成,是一种根据 OOM_ADJ 阈值级别触发相应力度的内存回收的机制。

LowMemoryKiller是Android 系统在Linux kernel的OOMKiller基础上打的一个补丁。

OOMKiller在kernel 没法再分配内存的时候,寻找一个得分最高的进程来杀掉。LowMemoryKiller则提前一步,通过把剩余内存划分成不同的级别,内存在消耗的过程中,触发不同的级别,杀死相应的app进程。在触发OOMKiller前,大量缓存的app进程已经被杀死掉了

进程的信息主要有下面3个

OOM_SCORE 是什么

该进程的最终得分,分数越高,越容易被杀死

1
2
$ adb shell cat /proc/5947/oom_score    
124

OOM_SCORE_ADJ 是什么

是kernel用来配置进程优先级的。值越低,最终的oom_score越低。

范围:[-1000, 1000]

1
2
$ adb shell cat /proc/5947/oom_score_adj 
100

OOM_ADJ 是什么

范围:[-16, 15],是给用户来配置进程优先级的。为了方便用户配置,提供了范围更小的oom_adj参数,数字越小优先级越高,-17表示该进程被保护,不被OOMKiller杀死

OOM_ADJ 可与 OOM_SCORE_ADJ 相互转换

如何查看自己app 的级别

1
2
3
# jhy @ allens-MBP in ~/Documents/project/android/Starot/starot_unity_game on git:master x [20:56:21] C:1
$ adb shell ps|grep com.allens.unitygame
u0_a285 5947 977 1228344 130056 0 0 S com.allens.unitygame

参数含义

参数含义
USER进程当前用户
PID进程ID
PPID进程的父进程ID
VSZ进程的虚拟内存大小
RSS实际驻留”在内存中”的内存大小
WCHAN休眠进程在内核中的地址
NAME进程名

输入 adb shell cat /proc/进程uid/oom_adj可以查看当前的进程级别

1
2
3
# jhy @ allens-MBP in ~/Documents/project/android/Starot/starot_unity_game on git:master x [19:50:17] 
$ adb shell cat /proc/5947/oom_adj
0

进程级别说明

图片来着网络

曾经的保活方案有哪些

几年前也专门写过如果保活的文章

Android - 保活(1)前台服务保活
Android - 保活(2)一像素保活
Android - 保活(3)无声音乐保活

如何在如今的开发环境下实现保活?

针对这个问题,以个人开发者的角度,我角色现在的Android开发不在像几年以前那么乱,一方面是Google对Android权限的管理越来越严格,另一方面,时代也在进步,人们也在追求更好更高的用户体验,

如果现在还有保活的需求 可以尝试下面的方法

Android 后台运行白名单,优雅实现保活

参考

Android进程永生技术终极揭秘:进程被杀底层原理、APP应对技巧
Android 黑科技保活实现原理揭秘
Android Low Memory Killer 机制