当前位置:网站首页>【解惑】App处于前台,Activity就不会被回收了?
【解惑】App处于前台,Activity就不会被回收了?
2022-07-07 15:40:00 【QXXXD】

转换后的理解:单进程场景,Activity被回收只可能是因为进程被系统回收了。
感觉不太对?因为在很久以前,遇到过这样一个场景:
App打开多个Activity,然后手机晾一边,过一段时间后(屏幕常亮),点击回退,之前的Activity空白,然后重新加载了。

App在前台,不在栈顶的Activity却被干掉,但进程还健在,如果真是这样,就和上面的理解有些出入了。
立马写个代码验证下,大概流程如下:
写个父类Activity,生命周期回调加日志打印,接着打开一个Activity,包含一个按钮,点击后依次打开多个Activity,最后一个加个按钮,点一下就申请一个大一点的ByteArray来模拟内存分配,看内存不足时是否会回收Activity。
测试结果如下:

App宁愿OOM,也不愿意回收Activity,鬼使神差地加上 android:largeHeap=“true” ,结果一样。
em…难道是我记错了???
等等!!!我好像混淆了两个东西:系统可用内存不足 和 应用可用内存不足。
0x1、系统可用内存不足
LMK机制
Android系统中,进程的生命周期由系统控制,处于体验和性能考虑,在APP中点击Home键或Back回退操作,并不会真的杀掉APP,进程依旧存在于内存中,这样下次启动此APP时就能更加快速。随着系统运行时间增长,打开APP越来越多,内存中的进程随着增多,系统的可用内存会越来越少。咋办,总不能让用户自己去杀进程吧,所以系统内置一套 回收机制,当系统可用内存达到一个 阈值,系统会根据 进程优先级 来杀掉一部分进程,释放内存供后续启动APP使用。
Android的这套回收机制,是基于Linux内核的OOM规则改进而来的,叫 Low Memory Killer,简称 LMK。
阈值 & 杀谁
通过下述两个文件配合完成,不同手机数值可能不同,以我的老爷机 魅蓝E2 为例 (Android 11的Mix2S一直说没权限打开此文件):
# /sys/module/lowmemorykiller/parameters/minfree
# 单位:Page页,1Page = 4KB
18432,23040,27648,46080,66560,97280
# /sys/module/lowmemorykiller/parameters/adj
0,58,117,176,529,1000
Android系统会为每个进程维护一个 adj(优先级):
- Android 6及以前称为:
oom_adj,值范围:[-17,16],LMK要换算*1000/17 - Android 7后称为:
oom_score_adj,值范围:[-1000,1000]
然后,上面两个文件的值,其实是以一一对应的,比如:
66560 * 4 / 1024 = 260MB → 当系统可用内存减少到260MB时,会杀掉adj值大于529的进程;
18432 * 4 / 1024 = 72MB → 当系统可用内存减少到72MB,杀掉ajd值大于0的进程;
adj怎么看
直接通过命令行查看:

可以看到,adj是动态变化的,当App状态及四大组件生命周期发生改变时,都会改变它的值。常见ADJ级别如下:
- NATIVE_ADJ → -1000,init进程fork出来的native进程,不受system管控;
- SYSTEM_ADJ → -900,system_server进程;
- PERSISTENT_PROC_ADJ → -800,系统persistent进程,一般不会被杀,杀了或者Carsh系统也会重新拉起;
- PERSISTENT_SERVICE_ADJ → -700,关联着系统或persistent进程;
- FOREGROUND_APP_ADJ → 0,前台进程;
- VISIBLE_APP_ADJ → 100,可见进程;
- PERCEPTIBLE_APP_ADJ → 200,可感知进程,比如后台音乐播放;
- BACKUP_APP_ADJ → 300,执行bindBackupAgent()过程的备份进程;
- HEAVY_WEIGHT_APP_ADJ → 400,重量级进程,system/rootdir/init.rc文件中设置;
- SERVICE_ADJ → 500,服务进程;
- HOME_APP_ADJ → 600,Home进程,类型为ACTIVITY_TYPE_HOME的应用,如Launcher;
- PREVIOUS_APP_ADJ → 700,用户上一个使用的App进程;
- SERVICE_B_ADJ → 800,B List中的Service;
- CACHED_APP_MIN_ADJ → 900,不可见进程 的adj最小值;
- CACHED_APP_MAX_ADJ → 906,不可见进程的adj最大值;
- UNKNOWN_ADJ → 1001,一般指将要会缓存进程,无法获取确定值;
关于ADJ计算的详细算法分析可见Gityuan大佬的:《解读Android进程优先级ADJ算法》,干货多多,顺带从总结处捞一波进程保活伎俩:
- UI进程与Service进程分离,包含Activity的Service进程,一进后台ADJ>=900,随时可能被系统回收,分离的话ADJ=500,被杀的可能性降低,尤其是系统允许自启动的服务进程,必须做UI分离,避免消耗较大内存;
- 真正需要用户可感知的应用,调用startForegroundService()启用前台服务,ADJ=200;
- 进程中的Service工作完,务必主动调用stopService或stopSelf来停止服务,避免占用内存,浪费系统资源;
- 不要长时间绑定其他进程的service或者provider,每次使用完成后应立刻释放,避免其他进程常驻于内存;
- APP应该实现接口onTrimMemory()和onLowMemory(),根据TrimLevel适当地将非必须内存在回调方法中加以释放,当系统内存紧张时会回调该接口,减少系统卡顿与杀进程频次;
- 更应在优化内存上下功夫,相同ADJ级别,系统会优先杀内存占用的进程;
问:能否把自己的App的ADJ值设置为-1000,让其杀不死? 答:不可以,要有root权限才能修改adj,而且改了重启手机还是恢复的。
扯得有点远了,回到问题上:
系统内存不足时,会在内核层直接查杀进程,不会在Framework层还跟你叨逼叨看回收哪个Activity。
所以在系统这个层面,单进程场景,Activity被回收只可能是因为进程被系统回收了,这句话是没毛病的,但在应用层面就不一定了。
0x2、应用可用内存不足
APP进程(虚拟机)的内存分配实际上是对 堆的分配和释放,为了整个系统的内存控制需要,会为每个应用程序设置一个 堆的限制阈值,如果应用使用内存接近阈值还尝试分配内存,就很容易引起OOM。

