本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
前言
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,我们也能够检查一下自己的项目中有没有这种写法,有的话要尽量改掉噢!不然线上说不定还真的呈现这种异常的逻辑状况,好啦本篇到此结束,感谢阅读!!