一、探究 FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
的行为及其应用场景
在 Android 中,我们有时需要对 Activity 的启动模式进行精细的控制,以满足特定的需求。为此,Android 提供了一些标志来帮助我们实现这些控制,其中 FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
就是这样两个标志。
FLAG_ACTIVITY_CLEAR_TOP
首先来看 FLAG_ACTIVITY_CLEAR_TOP
。当我们为一个新启动的 Activity 设置了这个标志,系统会检查当前任务栈中是否已经存在相同的 Activity 实例。
这个标志通常用于需要返回到任务栈中某个 Activity 的场景,如注销登录后返回到主页等。但是,如果我们没有与 FLAG_ACTIVITY_CLEAR_TOP
同时使用 FLAG_ACTIVITY_SINGLE_TOP
,系统仍然会重新创建目标 Activity 实例。另外,如果任务栈中没有目标 Activity,这个标志将不起作用。
FLAG_ACTIVITY_NEW_TASK
在 Android 中,任务(Task)和任务栈(Task Stack)是用来管理应用的 Activity 生命周期和导航的重要概念。
这两个概念对于理解 Android 的 Activity 启动模式,以及如何控制 Activity 的导航和生命周期等都非常重要。
FLAG_ACTIVITY_NEW_TASK
的使用和注意事项接下来,我们来看一看 FLAG_ACTIVITY_NEW_TASK
。当我们为一个 Activity 设置了这个标志,这个 Activity 会成为新任务的根,也就是新任务的第一个 Activity。如果没有设置这个标志,Activity 会被插入到当前任务栈。
这个标志通常用于从非 Activity(如 Service、BroadcastReceiver)中启动 Activity,或者需要在新的任务栈中打开 Activity 的场景。然而,使用这个标志时需要注意,如果已经存在相同的任务,那么这个标志会使得 Activity 请求被路由到已经存在的任务中,而不会创建新的任务。此外,如果没有正确理解任务和任务栈的概念,可能会导致 Activity 启动的行为与预期不符。因此,在使用 FLAG_ACTIVITY_NEW_TASK
时,我们需要确保充分理解了它的行为和可能的副作用。
下面是两个使用这些标志的代码例子:
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,没有弹出手势密码页面,而是直接进入消息页面。
NotificationClickedActivity
,同时弹窗也会调起消息页面(业务逻辑)。NotificationClickedActivity
的 onResume
方法触发ActivityLifecycle
弹出手势密码页面。NotificationClickedActivity
就自动 finish
了(特定小米机型必现),手势密码页面被连带着finish
。在 onActivityResumed
方法中,我们调用了 upAppLock
方法。upAppLock
的作用是重新把不在最顶部的手势密码页面弹出到任务栈的最上面:
@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);
}
}
}
在这个过程中,我们将关注两个问题: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_NEW_TASK
,我们可以观察以下四种情况:
FLAG_ACTIVITY_NEW_TASK
,手势密码页面没有 FLAG_ACTIVITY_NEW_TASK
:没有弹出手势密码页面,直接进入消息页面。FLAG_ACTIVITY_NEW_TASK
:手势密码页面显示,但没有消息页面,Launcher
启动的是主页面。原因是消息页面也被 NotificationClickedActivity
一起 finish
了(特定机型必现)。FLAG_ACTIVITY_NEW_TASK
,弹出手势密码页面有300ms延迟:upAppLock
起作用。upAppLock
不起作用。FLAG_ACTIVITY_NEW_TASK
,没有延迟弹出手势密码页面:成功弹出手势密码页面,upAppLock
起作用。最后,我们重点分析第4种情况表现正常的原因:
FLAG_ACTIVITY_NEW_TASK
,所以两个页面都和NotificationClickedActivity
不在同一个任务栈内,不会被连带finish
。onActivityResumed
触发了upAppLock
,重新把手势密码页面弹出到任务栈的最上面。此时的任务栈符合产品预期逻辑。通过以上分析,我们可以得出结论:为了正确弹出手势密码页面,我们需要注意 FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
的使用,以及如何正确处理任务和任务栈。
总的来说,FLAG_ACTIVITY_CLEAR_TOP
和 FLAG_ACTIVITY_NEW_TASK
可以用来控制 Activity 的启动模式和任务栈的行为。然而,使用它们时需要谨慎,确保理解了它们的行为和可能的副作用。在实际开发中,我们可能会遇到一些复杂的场景,如小米手机上的离线推送问题。这时,我们需要深入理解和分析问题,找出问题的根源,才能找到解决问题的方法。