深入探究 Android Activity 启动模式:FLAG_ACTIVITY_CLEAR_TOP 与 FLAG_ACTIVITY_NEW_TASK 实战解析
本文深入探讨了 Android 中的 FLAG_ACTIVITY_CLEAR_TOP 和 FLAG_ACTIVITY_NEW_TASK 启动标志,通过实际案例分析了小米手机离线推送跳转问题,旨在帮助开发者更好地理解和应用这两个标志。
文章目录
一、探究 FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
的行为及其应用场景
在 Android 中,我们有时需要对 Activity 的启动模式进行精细的控制,以满足特定的需求。为此,Android 提供了一些标志来帮助我们实现这些控制,其中 FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
就是这样两个标志。
1.1 FLAG_ACTIVITY_CLEAR_TOP
首先来看 FLAG_ACTIVITY_CLEAR_TOP
。当我们为一个新启动的 Activity 设置了这个标志,系统会检查当前任务栈中是否已经存在相同的 Activity 实例。
- 如果存在,那么这个 Activity 之上的所有 Activity 都会被销毁,使得这个 Activity 实例成为栈顶。
- 如果不存在,系统会正常启动新的 Activity。
这个标志通常用于需要返回到任务栈中某个 Activity 的场景,如注销登录后返回到主页等。但是,如果我们没有与 FLAG_ACTIVITY_CLEAR_TOP
同时使用 FLAG_ACTIVITY_SINGLE_TOP
,系统仍然会重新创建目标 Activity 实例。另外,如果任务栈中没有目标 Activity,这个标志将不起作用。
1.2 FLAG_ACTIVITY_NEW_TASK
1.2.1 任务和任务栈
在 Android 中,任务(Task)和任务栈(Task Stack)是用来管理应用的 Activity 生命周期和导航的重要概念。
-
任务(Task):任务是一个用户与应用进行交互的会话。它是由用户从启动应用开始,到用户离开应用结束的一系列操作过程。一个任务对应于一个应用程序,但一个应用程序可以有多个任务。任务中可以包含一个或多个 Activity,这些 Activity 按照它们打开的顺序排列,形成了任务栈。
-
任务栈(Task Stack):任务栈是用来管理一个任务中所有 Activity 的堆栈结构。新的 Activity 被放置(push)到栈的顶部,用户看到的总是位于栈顶的 Activity。当用户按下返回键时,当前的 Activity 会从栈顶被移除(pop),并销毁,之前的 Activity 会重新显示。任务栈遵循“后进先出”(LIFO)的原则。
这两个概念对于理解 Android 的 Activity 启动模式,以及如何控制 Activity 的导航和生命周期等都非常重要。
1.2.2 FLAG_ACTIVITY_NEW_TASK
的使用和注意事项
接下来,我们来看一看 FLAG_ACTIVITY_NEW_TASK
。当我们为一个 Activity 设置了这个标志,这个 Activity 会成为新任务的根,也就是新任务的第一个 Activity。如果没有设置这个标志,Activity 会被插入到当前任务栈。
这个标志通常用于从非 Activity(如 Service、BroadcastReceiver)中启动 Activity,或者需要在新的任务栈中打开 Activity 的场景。然而,使用这个标志时需要注意,如果已经存在相同的任务,那么这个标志会使得 Activity 请求被路由到已经存在的任务中,而不会创建新的任务。此外,如果没有正确理解任务和任务栈的概念,可能会导致 Activity 启动的行为与预期不符。因此,在使用 FLAG_ACTIVITY_NEW_TASK
时,我们需要确保充分理解了它的行为和可能的副作用。
1.3 代码例子
下面是两个使用这些标志的代码例子:
Intent intent = new Intent(this, TargetActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
在这个例子中,我们创建了一个指向 TargetActivity
的 Intent
,并为它添加了 FLAG_ACTIVITY_CLEAR_TOP
标志。当我们启动这个 Intent
时,系统会检查当前任务栈中是否已经存在 TargetActivity
的实例。如果存在,那么这个实例之上的所有 Activity
都会被销毁,使得 TargetActivity
实例成为栈顶。
Intent intent = new Intent(this, TargetActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
在这个例子中,我们创建了一个指向 TargetActivity
的 Intent
,并为它添加了 FLAG_ACTIVITY_NEW_TASK
标志。当我们启动这个 Intent
时,TargetActivity
会成为新任务的根,也就是新任务的第一个 Activity
。
二、深入探究:小米手机离线推送跳转问题实例分析
本节将阐述在小米手机上点击离线推送,跳转到消息页面时,无法弹出手势密码页面的问题定位过程。
首先描述一下出问题的表现:
设置了手势密码,kill掉app,收到消息离线推送弹窗,点击弹窗拉起app,没有弹出手势密码页面,而是直接进入消息页面。
2.1 问题流程分析
- 点击小米离线推送弹窗,会自动调起小米推送sdk的页面
com.xiaomi.mipush.sdk.NotificationClickedActivity
(下文简化为NotificationClickedActivity
),同时弹窗也会调起消息页面(业务逻辑)。 NotificationClickedActivity
的onResume
方法触发ActivityLifecycle
弹出手势密码页面。- 手势密码页面的弹出逻辑会延迟300ms执行,目的是为了先让前一个业务页面弹出,再盖上手势密码页面。
- 但紧接着
NotificationClickedActivity
就自动finish
了(特定小米机型必现),手势密码页面被连带着finish
。
- 这个场景描述的是系统行为。
- NotificationClickedActivity 页面后续在某个时机会自动finish(猜测是小米推送sdk控制)。
- NotificationClickedActivity 页面finish时,跟 NotificationClickedActivity页面在同一个任务栈的页面,都会跟着finish(多次测试观察验证如此,只有特定小米机器上会出现)。
- 此时只剩一个消息界面。
在 onActivityResumed
方法中,我们调用了 upAppLock
方法。upAppLock
的作用是重新把不在最顶部的手势密码页面弹出到任务栈的最上面。
onActivityResumed
是 ActivityLifecycle
中的方法,这个 ActivityLifecycle
单例是在App进程启动时,通过 registerActivityLifecycleCallback
注册的。NotificationClickedActivity
、手势密码页、消息界面的 onResume
被调用前,都会先进入 ActivityLifecycle
的 onActivityResumed
函数。
public final class ActivityLifecycle implements Application.ActivityLifecycleCallbacks, ITPFEventListener {
//...
@Override
public void onActivityResumed(Activity activity) {
// ... 省略部分代码 ...
if (ActivityLifecycleState.INSTANCE.isAndroid10Background()) {
// 设置前台状态
ActivityLifecycleState.INSTANCE.setAndroid10Background(false);
// 处理进入前台任务
handleGoForeground(activity);
}
upAppLock(activity);
// ... 省略部分代码 ...
}
private void handleGoForeground(Activity activity) {
// ... 省略部分代码 ...
ThreadUtils.runOnMainThread(new Runnable() {
@Override
public void run() {
// 弹出手势密码页面
IAccount.get().onActivityStarted(activity);
}
}, 300);
// ... 省略部分代码 ...
}
private void upAppLock(Activity activity) {
// ... 省略部分代码 ...
if (appLockActivity != null && (!appLockActivity.isFinishing() || !appLockActivity.isDestroyed())) {
// 防止出现连续两个手势密码页面
if (!activity.getClass().getName().equals(appLockName) &&
// 防止把最上面的手势密码页面 finish
topActivity != null && !topActivity.getClass().getName().equals(appLockName)) {
// 结束任务栈内的重复手势密码页面,这个手势密码页面当前已经被压在底下看不见
if (appLockActivity.getTaskId() == topActivity.getTaskId()) {
appLockActivity.finish();
}
// 弹出手势密码页面
IAccount.get().onActivityStarted(activity);
}
}
}
// ...
}
2.2 解决问题过程
在这个过程中,我们将关注两个问题:FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
。
2.2.1 问题一:FLAG_ACTIVITY_CLEAR_TOP
首先,我们猜测消息页面使用的 FLAG_ACTIVITY_CLEAR_TOP
,会导致手势密码页面消失。所以第一个改动点是启动消息页面时,不使用FLAG_ACTIVITY_CLEAR_TOP
。
2.2.2 问题二:FLAG_ACTIVITY_NEW_TASK
关于 FLAG_ACTIVITY_NEW_TASK
,我们可以观察以下四种情况:
-
消息页面有
FLAG_ACTIVITY_NEW_TASK
,手势密码页面没有FLAG_ACTIVITY_NEW_TASK
:没有弹出手势密码页面,直接进入消息页面。 -
消息页面和手势密码页面都没有
FLAG_ACTIVITY_NEW_TASK
:手势密码页面显示,但没有消息页面,Launcher
启动的是主页面。原因是消息页面也被NotificationClickedActivity
一起finish
了(特定机型必现)。 -
消息页面和手势密码页面都有
FLAG_ACTIVITY_NEW_TASK
,弹出手势密码页面有300ms延迟:
- 正常弹出手势密码页面的情况是,先弹出了手势密码页面,消息页面在 300ms 后创建,
upAppLock
起作用。 - 无法弹出手势密码页面的情况是,消息页面在 300ms 内创建,再弹出手势密码页面,
upAppLock
不起作用。
- 消息页面和手势密码页面都有
FLAG_ACTIVITY_NEW_TASK
,没有延迟弹出手势密码页面:成功弹出手势密码页面,upAppLock
起作用。
最后,我们重点分析第4种情况表现正常的原因:
-
因为消息页面和手势密码页面都有
FLAG_ACTIVITY_NEW_TASK
,所以两个页面都和NotificationClickedActivity
不在同一个任务栈内,不会被连带finish
。 -
因为手势密码页面不延迟弹出,所以页面的弹出时序变成了:先弹出手势密码页面,再弹出消息页面,此时任务栈中,手势密码页面在消息页面的下面。
-
消息页面的
onActivityResumed
触发了upAppLock
,重新把手势密码页面弹出到任务栈的最上面。此时的任务栈符合产品预期逻辑。
页面名称 | 修改前 | 修改后 | 修改原因分析 |
---|---|---|---|
手势密码页面 | 没有FLAG_ACTIVITY_NEW_TASK,没有FLAG_ACTIVITY_CLEAR_TOP | 有FLAG_ACTIVITY_NEW_TASK,没有FLAG_ACTIVITY_CLEAR_TOP | 因为手势密码页面带上了FLAG_ACTIVITY_NEW_TASK,所以手势密码页面跟小米推送sdk的NotificationClickedActivity页面不在同一个任务栈,不会被连带finish。 |
消息页面 | 有FLAG_ACTIVITY_NEW_TASK,有FLAG_ACTIVITY_CLEAR_TOP | 有FLAG_ACTIVITY_NEW_TASK,没有FLAG_ACTIVITY_CLEAR_TOP | 因为消息页面删掉了FLAG_ACTIVITY_CLEAR_TOP,所以消息页面启动时,不会清除掉它上面的手势密码页面。 |
通过以上分析,我们可以得出结论:为了正确弹出手势密码页面,我们需要注意 FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
的使用,以及如何正确处理任务和任务栈。
三、总结
总的来说,FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
可以用来控制 Activity 的启动模式和任务栈的行为。然而,使用它们时需要谨慎,确保理解了它们的行为和可能的副作用。在实际开发中,我们可能会遇到一些复杂的场景,如小米手机上的离线推送问题。这时,我们需要深入理解和分析问题,找出问题的根源,才能找到解决问题的方法。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)