当然,不会那么蠢,还要开发仔自己在APP里回收内存,虚拟机自带 GC,这里就不向去卷具体的回收算法了
假设应用内存不足真的会回收Activity,那该怎么设计?一种解法如下:
应用启动时,开一个子线程,定时轮询当前可用内存是否超过阈值,超过的话干掉Activity
那就来跟下Android是不是也是这样设计的?
Activity回收机制
跟下应用启动入口:ActivityThread → main()

跟下 attach():

这里就非常像,run()中计算:已用内存 > 3/4最大内存,就执行 releaseSomeActivities(),跟下:

所以 getService() 是获取了 IActivityTaskManager.aidl接口,具体的实现类是 ActivityTaskManangerService:

继续往下跟: RootActivityContainer → releaseSomeActivitiesLocked():

跟下:WindowProcessController → getReleaseSomeActivitiesTasks()

然后再往下走就是释放Activity的代码了:ActivityStack → releaseSomeActivitiesLocked()

具体咋释放,就不往下跟了哈,接着跟下是怎么监控的~
内存监控机制
跟回:BinderInternal.addGcWatcher()

这里可能看得你有点迷,但是当你理解了就会觉得很妙了:
虚拟机GC会干掉 WeakReference 的对象,在释放内存前,会调用对象的 finalize(),而这里有创建了一个新的 WeakReference 实例。下次GC,又会走一遍这里的代码,啧啧啧,相比起轮询高效多了

到此,应用内存不足回收Activity的流程就大概缕清了,接着可以写个代码验证下是否真的这样。
Demo验证
先试下两个Task的:

模拟内存分配的页面,然后一直点~


宁愿OOM,也不回收,试试三个~


好家伙,onDestory()了,此时按Back回退这些页面,发现走了onCreate(),即回收了,接着试试四个的情况:


可以,每次只回收一个Task,到此验证完毕了~
0x3、结论
- 系统内存不足时,直接在内核层查杀(回收)进程,并不会考虑回收哪个Activity;
- 进程内存不足时,如果此进程 Activity Task数 >= 3 且 使用内存超过3/4,会对 不可见 Task进行回收,每次回收 1个 Task,回收时机为每次gc;
文末
我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。
需要的直接点击文末小卡片可以领取哦!我免费分享给你,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)
Android学习PDF+架构视频+面试文档+源码笔记
部分资料一览:
- 330页PDF Android学习核心笔记(内含8大板块)


- Android学习的系统对应视频

- Android进阶的系统对应学习资料

- Android BAT大厂面试题(有解析)

边栏推荐
- First in China! Todesk integrates RTC technology into remote desktop, with clearer image quality and smoother operation
- 第3章业务功能开发(安全退出)
- 【信息安全法律法規】複習篇
- LeetCode 515(C#)
- LeetCode 497(C#)
- DNS 系列(一):为什么更新了 DNS 记录不生效?
- VSCode关于C语言的3个配置文件
- Ratingbar的功能和用法
- Flash build API Service - generate API documents
- 麒麟信安操作系统衍生产品解决方案 | 存储多路径管理系统,有效提高数据传输可靠性
猜你喜欢
随机推荐
Problems encountered in Jenkins' release of H5 developed by uniapp
本周小贴士#140:常量:安全习语
Is AI more fair than people in the distribution of wealth? Research on multiplayer game from deepmind
First in China! Todesk integrates RTC technology into remote desktop, with clearer image quality and smoother operation
麒麟信安加入宁夏商用密码协会
Solidity函数学习
Solid function learning
PLC:自动纠正数据集噪声,来洗洗数据集吧 | ICLR 2021 Spotlight
Flask build API service SQL configuration file
Matplotlib绘制三维图形
【可信计算】第十三次课:TPM扩展授权与密钥管理
使用 xml资源文件定义菜单
Numberpick的功能和用法
Share the latest high-frequency Android interview questions, and take you to explore the Android event distribution mechanism
[Fantan] how to design a test platform?
责任链模式 - Unity
【TPM2.0原理及应用指南】 9、10、11章
第3章业务功能开发(安全退出)
【TPM2.0原理及应用指南】 12、13、14章
DNS series (I): why does the updated DNS record not take effect?









