【解惑】App处于前台,Activity就不会被回收了?

54d38f0b2e1a0a622b8a94ea33a884a1.jpeg

/   今日快讯   /

昨晚2022年卡塔尔世界杯决赛,阿根廷对阵法国。两队在90分钟常规比赛时间里2-2战平。加时赛中梅西和姆巴佩各入一球,3-3,比赛拖进了点球大战。点球大战中阿根廷最终总比分7-5战胜法国,时隔36年再次夺得世界杯冠军,这也是阿根廷队史第3次在世界杯夺冠。

/   作者简介   /

大家周一好,再防寒的同时也别忘了防“羊”哦!

本篇文章转自coder_pig的博客,文章主要分享了作者探究Activity存活机制的相关过程,相信会对大家有所帮助!

原文地址:

https://juejin.cn/post/7063068797304832030

/   前言   /

昨天在康 KunMinX 大佬的:《重学安卓:Activity 生命周期的 3 个辟谣》,原文地址如下:

https://xiaozhuanlan.com/topic/0213584967

在加餐处看到这段:

105507f678c54da6b8dba254fd292e0e.png

转换后的理解:单进程场景,Activity被回收只可能是因为进程被系统回收了。

感觉不太对?因为在很久以前,遇到过这样一个场景:

App打开多个Activity,然后手机晾一边,过一段时间后(屏幕常亮),点击回退,之前的Activity空白,然后重新加载了。

6da96efef22c96edcc3f091dcf733a2d.gif

App在前台,不在栈顶的Activity却被干掉,但进程还健在,如果真是这样,就和上面的理解有些出入了。立马写个代码验证下,大概流程如下:

写个父类Activity,生命周期回调加日志打印,接着打开一个Activity,包含一个按钮,点击后依次打开多个Activity,最后一个加个按钮,点一下就申请一个大一点的ByteArray来模拟内存分配,看内存不足时是否会回收Activity。

测试结果如下:

6cf815866130f757709b335828ac6cb3.png

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怎么看

直接通过命令行查看:

b9b760e3b988213427947df4ea9fc367.png

可以看到,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大佬的:

http://gityuan.com/2018/05/19/android-process-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。

1ad22bff2fd5d43c55620eaa2c5df360.png

当然,不会那么蠢,还要开发仔自己在APP里回收内存,虚拟机自带GC,这里就不去卷具体的回收算法了~

假设应用内存不足真的会回收Activity,那该怎么设计?一种解法如下:

应用启动时,开一个子线程,定时轮询当前可用内存是否超过阈值,超过的话干掉Activity。

那就来跟下Android是不是也是这样设计的?

Activity回收机制

跟下应用启动入口:ActivityThread → main()

8ca682bb51e55ab626ff8ceb6851afbd.png

跟下attach():

49ac7919c86b62d5ad37cbaa241f3871.png

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

f757b98278626215503838b67165ffdc.png

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

84e92a38a0176dc10d7b1d1129a7d8f7.png

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

8c866af82306bc1f4291ffb9bd62827c.png

跟下:WindowProcessController→getReleaseSomeActivitiesTasks()。

9de8d6b1a79e00fccd4da78b92b164ab.png

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

492af1554e0ea807946d166616cd594e.png

具体咋释放,就不往下跟了哈,接着跟下是怎么监控的~

内存监控机制

跟回:BinderInternal.addGcWatcher()

9c286dd380bf649c2fa0e6d65a3927ea.png

这里可能看得你有点迷,但是当你理解了就会觉得很妙了:

虚拟机GC会干掉WeakReference的对象,在释放内存前,会调用对象的finalize(),而这里有创建了一个新的WeakReference实例。下次GC,又会走一遍这里的代码,啧啧啧,相比起轮询高效过了~

41af45e736b9e815119eb9d430af6457.png

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

Demo验证

先试下两个Task的:

3d0d5192ef44a50a8b7d3ded868ce3a3.png

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

6ed1a410bfa70dd249d0e9205721aa4d.png

ac4555ba6e87a0bada63b2a24c2d93a7.png

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

7f0692a7435eb67e45ef2e1e577bd174.png

f10f60388007b7c3ea57b51b7a1639a0.png

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

ab20e93a67aa9917ff34e17354996c66.png

fd6eea817ba2a8819e3029b7ada83d53.png

可以,每次只回收一个Task,到此验证完毕了~

/   0x3、结论   /

  • 系统内存不足时,直接在内核层查杀(回收)进程,并不会考虑回收哪个Activity;

  • 进程内存不足时,如果此进程Activity Task数 >= 3 且 使用内存超过3/4,会对不可见Task进行回收,每次回收1个Task,回收时机为每次gc;

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

Android 13 Developer Preview一览

“一文读懂”系列:无处不在的WMS

欢迎关注我的公众号

学习技术或投稿

c0860a5ff2340bf12688985e68c75216.png

981166001a45ea1b62534a18dc73700c.jpeg

长按上图,识别图中二维码即可关注

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值