AndroidAndroid开发经验谈Android技术知识

Android 服务为啥保不活

2019-05-06  本文已影响5人  BmobSnail

转发请注明出处:https://www.jianshu.com/p/bf0c82be9b25
请尊重原创作者


有一段时间都在说服务怎么保活,怎么才能不被杀死,各种花里胡哨的保活方案抄来抄去,到头来 能打的 一个都没有。

硬核场景
既然这么多可操作性低的“保命方案”,那么需要知道些什么知识点才能脱颖而出,好吧我们提供不了保活的黑科技,如果对方按套路出牌的话,至少会让你解释下为什么保不活。

GC
就是因为GC机制,所以我们的服务才保不活,但是这个过程我们不可控,很难让运行中的应用感知什么时候GC触发了,从而做点什么来补救。所以才有了 轮询机制 来唤醒,一段时间检查下服务有没有在跑,没有就再扶起来继续送。

通常情况下,触发GC的条件有两个:
1.当应用程序空闲时,即没有应用线程在运行时,GC会被调用(随心随意收垃圾)
2.Java堆内存不足时,GC会被调用(被强制叫去收垃圾)

D/dalvikvm: GC_CONCURRENT freed 2012K, 63% free 3213K/9291K, external 4501K/5161K, paused 2ms+2ms

各参数对照含义
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>

相信大家都很熟悉这log,但对它表达的意义估计就看不太懂,为了方便解释,把各参数对照在上面列出,简单说个参数,其它大家再去详细了解GC的参数解读吧。

GC Reason 就是指引起GC原因,有以下几种:

  • GC_CONCURRENT:当堆开始填充时,并发GC可以释放内存。
  • GC_FOR_MALLOC:当堆内存已满时,app尝试分配内存而引起的GC,系统必须停止app并回收内存。
  • GC_HPROF_DUMP_HEAP:当你请求创建 HPROF 文件来分析堆内存时出现- 的GC。
  • GC_EXPLICIT:显示的GC,例如调用System.gc()(应该避免调用显示的GC,信任GC会在需要时运行)。
  • GC_EXTERNAL_ALLOC:仅适用于 API 级别小于等于10 ,用于外部分配内存的GC。

回到正题,GC为什么导致服务保不活?那是因为在当各种情况下触发了垃圾回收,它就会去找能宰的都宰掉,从而挤出内存给那些优先给用户接触到的应用或服务。所以才会有 Notification的前台服务 这样的做法让服务不轻易被杀死,尽可能把服务提升靠近用户,但是其核心思想都是为了改变 oom_adj 的等级,等级越低越重要越不容易被杀死。在解释改变等级之前,有个等级的概念需要先了解的。

进程等级
还是大家熟知的知识点,进程等级可以划分为这五大类:
1.前台进程
2.可见进程
3.服务进程
4.后台进程
5.空进程

他们在不同场景下,会对应到某一个值,具体查看方法可以用 adb 的命令查看。但这还只是等级概念,能大概地估算被杀死的可能性。而最终决定会不会被杀的是一个叫做 阀值 概念的值,这就涉及到 linux 的知识点,我们只需要知道 linux 会通过某个计算方式得出一个叫做 oom_score 的值,而这个就是和GC有直接关系了,因为根据不同情况,GC回收的程度不一样,那么每个等级都有对应阀值,只要没超过这个阀值的进程,就不会被回收。

比如说,这次GC只宰到 “后台进程” 的最低阀值,那么 “空进程”、“后台进程” 都会被宰,而 [服务进程]、[可见进程]、[前台进程] 这些进程安然无事,但某一时机这个阀值被系统调得更严格,需要更多内存了,调到 “服务进程” 的最低阀值,那么按照这个道理, “空进程”、“后台进程” 、“服务进程” 都会被宰掉。好,那么我们平时怎么知道自己的服务在什么等级呢?

oom_adj

adj级别 解释
UNKNOWN_ADJ 16 预留的最低级别,一般对于缓存的进程才有可能设置成这个级别
CACHED_APP_MAX_ADJ 15 缓存进程,空进程,在内存不足的情况下就会优先被kill
CACHED_APP_MIN_ADJ 9 缓存进程,也就是空进程
SERVICE_B_ADJ 8 不活跃的进程
PREVIOUS_APP_ADJ 7 切换进程
HOME_APP_ADJ 6 与Home交互的进程
SERVICE_ADJ 5 有Service的进程
HEAVY_WEIGHT_APP_ADJ 4 高权重进程
BACKUP_APP_ADJ 3 正在备份的进程
PERCEPTIBLE_APP_ADJ 2 可感知的进程,比如那种播放音乐
VISIBLE_APP_ADJ 1 可见进程
FOREGROUND_APP_ADJ 0 前台进程
PERSISTENT_SERVICE_ADJ -11 重要进程
PERSISTENT_PROC_ADJ -12 核心进程
SYSTEM_ADJ -16 系统进程
NATIVE_ADJ -17 系统起的Native进程

在终端输入命令(过程就大概是 先连接设备;进入shell;获得进程pid;查看值)
adb shell
cat /proc/{pid}/oom_adj


查看oom_adj的终端命令

如图所示了,某一个进程等级在 -13 那么对照到上面的表,可以知道起码是 系统进程 级别,那么普通的GC基本是宰不到它的。所以保活的核心,就是为了提升这个等级,类似的思想就有在AndroidManifest.xml的服务增加 优先级priority ,就尽量在这个阀值的范围往上靠,希望别宰到我。

不过
也不是没有办法保活,你需要知道一下这几个属性的作用,但有个前提咯!要提升到系统级别,唯一有效而且安全的做法,就是有 系统签名 。类似的,每个品牌每个主板每个型号都有会它的签名文件,就如平时我们自己生成的那种 jks、keystore 文件(只不过它们由 platform.x509.pem、platform.pk8 这些来生成),然后下面这些属性就能起作用

android:sharedUserId="android.uid.system"
这个属性使用应该会比较少,官方对它的解释:与其他应用程序共享的Linux用户ID的名称。默认情况下,Android为每个应用程序分配了自己唯一的用户ID。但是,如果将该属性设置为两个或多个应用程序相同的值,它们将共享相同的ID——前提是它们的证书集相同。具有相同用户ID的应用程序可以访问彼此的数据,如果需要,还可以在相同的进程中运行。

换言之,你得有系统签名才能和他们平起平坐。

persistent=true
这个在保活文章出现的频率就比较高了,但是却不怎么起作用,再来看看官方解释:应用程序是否应该一直运行—如果应该,则为“true”;如果不应该,则为“false”。默认值为“false”。应用程序通常不应设置此标志;持久性模式仅适用于某些系统应用程序。

通常情况下,我们的包都会安装到 data/data/ 目录下,而 persistent 需要 system/app 配套使用才会起效果的,也就是说应用本身就已经是系统应用,那么这个属性才生效,才会在系统启动的时候拉起有该属性的应用,并且被杀死后能够重启应用。

那我们怎么验证自己的属性有没有生效呢?
adb shell dumpsys meminfo


专业素养,打码了

看一下自己的应用包名有没有出现在 Persistent 列表就行了,按照网上那些保活方式,通常只会出现在 A Services 、Visible、Foreground 这些列表内。

总结
大家对服务为啥保不活,有了个整体概念,那么概括成一句话应该就是:GC可能会杀死等级 oom_adj 不太重要的服务,而我们所做的一切都是为了提升这个等级的值,让它不那么轻易被杀死。

上一篇下一篇

猜你喜欢

热点阅读