Android可见APP的不可见任务栈(TaskRecord)销毁分析

如上图,在前台时,左边单栈APP跟进程生命周期绑定,多栈的,不可见栈TaskRecord1是有被干掉风险,TaskRecord2不会。下面简单分析下。
Android原生提供内存回收入口
Google应该也是想到了这种情况,源码自身就给APP自身回收内存留有入口,在每个进程启动的时候,回同步启动个微小的内存监测工具,入口是ActivityThread的attach函数,Android应用进程启动后,都会调用该函数:
ActivityThread
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
...
final IActivityManager mgr = ActivityManagerNative.getDefault();
...
// Watch for getting close to heap limit.
//关键点1,添加监测工具
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();
//关键点2 :如果已经可用的内存不足1/4着手处理杀死Activity,并且这个时候,没有缓存进程
if (dalvikUsed > ((3*dalvikMax)/4)) {
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
...
}

APP在GC节点的内存监测机制
之前说过,通过BinderInternal.addGcWatcher就添加了一个内存监测工具,原理是什么?其实很简单,就是利用了Java的finalize那一套:JVM垃圾回收器准备释放内存前,会先调用该对象finalize(如果有的话)
public class BinderInternal {
//关键点1 弱引用
static WeakReference<GcWatcher> sGcWatcher
= new WeakReference<GcWatcher>(new GcWatcher());
static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
static Runnable[] sTmpWatchers = new Runnable[1];
static long sLastGcTime;
static final class GcWatcher {
@Override
protected void finalize() throws Throwable {
handleGc();
sLastGcTime = SystemClock.uptimeMillis();
synchronized (sGcWatchers) {
sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
}
//关键点2 执行之前添加的回调
for (int i=0; i<sTmpWatchers.length; i++) {
if (sTmpWatchers[i] != null) {
sTmpWatchers[i].run();
}
}
//关键点3 下一次轮回
sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
}
}
public static void addGcWatcher(Runnable watcher) {
synchronized (sGcWatchers) {
sGcWatchers.add(watcher);
}
}
...
}
这里有几个关键点,关键点1是弱引用,GC的sGcWatcher引用的对象是要被回收的,这样回收前就会走关键点2,遍历执行之前通过BinderInternal.addGcWatcher添加的回调,执行完毕后,重新为sGcWatcher赋值新的弱引用,这样就会走下一个轮回,这就是为什么GC的时候,有机会触发releaseSomeActivities,其实,这里是个不错的内存监测点,用来扩展自身的需求。
AMS的TaskRecord栈释放机制
如果GC的时候,APP的Java内存使用超过了3/4,就会触发AMS的releaseSomeActivities,尝试回收界面,增加可用内存,但是并非所有场景都会真的销毁Activity,比如单栈的APP就不会销毁,多栈的也要分场景,可能选择性销毁不可见Activity。
ActivityManagerService
@Override
public void releaseSomeActivities(IApplicationThread appInt) {
synchronized(this) {
final long origId = Binder.clearCallingIdentity();
try {
ProcessRecord app = getRecordForAppLocked(appInt);
mStackSupervisor.releaseSomeActivitiesLocked(app, "low-mem");
} finally {
Binder.restoreCallingIdentity(origId);
}
}
} void releaseSomeActivitiesLocked(ProcessRecord app, String reason) {
TaskRecord firstTask = null;
ArraySet<TaskRecord> tasks = null;
for (int i = 0; i < app.activities.size(); i++) {
ActivityRecord r = app.activities.get(i);
//如果已经有一个进行,则不再继续
if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) {
return;
}
//过滤
if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING
|| r.state == PAUSED || r.state == STOPPING) {
continue;
}
if (r.task != null) {
if (firstTask == null) {
firstTask = r.task;
//关键点1 只要要多余一个TaskRecord才有机会走这一步
} else if (firstTask != r.task) {
if (tasks == null) {
tasks = new ArraySet<>();
tasks.add(firstTask);
}
tasks.add(r.task);
}
}
}
//注释很明显
if (tasks == null) {
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Didn't find two or more tasks to release");
return;
} // If we have activities in multiple tasks that are in a position to be destroyed,
// let's iterate through the tasks and release the oldest one.
final int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
// Step through all stacks starting from behind, to hit the oldest things first.
for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
final ActivityStack stack = stacks.get(stackNdx);
// Try to release activities in this stack; if we manage to, we are done.
if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
return;
}
}
}
}
这里先看第一个关键点1:如果想要tasks非空,则至少需要两个TaskRecord才行,不然,只有一个firstTask,永远无法满足firstTask != r.task这个条件,也无法走
tasks = new ArraySet<>();
也就是说,APP当前进程中,至少两个TaskRecord才有必要走Activity的销毁逻辑,注释说明很清楚:Didn't find two or more tasks to release,如果能找到超过两个会怎么样呢?
final int releaseSomeActivitiesLocked(ProcessRecord app, ArraySet<TaskRecord> tasks,
String reason) { //maxTasks 保证最多清理- tasks.size() / 4有效个,最少清理一个 同时最少保留一个前台TaskRecord
int maxTasks = tasks.size() / 4;
if (maxTasks < 1) {
//至少清理一个
maxTasks = 1;
}
int numReleased = 0;
for (int taskNdx = 0; taskNdx < mTaskHistory.size() && maxTasks > 0; taskNdx++) {
final TaskRecord task = mTaskHistory.get(taskNdx);
if (!tasks.contains(task)) {
continue;
}
int curNum = 0;
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
final ActivityRecord activity = activities.get(actNdx);
if (activity.app == app && activity.isDestroyable()) {
destroyActivityLocked(activity, true, reason);
if (activities.get(actNdx) != activity) {
actNdx--;
}
curNum++;
}
}
if (curNum > 0) {
numReleased += curNum;
maxTasks--;
if (mTaskHistory.get(taskNdx) != task) {
// The entire task got removed, back up so we don't miss the next one.
taskNdx--;
}
}
}
return numReleased;
}
ActivityStack利用maxTasks 保证,最多清理tasks.size() / 4,最少清理1个TaskRecord,同时,至少要保证保留一个前台可见TaskRecord,比如如果有两个TaskRecord,则清理先前的一个,保留前台显示的这个,如果三个,则还要看看最老的是否被有效清理,也就是是否有Activity被清理,如果有则只清理一个,保留两个,如果没有,则继续清理次老的,保留一个前台展示的,如果有四个,类似,如果有5个,则至少两个清理,这里的规则如果有兴趣,可自己简单看下。一般APP中,很少有超过两个TaskRecord的。
Android可见APP的不可见任务栈(TaskRecord)销毁分析的更多相关文章
- 【Flutter学习一】Android的App的三种开发方式
是时候学习新技术了: 转自:https://blog.csdn.net/qq_41346910/article/details/86692124 移动开发发展到现在,已经出现了三种开发方式.本文我将为 ...
- Cordova 打包 Android release app 过程详解
Cordova 打包 Android release app 过程详解 时间 -- :: SegmentFault 原文 https://segmentfault.com/a/119000000517 ...
- 【Android端 APP GPU过度绘制】GPU过度绘制及优化
一.Android端的卡顿 Android端APP在具体使用的过程中容易出现卡顿的情况,比如查看页面时出现一顿一顿的感受,切换tab之后响应很慢,或者具体滑动操作的时候也很慢. 二.卡顿的原因 卡顿的 ...
- 分享我开发的网络电话Android手机APP正式版,图文详解及下载
分享我开发的网络电话Android手机APP正式版,图文详解及下载 分享我开发的网络电话Android手机APP正式版 实时语音通讯,可广域网实时通讯,音质清晰流畅! 安装之后的运行效果: 第一次安装 ...
- Android开发App工程结构搭建
本文算是一篇漫谈,谈一谈关于android开发中工程初始化的时候如何在初期我们就能搭建一个好的架构. 关于android架构,因为手机的限制,目前我觉得也确实没什么大谈特谈的,但是从开发的角 ...
- Android 启动APP黑屏解决方案
#Android 启动APP黑屏解决方案# 1.自定义Theme //1.设置背景图Theme <style name="Theme.AppStartLoad" parent ...
- Android手机app启动的时候第一个Activity必须是MainActivity吗
原文:Android手机app启动的时候第一个Activity必须是MainActivity吗 Android手机APP启动的第一个Activity是可以自己设置的,不是必须的MainActivity ...
- Android原生APP内分享
Android原生APP内分享,可实现数据分享以及assets文件夹分享及私有文件分享 项目地址:https://github.com/json-pu/AndroidAppShare.git
- Android替换APP字体 — Typeface
Android替换APP字体 — Typeface APP字体的思路一般都会想到自定义控件(TextView.EditView),但是项目中会有很多种控件,就算用也会承担一些风险和资源的消耗,主要是这 ...
随机推荐
- 编写高质量代码改善C#程序的157个建议——建议146:只对外公布必要的操作
建议146:只对外公布必要的操作 那些没有必要公开的方法和属性要声明成private.如果需要公开的方法和属性超过9个,在VS默认的设置下,就需要滚屏才能显示在Intellisense中,如图: Sa ...
- 在java中对数据库进行增删改查
1.java连接MySql数据库 代码区域: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ...
- C#函数式程序设计之泛型(上)
在面向对象语言中,我们可以编写一个元素为某个专用类型(可能需要为此创建一个ListElement)的List类,或者使用一个非常通用.允许添加任何类型元素的基类(在.NET中,首先想到的是System ...
- VC6.0 多线程输出乱序问题
今天尝试编写多线程最简单的例子 #include "stdafx.h" #include "windows.h" #include <iostream&g ...
- mybatis--mapper配置总结
mapper介绍 mapper使用规则:按业务划分,一个业务模块相关的sql均定义在一个mapper文件 mapper的xml格式: doctype: <!DOCTYPE mapper PUBL ...
- /Date(1512551901709+0800)/转换
var convertDT=function(dt) { dt.replace(/Date\([\d+]+\)/, function (a) { eval('d = new ' + a) }); al ...
- ZKEACMS 自定义表单的使用
ZKEACMS Core 2.2 已经发布了,其中主要添加了自定义表单的功能.使用自定义表单的功能,您可以在几分钟内就创建一个表单,并用它来收集一些信息.导出收集的信息,就可以做一些统计分析. 创建表 ...
- 「ONTAK2010」 Peaks加强版
题目链接 戳我 \(Solution\) 首先来介绍一下kruskal重构树:详见 知道kruskal重构树后这一道题就可以几乎没了. 利用kruskal重构树的性质,一个节点的左右儿子都比他小(其实 ...
- 526. Beautiful Arrangement
Suppose you have N integers from 1 to N. We define a beautiful arrangement as an array that is const ...
- python字符串的切片
# 字符串的切片 """ (5)字符串的切片 :切片就是截取字符串的意思 (1)语法 =>字符串[::] 完整格式:[开始索引:结束索引:间隔值 (2)[:结束索引 ...