从framework角度看app保活问题
问题背景
最近在群里看到群友在讨论app保活的问题,回想之前做应用(运动类)开发时也遇到过类似的需求,于是便又来了兴趣,果断加入其中,和群友展开了激烈的讨论
不少群友的想法和我当初的想法一样,这特么保活不是看系统的心情么,系统想让谁活谁才能活,作为app开发者,根本无能为力,可真的是这样的吗?
保活方案
首先,我整理了从古到今,app开发者所使用过的以及当前还在使用的保活方式,主要思路有两个:保活和复活
保活的方案有:
1像素惨案
后台无声音乐
前台service
心跳机制
socket长连接
无障碍服务
......
复活的方案有:
- 双进程守护(java层和native层)
JobScheduler
定时任务- 推送/相互唤醒
- ......
不难看出,app开发者为了能让自己的应用多存活一会儿,可谓是绞尽脑汁,但即使这样,随着Android系统升级,尤其是进入8.0之后,系统对应用的限制越来越高,传统的保活方式已经不生效,这让Android开发者手足无措,于是乎,出现了一种比较和谐的保活方式:
- 引导用户开启手机白名单
这也是目前绝大多数应用所采用的的方式,相对于传统黑科技而言,此方式显得不那么流氓,比较容易被用户所接受。
但跟微信这样的国民级应用比起来,保活效果还是差了一大截,那么微信是怎么实现保活的呢?或者回到我们开头的问题,应用的生死真的只能靠系统调度吗?开发者能否干预控制呢?
进程调度原则
解开这个疑问之前,我们需要了解一下Android系统进程调度原则,主要介绍framework中承载四大组件的进程是如何根据组件状态而动态调节自身状态的。进程有两个比较重要的状态值:
oom_adj,定义在
frameworks/base/services/core/java/com/android/server/am/ProcessList.java
当中procState,定义在
frameworks/base/core/java/android/app/ActivityManager.java
当中
OOM_ADJ
以Android10的源码为例,oom_adj划分为20级,取值范围[-10000,1001],Android6.0以前的取值范围是[-17,16]
oom_adj值越大,优先级越低
oom_adj<0的进程都是系统进程。
public final class ProcessList {static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;// The minimum time we allow between crashes, for us to consider this// application to be bad and stop and its services and reject broadcasts.static final int MIN_CRASH_INTERVAL = 60 * 1000;// OOM adjustments for processes in various states:// Uninitialized value for any major or minor adj fieldsstatic final int INVALID_ADJ = -10000;// Adjustment used in certain places where we don't know it yet.// (Generally this is something that is going to be cached, but we// don't know the exact value in the cached range to assign yet.)static final int UNKNOWN_ADJ = 1001;// This is a process only hosting activities that are not visible,// so it can be killed without any disruption.static final int CACHED_APP_MAX_ADJ = 999;static final int CACHED_APP_MIN_ADJ = 900;// This is the oom_adj level that we allow to die first. This cannot be equal to// CACHED_APP_MAX_ADJ unless processes are actively being assigned an oom_score_adj of// CACHED_APP_MAX_ADJ.static final int CACHED_APP_LMK_FIRST_ADJ = 950;// Number of levels we have available for different service connection group importance// levels.static final int CACHED_APP_IMPORTANCE_LEVELS = 5;// The B list of SERVICE_ADJ -- these are the old and decrepit// services that aren't as shiny and interesting as the ones in the A list.static final int SERVICE_B_ADJ = 800;// This is the process of the previous application that the user was in.// This process is kept above other things, because it is very common to// switch back to the previous app. This is important both for recent// task switch (toggling between the two top recent apps) as well as normal// UI flow such as clicking on a URI in the e-mail app to view in the browser,// and then pressing back to return to e-mail.static final int PREVIOUS_APP_ADJ = 700;// This is a process holding the home application -- we want to try// avoiding killing it, even if it would normally be in the background,// because the user interacts with it so much.static final int HOME_APP_ADJ = 600;// This is a process holding an application service -- killing it will not// have much of an impact as far as the user is concerned.static final int SERVICE_ADJ = 500;// This is a process with a heavy-weight application. It is in the// background, but we want to try to avoid killing it. Value set in// system/rootdir/init.rc on startup.static final int HEAVY_WEIGHT_APP_ADJ = 400;// This is a process currently hosting a backup operation. Killing it// is not entirely fatal but is generally a bad idea.static final int BACKUP_APP_ADJ = 300;// This is a process bound by the system (or other app) that's more important than services but// not so perceptible that it affects the user immediately if killed.static final int PERCEPTIBLE_LOW_APP_ADJ = 250;// This is a process only hosting components that are perceptible to the// user, and we really want to avoid killing them, but they are not// immediately visible. An example is background music playback.static final int PERCEPTIBLE_APP_ADJ = 200;// This is a process only hosting activities that are visible to the// user, so we'd prefer they don't disappear.static final int VISIBLE_APP_ADJ = 100;static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1;// This is a process that was recently TOP and moved to FGS. Continue to treat it almost// like a foreground app for a while.// @see TOP_TO_FGS_GRACE_PERIODstatic final int PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ = 50;// This is the process running the current foreground app. We'd really// rather not kill it!static final int FOREGROUND_APP_ADJ = 0;// This is a process that the system or a persistent process has bound to,// and indicated it is important.static final int PERSISTENT_SERVICE_ADJ = -700;// This is a system persistent process, such as telephony. Definitely// don't want to kill it, but doing so is not completely fatal.static final int PERSISTENT_PROC_ADJ = -800;// The system process runs at the default adjustment.static final int SYSTEM_ADJ = -900;// Special code for native processes that are not being managed by the system (so// don't have an oom adj assigned by the system).static final int NATIVE_ADJ = -1000;// Memory pages are 4K.static final int PAGE_SIZE = 4 * 1024;//省略部分代码
}
复制代码
ADJ级别 | 取值 | 说明(可参考源码注释) |
---|---|---|
INVALID_ADJ | -10000 | 未初始化adj字段时的默认值 |
UNKNOWN_ADJ | 1001 | 缓存进程,无法获取具体值 |
CACHED_APP_MAX_ADJ | 999 | 不可见activity进程的最大值 |
CACHED_APP_MIN_ADJ | 900 | 不可见activity进程的最小值 |
CACHED_APP_LMK_FIRST_ADJ | 950 | lowmemorykiller优先杀死的级别值 |
SERVICE_B_ADJ | 800 | 旧的service的 |
PREVIOUS_APP_ADJ | 700 | 上一个应用,常见于应用切换场景 |
HOME_APP_ADJ | 600 | home进程 |
SERVICE_ADJ | 500 | 创建了service的进程 |
HEAVY_WEIGHT_APP_ADJ | 400 | 后台的重量级进程,system/rootdir/init.rc文件中设置 |
BACKUP_APP_ADJ | 300 | 备份进程 |
PERCEPTIBLE_LOW_APP_ADJ | 250 | 受其他进程约束的进程 |
PERCEPTIBLE_APP_ADJ | 200 | 可感知组件的进程,比如背景音乐播放 |
VISIBLE_APP_ADJ | 100 | 可见进程 |
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ | 50 | 最近运行的后台进程 |
FOREGROUND_APP_ADJ | 0 | 前台进程,正在与用户交互 |
PERSISTENT_SERVICE_ADJ | -700 | 系统持久化进程已绑定的进程 |
PERSISTENT_PROC_ADJ | -800 | 系统持久化进程,比如telephony |
SYSTEM_ADJ | -900 | 系统进程 |
NATIVE_ADJ | -1000 | native进程,不受系统管理 |
可以通过cat /proc/进程id/oom_score_adj
查看目标进程的oom_adj值,例如我们查看电话的adj
值为935,处于不可见进程的范围内,当我启动电话app,再次查看
此时adj值为0,也就是正在与用户交互的进程
ProcessState
process_state划分为23类,取值范围为[-1,21]
@SystemService(Context.ACTIVITY_SERVICE)
public class ActivityManager {//省略部分代码/** @hide Not a real process state. */public static final int PROCESS_STATE_UNKNOWN = -1;/** @hide Process is a persistent system process. */public static final int PROCESS_STATE_PERSISTENT = 0;/** @hide Process is a persistent system process and is doing UI. */public static final int PROCESS_STATE_PERSISTENT_UI = 1;/** @hide Process is hosting the current top activities. Note that this covers* all activities that are visible to the user. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_TOP = 2;/** @hide Process is hosting a foreground service with location type. */public static final int PROCESS_STATE_FOREGROUND_SERVICE_LOCATION = 3;/** @hide Process is bound to a TOP app. This is ranked below SERVICE_LOCATION so that* it doesn't get the capability of location access while-in-use. */public static final int PROCESS_STATE_BOUND_TOP = 4;/** @hide Process is hosting a foreground service. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_FOREGROUND_SERVICE = 5;/** @hide Process is hosting a foreground service due to a system binding. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 6;/** @hide Process is important to the user, and something they are aware of. */public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 7;/** @hide Process is important to the user, but not something they are aware of. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 8;/** @hide Process is in the background transient so we will try to keep running. */public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 9;/** @hide Process is in the background running a backup/restore operation. */public static final int PROCESS_STATE_BACKUP = 10;/** @hide Process is in the background running a service. Unlike oom_adj, this level* is used for both the normal running in background state and the executing* operations state. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_SERVICE = 11;/** @hide Process is in the background running a receiver. Note that from the* perspective of oom_adj, receivers run at a higher foreground level, but for our* prioritization here that is not necessary and putting them below services means* many fewer changes in some process states as they receive broadcasts. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_RECEIVER = 12;/** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */public static final int PROCESS_STATE_TOP_SLEEPING = 13;/** @hide Process is in the background, but it can't restore its state so we want* to try to avoid killing it. */public static final int PROCESS_STATE_HEAVY_WEIGHT = 14;/** @hide Process is in the background but hosts the home activity. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_HOME = 15;/** @hide Process is in the background but hosts the last shown activity. */public static final int PROCESS_STATE_LAST_ACTIVITY = 16;/** @hide Process is being cached for later use and contains activities. */@UnsupportedAppUsagepublic static final int PROCESS_STATE_CACHED_ACTIVITY = 17;/** @hide Process is being cached for later use and is a client of another cached* process that contains activities. */public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 18;/** @hide Process is being cached for later use and has an activity that corresponds* to an existing recent task. */public static final int PROCESS_STATE_CACHED_RECENT = 19;/** @hide Process is being cached for later use and is empty. */public static final int PROCESS_STATE_CACHED_EMPTY = 20;/** @hide Process does not exist. */public static final int PROCESS_STATE_NONEXISTENT = 21;//省略部分代码
}
复制代码
state级别 | 取值 | 说明(可参考源码注释) |
---|---|---|
PROCESS_STATE_UNKNOWN | -1 | 不是真正的进程状态 |
PROCESS_STATE_PERSISTENT | 0 | 持久化的系统进程 |
PROCESS_STATE_PERSISTENT_UI | 1 | 持久化的系统进程,并且正在操作UI |
PROCESS_STATE_TOP | 2 | 处于栈顶Activity的进程 |
PROCESS_STATE_FOREGROUND_SERVICE_LOCATION | 3 | 运行前台位置服务的进程 |
PROCESS_STATE_BOUND_TOP | 4 | 绑定到top应用的进程 |
PROCESS_STATE_FOREGROUND_SERVICE | 5 | 运行前台服务的进程 |
PROCESS_STATE_BOUND_FOREGROUND_SERVICE | 6 | 绑定前台服务的进程 |
PROCESS_STATE_IMPORTANT_FOREGROUND | 7 | 对用户很重要的前台进程 |
PROCESS_STATE_IMPORTANT_BACKGROUND | 8 | 对用户很重要的后台进程 |
PROCESS_STATE_TRANSIENT_BACKGROUND | 9 | 临时处于后台运行的进程 |
PROCESS_STATE_BACKUP | 10 | 备份进程 |
PROCESS_STATE_SERVICE | 11 | 运行后台服务的进程 |
PROCESS_STATE_RECEIVER | 12 | 运动广播的后台进程 |
PROCESS_STATE_TOP_SLEEPING | 13 | 处于休眠状态的进程 |
PROCESS_STATE_HEAVY_WEIGHT | 14 | 后台进程,但不能恢复自身状态 |
PROCESS_STATE_HOME | 15 | 后台进程,在运行home activity |
PROCESS_STATE_LAST_ACTIVITY | 16 | 后台进程,在运行最后一次显示的activity |
PROCESS_STATE_CACHED_ACTIVITY | 17 | 缓存进程,包含activity |
PROCESS_STATE_CACHED_ACTIVITY_CLIENT | 18 | 缓存进程,且该进程是另一个包含activity进程的客户端 |
PROCESS_STATE_CACHED_RECENT | 19 | 缓存进程,且有一个activity是最近任务里的activity |
PROCESS_STATE_CACHED_EMPTY | 20 | 空的缓存进程,备用 |
PROCESS_STATE_NONEXISTENT | 21 | 不存在的进程 |
进程调度算法
在frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
中,有三个核心方法用于计算和更新进程的oom_adj值
updateOomAdjLocked()
:更新adj,当目标进程为空,或者被杀则返回false,否则返回true。computeOomAdjLocked()
:计算adj,计算成功返回true,否则返回false。applyOomAdjLocked()
:应用adj,当需要杀掉目标进程则返回false,否则返回true。
adj更新时机
也就是updateOomAdjLocked()
被调用的时机。通俗的说,只要四大组件被创建或者状态发生变化,或者当前进程绑定了其他进程,都会触发adj更新,具体可在源码中查看此方法被调用的地方,比较多,这里就不列举了
adj的计算过程
computeOomAdjLocked()
计算过程相当复杂,将近1000行代码,这里就不贴了,有兴趣可自行查看,总体思路就是根据当前进程的状态,设置对应的adj值,因为状态值很多,所以会有很多个if来判断每个状态是否符合,最终计算出当前进程属于哪种状态。
adj的应用
计算得出的adj值将发送给lowmemorykiller
(简称lmk),由lmk来决定进程的生死,不同的厂商,lmk的算法略有不同,下面是源码中对lmk的介绍
/* drivers/misc/lowmemorykiller.c** The lowmemorykiller driver lets user-space specify a set of memory thresholds* where processes with a range of oom_score_adj values will get killed. Specify* the minimum oom_score_adj values in* /sys/module/lowmemorykiller/parameters/adj and the number of free pages in* /sys/module/lowmemorykiller/parameters/minfree. Both files take a comma* separated list of numbers in ascending order.** For example, write "0,8" to /sys/module/lowmemorykiller/parameters/adj and* "1024,4096" to /sys/module/lowmemorykiller/parameters/minfree to kill* processes with a oom_score_adj value of 8 or higher when the free memory* drops below 4096 pages and kill processes with a oom_score_adj value of 0 or* higher when the free memory drops below 1024 pages.** The driver considers memory used for caches to be free, but if a large* percentage of the cached memory is locked this can be very inaccurate* and processes may not get killed until the normal oom killer is triggered.** Copyright (C) 2007-2008 Google, Inc.** This software is licensed under the terms of the GNU General Public* License version 2, as published by the Free Software Foundation, and* may be copied, distributed, and modified under those terms.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.**/
复制代码
保活核心思路
根据上面的Android进程调度原则得知,我们需要尽可能降低app进程的adj值,从而减少被lmk杀掉的可能性,而我们传统的保活方式最终目的也是降低adj值。而根据adj等级分类可以看出,通过应用层的方式最多能将adj降到100~200之间,我分别测试了微信、支付宝、酷狗音乐,启动后返回桌面并息屏,测试结果如下
微信测试结果:
微信创建了两个进程,查看这两个进程的adj值均为100,对应为adj等级表中的VISIBLE_APP_ADJ
,此结果为测试机上微信未登录状态测试结果,当换成我的小米8测试后发现,登录状态下的微信有三个进程在运行
后查阅资料得知,进程名为com.tencent.soter.soterserver
的进程是微信指纹支付,此进程的adj值居然为-800,上面我们说过,adj小于0的进程为系统进程,那么微信是如何做到创建一个系统进程的,我和我的小伙伴都惊呆了o.o,为此,我对比了一下支付宝的测试结果
支付宝测试结果:
支付宝创建了六个进程,查看这六个进程的adj值,除了一个为915,其余均为0,怎么肥事,0就意味着正在与用户交互的前台进程啊,我的世界要崩塌了,只有一种可能,支付宝通过未知的黑科技降低了adj值。
酷狗测试结果:
酷狗创建了两个进程,查看这两个进程的adj值分别为700、200,对应为adj等级表中的PREVIOUS_APP_ADJ
和PERCEPTIBLE_APP_ADJ
,还好,这个在意料之中。
测试思考
通过上面三个app的测试结果可以看出,微信和支付宝一定是使用了某种保活手段,让自身的adj降到最低,尤其是微信,居然可以创建系统进程,简直太逆天了,这是应用层绝对做不到的,一定是在native层完成的,但具体什么黑科技就不得而知了,毕竟反编译技术不是我的强项。
正当我郁郁寡欢之时,我想起了前两天看过的一篇文章《当 App 有了系统权限,真的可以为所欲为?》,文章讲述了第三方App如何利用CVE漏洞获取到系统权限,然后神不知鬼不觉的干一些匪夷所思的事儿,这让我茅塞顿开,或许这些大厂的app就是利用了系统漏洞来保活的,不然真的就说不通了,既然都能获取到系统权限了,那创建个系统进程不是分分钟的事儿吗,还需要啥厂商白名单。
总结
进程保活是一把双刃剑,增加app存活时间的同时牺牲的是用户手机的电量,内存,cpu等资源,甚至还有用户的忍耐度,作为开发者一定要合理取舍,不要为了保活而保活,即使需要保活,也尽量采用白色保活手段,别让用户手机变板砖,然后再来哭爹骂娘。
从framework角度看app保活问题相关推荐
- 从源码角度看CPU相关日志
简介 (本文原地址在我的博客CheapTalks, 欢迎大家来看看~) 安卓系统中,普通开发者常常遇到的是ANR(Application Not Responding)问题,即应用主线程没有相应.根本 ...
- 从主数据的角度看一个零售ERP系统
What is an ERP Retail System from a_ master Data Perspective 从主数据的角度看一个零售ERP系统 Retailers have lot of ...
- 从一个程序员的角度看——微信小应用
前言: 最近初步了解了一下微信小应用,APP端的同事也非常感兴趣,于是在公司内部做了一个小小的分享,分享的过程中有很多讨论内容,大家也是非常感兴趣和有自己的看法,当时"混乱"的场面 ...
- 从源码角度看Android系统Launcher在开机时的启动过程
Launcher是Android所有应用的入口,用来显示系统中已经安装的应用程序图标. Launcher本身也是一个App,一个提供桌面显示的App,但它与普通App有如下不同: Launcher是所 ...
- 从源码角度看Android系统SystemServer进程启动过程
SystemServer进程是由Zygote进程fork生成,进程名为system_server,主要用于创建系统服务. 备注:本文将结合Android8.0的源码看SystemServer进程的启动 ...
- 从源码角度看Android系统Zygote进程启动过程
在Android系统中,DVM.ART.应用程序进程和SystemServer进程都是由Zygote进程创建的,因此Zygote又称为"孵化器".它是通过fork的形式来创建应用程 ...
- 一步一步教你玩转.NET Framework的配置文件app.config (转载)
一步一步教你玩转.NET Framework的配置文件app.config 在一般的项目中,为了使你的代码更加灵活,更方便调整,减少不必要的hard code,我们都在config中添加许多配置信息, ...
- 《惢客创业日记》2019.03.18(周一)从创业角度看郭德纲(5)
今天,继续分享<从创业角度看郭德纲>最后一期,前面四期已经把所有的人物和故事都交待完了.这最后一期就一起来思考三个问题. 第一个问题是郭德纲为什么年纪轻轻就能掀起第三波相声潮流? 我们从两 ...
- 2021北大软微计算机考研感想——从另一角度看考研
2021北大软微考研感想--从另一角度看考研 其实这篇经验帖的主体内容是我在初试备考期间就已经写好了的,拟录取结果出来后做了一些必要的修改和整理,主要是纠结于什么该说.什么不该说,尽可能地消除一些主观 ...
最新文章
- django实现长链接
- 恢复删除的Linux文件
- HDU 5274 Dylans loves tree(树链剖分)
- python3.6运行界面_Electron+Python界面开发(通过zerorpc)
- 规则2 使用内容发布网络
- kibana管理界面日志级别查询_微服务海量日志怎么处理,我不允许你还不知道这款工具...
- 美国佛罗里达州立大学刘毅老师招收机器学习方向全奖博士生
- 瑞萨 报错 Section “.monitor2“ overlaps section “.textf“
- 讯时网站管理系统通杀0DAY漏洞
- 凸优化、轨迹优化、最优控制
- 基于MATLAB的语音去噪处理系统
- 几何画板演示空间几何体三视图的步骤
- 《C++ Concurrency in Action》笔记28 无锁并行数据结构
- 用python爬取公众号推送图片并保存为PPT
- oracle官网下载教程
- SSM------MyBatis(更新中)
- jQuery经典小案例
- solid works学习记录8(椭圆 椭圆弧)
- C语言-随机数的生成
- 单循环链表(简称循环链表)
热门文章
- 浅谈996工作制:蜜糖还是“毒药”?
- PS设计小白如何设计一张高品位高水准海报?
- mysql锁表与解锁
- 双路1080Ti主机ubuntu16.04.3+nivdia小白安装记录
- day02 邮箱测试用例/qq账号测试用例/城市电话号用例
- Briefings in Bioinformatics|南开大学药学院林建平教授|用于天然产物靶标预测的机器学习算法的大规模比较
- 一小时搞定c#微信支付
- 电子半导体万级洁净室的指标大全
- 基于默认路由及浮动路由的公司与分部互联
- 工信部:收了你的神通吧 安卓预装软件!