避免踩坑,内存不足时系统回收Activity的流程解析
Posted bug樱樱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了避免踩坑,内存不足时系统回收Activity的流程解析相关的知识,希望对你有一定的参考价值。
前言
android开发中,activity我们会经常遇到,作为view的容器,activity天然就具备了生命周期的特点,当然这篇不是讲生命周期,而是关于系统不足时回收的动作,有可能导致app运行时会出现一些不可预料的“逻辑”异常行为。
以一个例子出发
在一些比较久的项目中,可能会存在这样一个事务处理架构,比如有个推送到来,同时eventbus发送给activity1进行部分逻辑处理,然后再把处理好的数据发送给其他Activity,比如例子中的Activity2,此时Activity2就处于可见状态。
可能有读者问为什么会有这么一个奇怪的架构,emmm,在笔者所经历的项目中,还真的有这样的处理,我们暂且抛开这个架构不谈,我们来思考一下,这个架构有什么不妥的地方!很明显,事件的处理依赖了Activity1这个中间环节,倘若Activity1被系统所回收了,那么整个消息处理环节就中断了!从而导致不可预期的逻辑出现。
Activity回收
那么问题来了,这个不可见的activity(这里activity1跟activity2属于同一个任务栈),有没有可能会被系统所回收,如果有可能会被回收,那么什么情况下才会出现,如果会出现,有没有手段可以避免?
我们带着这三个问题,去继续我们探索
ActivityThread的内存回收机制
我们都知道,Activity创建过程中,会通过ActivityThread进行各种初始化,其中我们特别关注一下attach函数(以master分支为例子,android13) cs.android.com/android/pla…
ActivityThread.java
private void attach(boolean system, long startSeq)
...
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable()
@Override public void run()
if (!mSomeActivitiesChanged)
return;
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
// 当内存大于3/4的时候,启动回收策略
if (dalvikUsed > ((3*dalvikMax)/4))
if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+ " total=" + (runtime.totalMemory()/1024)
+ " used=" + (dalvikUsed/1024));
mSomeActivitiesChanged = false;
try
// 释放逻辑
ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
catch (RemoteException e)
throw e.rethrowFromSystemServer();
);
通过上面源码我们可以看到,attach中通过BinderInternal.addGcWatcher进行了一个gc的监听,如果此时已用内存大于runtime.maxMemory()即当前进程最大可用内存的3/4的时候,就会进入一个释放逻辑,我们继续看ActivityTaskManager.getService().releaseSomeActivities中releaseSomeActivities函数的实现
@Override
public void releaseSomeActivities(IApplicationThread appInt)
synchronized (mGlobalLock)
final long origId = Binder.clearCallingIdentity();
try
// 真正的释放,通过WindowProcessController,原因是low-mem
final WindowProcessController app = getProcessController(appInt);
app.releaseSomeActivities("low-mem");
finally
Binder.restoreCallingIdentity(origId);
这个比较简单,就是直接包了一层,真正处理的是通过WindowProcessController的releaseSomeActivities方法,这个releaseSomeActivities非常重要,是我们上面三个问题的答案
void releaseSomeActivities(String reason)
// Examine all activities currently running in the process.
// Candidate activities that can be destroyed.
ArrayList<ActivityRecord> candidates = null;
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Trying to release some activities in " + this);
for (int i = 0; i < mActivities.size(); i++)
遍历所有的ActivityRecord
final ActivityRecord r = mActivities.get(i);
如果当前activity本来就处于finishing或者DESTROYING/DESTROYED状态,continue,即不加入activity的释放列表
if (r.finishing || r.isState(DESTROYING, DESTROYED))
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
return;
// 如果处于以下状态,则该activity也不会被回收
if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
|| r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING))
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
continue;
// 稍后我们会讲到,这里其实就是说明当前window是不是合法的window
if (r.getParent() != null)
if (candidates == null)
candidates = new ArrayList<>();
candidates.add(r);
// 上面所以要释放的activityRecord信息都存在了candidates中
if (candidates != null)
// Sort based on z-order in hierarchy.
candidates.sort(WindowContainer::compareTo);
// Release some older activities
int maxRelease = Math.max(candidates.size(), 1);
do
final ActivityRecord r = candidates.remove(0);
if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + r
+ " in state " + r.getState() + " for reason " + reason);
// 回收
r.destroyImmediately(reason);
--maxRelease;
while (maxRelease > 0);
我们一步步解释一下上面的关键方法,上面ArrayList candidates 就是一个即将被释放的ActivityRecord列表,那么ActivityRecord是什么呢?相关的解释已经有很多了,这里我们其实简单理解ActivityRecord其实 是Activity的标识,与每个Activity是一一对应,只不过在ActivityThread中我们操作的对象是ActviityRecord而不是Activity罢了,关系图可以参考以下
总之,candidates 就包含了系统即将回收的activity,这里就回答了我们第一个问题,activity是有可能被回收的
接着我们继续看
if (r.finishing || r.isState(DESTROYING, DESTROYED))
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
return;
如果当前的activity的finishing 为true 或者 当前状态处于DESTROYING, DESTROYED,那么这个activity就不会再被加入回收列表了,因为本来已经要被回收
接着,处于以下状态的ActivityRecord,也不会被回收
r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
这几个判断条件非常有意思
- mVisibleRequested 当前activity虽然处于onstop,但是已经被要求可见,比如后台播放activity,不过现在大部分不支持了,还有就是壁纸类应用,也可以设置mVisibleRequested == true
- stopped 处于非stopped状态,就是当前可见activity
- !r.hasSavedState(),这个并非只activity没有重载onSaveInstanceState,没有重载onSaveInstanceState也有可能回收,可看源码
boolean hasSavedState()
return mHaveState;
void setSavedState(@Nullable Bundle savedState)
mIcicle = savedState;
mHaveState = mIcicle != null;
setSavedState 中savedState为null的时候基本是activity已经被回收的情况,比如activity处于不在历史任务里面,此时savedState就为null(但是这种activity不可见时就会被回收,可以尝试一下)
- !r.isDestroyable isDestroyable == false的activity,处于前台可见时,就是isDestroyable == false
这里就回答了我们的第二个问题,回收条件是当已用内存超过3/4且activity不可见时,且不满足上诉条件的activity就会被加入回收列表中。
验证
到了验证环节,我们可以通过创建三个activity,如下
在可见的MyActivity3中,通过以下代码模拟内存分配
companion object
@JvmStatic
var list = ArrayList<ByteArray>()
val runtime = Runtime.getRuntime()
val byteArray = ByteArray(1024*10000)
list.add(byteArray)
Log.e("hello","$runtime.maxMemory() $runtime.totalMemory() $runtime.freeMemory()")
多次分配后,就能看到处于非任务栈顶的MyActivity1跟MyActivity2就被回收掉了
思考与拓展
那么我们有没有办法阻止系统这种回收行为呢?我们来思考一下问题3,有读者可能会想到,打破这几个判断条件之一就可以了r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable() 但是很遗憾的是,除了可见activity外,笔者暂时还没找到其他打破以上规则的方法,因为大部分都是系统应用才能做到(如果有黑科技的话,可望告知),当然,系统回收activity然后用到的时候帮我们再次创建,这也是一个非常合理的行为,所以如果不是非常特殊的情况,请不要干扰正常的内存回收行为。
总结
从一个小小的activity回收,我们能看到系统做了很多很多的内部处理,保证了app运行时内存的充足,同时回归本文一开始提到的架构问题,我们尽量不要采取这种方式去传递信息,相反的,如果需要中转处理,我们完全可以依赖一个静态的全局类去处理即可,而不是把处理依赖于具有生命周期的activity,大家也可以检查一下自己的项目中有没有这种写法,有的话要尽量改掉噢!否则线上说不定还真的出现这种异常的逻辑情况,好啦本篇到此结束,感谢阅读!!
作者:Pika
链接:https://juejin.cn/post/7162521119004557348
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓
以上是关于避免踩坑,内存不足时系统回收Activity的流程解析的主要内容,如果未能解决你的问题,请参考以下文章