22、onNewIntent 与 Flag:AMS如何通过Intent重用Activity实例
咱们今天聊一个挺有意思的话题——Activity重用。说白了,就是同一个Activity实例,能不能被多次使用?
我记得刚做Android那会儿,有个需求:从通知栏点击进入某个页面,如果这个页面已经打开了,就不要再新建一个,直接跳转到已有的那个。我当时想当然地用了标准启动模式,结果每次点击通知都新建一个Activity,用户返回的时候要按好几次back才能退出。嗯,这就是没搞懂Activity重用机制。
今天我们就来深入剖析一下,AMS到底是怎么通过Intent的Flag来控制Activity实例重用的。
22.1 为什么要重用Activity实例?
你想想看,每次启动Activity都新建一个实例,会有什么问题?
- 内存浪费:同样的页面,堆里存了好几份
- 用户体验差:返回栈里一堆重复页面
- 状态丢失:用户填了一半的表单,新实例进来啥都没了
所以,Android提供了几种机制来复用Activity实例。最核心的就是通过Intent的Flag来控制。
22.2 核心Flag:FLAG_ACTIVITY_SINGLE_TOP
这个Flag的意思是:如果目标Activity已经在栈顶了,那就别新建了,直接复用栈顶的那个。
我项目中遇到过这样一个场景:用户点击推送消息,要跳转到消息详情页。如果用户已经在详情页了,再点推送,不应该重新加载,而是应该刷新当前页面的数据。这时候FLAG_ACTIVITY_SINGLE_TOP就派上用场了。
Intent intent = new Intent(this, MessageDetailActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
当AMS发现目标Activity已经在栈顶时,不会创建新实例,而是调用已有实例的onNewIntent()方法。
关键点:FLAG_ACTIVITY_SINGLE_TOP只检查栈顶。如果目标Activity在栈中但不在栈顶,还是会新建实例。
22.3 更强大的Flag:FLAG_ACTIVITY_CLEAR_TOP
这个Flag就厉害了。它会查找栈中是否存在目标Activity的实例,如果存在,就把它上面的所有Activity都出栈,然后把这个实例拉到栈顶。
我曾经做过一个电商App,用户从商品列表页进入商品详情页,然后下单、支付。支付成功后要回到商品列表页。如果直接用普通的startActivity,返回栈里会有一堆页面。用FLAG_ACTIVITY_CLEAR_TOP就能优雅地解决。
Intent intent = new Intent(this, ProductListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
这里我习惯把CLEAR_TOP和SINGLE_TOP一起用。为什么呢?因为单独用CLEAR_TOP时,如果目标Activity不在栈顶,AMS会销毁它上面的所有Activity,然后重新创建目标Activity的实例。加上SINGLE_TOP后,如果目标Activity已经在栈顶了,就不会重新创建,直接复用。
22.4 onNewIntent 的生命周期回调
当Activity被重用时,AMS会调用它的onNewIntent()方法。这个方法在onResume()之前被调用。
生命周期顺序是这样的:
- 如果Activity在栈顶,且设置了SINGLE_TOP:
onPause()→onNewIntent()→onResume() - 如果Activity在栈中,且设置了CLEAR_TOP:它上面的Activity会被销毁,然后走上面的流程
个人建议:在onNewIntent()里一定要调用setIntent(intent),把新的Intent保存下来。否则后续调用getIntent()拿到的还是旧的Intent。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 保存新的Intent
setIntent(intent);
// 处理新数据
handleIntent(intent);
}
22.5 AMS内部的处理逻辑
咱们来看看AMS到底是怎么判断要不要重用的。核心代码在ActivityStackSupervisor里。
简化后的逻辑是这样的:
// 伪代码,展示核心逻辑
boolean shouldReuseActivity(Intent intent, ActivityRecord target) {
if (intent.hasFlag(FLAG_ACTIVITY_SINGLE_TOP) && target.isTopRunning()) {
// 在栈顶,直接复用
return true;
}
if (intent.hasFlag(FLAG_ACTIVITY_CLEAR_TOP)) {
// 查找栈中是否存在
ActivityRecord found = findInStack(target);
if (found != null) {
// 清除上面的Activity
clearActivitiesAbove(found);
// 如果同时设置了SINGLE_TOP,复用
if (intent.hasFlag(FLAG_ACTIVITY_SINGLE_TOP)) {
return true;
}
// 否则重新创建
return false;
}
}
return false;
}
嗯,这里要注意一点:FLAG_ACTIVITY_CLEAR_TOP在没有SINGLE_TOP的情况下,会销毁目标Activity然后重新创建。为什么?因为AMS认为你需要一个"干净"的实例。
22.6 实战中的坑与避坑指南
我曾经踩过一个坑:用FLAG_ACTIVITY_CLEAR_TOP跳转到主页面,结果主页面被重新创建了,之前的状态全丢了。后来才发现,我忘了加FLAG_ACTIVITY_SINGLE_TOP。
避坑指南:
- 如果你只想复用实例,记得同时设置CLEAR_TOP和SINGLE_TOP
- 在onNewIntent()里处理完新数据后,要考虑是否需要刷新UI
- 如果Activity被系统回收了,onNewIntent()不会被调用,走的是正常onCreate()流程
还有一个容易被忽略的点:FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TASK的组合。这个组合会清空整个任务栈,然后新建一个实例。说白了就是"重启"这个Activity。
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
这个用法在退出登录、切换账号的场景下很常见。但要注意,它会销毁所有其他Activity,如果你的App有后台服务或者正在进行的操作,可能会受到影响。
22.7 流程图:AMS的Activity重用决策
下面这张图展示了AMS在启动Activity时的决策流程,帮你理清思路:
22.8 总结
Activity重用机制,说白了就是AMS通过Intent的Flag来决定"要不要省点力气"。核心就两个Flag:
- FLAG_ACTIVITY_SINGLE_TOP:栈顶复用,适合刷新当前页面的场景
- FLAG_ACTIVITY_CLEAR_TOP:栈中查找并清除上方,适合返回主页的场景
我个人习惯是,只要涉及到页面跳转,都会想一想:这个页面有没有可能已经存在了?如果存在,我该怎么处理?想清楚这个问题,你的App的用户体验会上一个台阶。
嗯,关于onNewIntent和Flag的内容就到这里。记住一点:onNewIntent()不是每次都会调用的,它只在Activity被复用的时候才触发。所以,别把初始化逻辑全放在onCreate()里,该拆分的要拆分。
最后的小建议:写代码的时候,可以在onNewIntent()和onCreate()里都打上日志,看看实际运行时的调用顺序。实践出真知,光看文档是不够的。
公众号:蓝海资料掘金营,微信deep3321