问题场景
先介绍一下业务场景,我们产品中有一个类似微信收款的功能,用户付款后会跳转到成功收款的页面。我们最近的优化版本将targetSDK版本升级到28后,这个功能无法正常跳转到成功收款的页面。经过定位发现在跳转过程中发生了以下异常。
Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
代码分析
下面是使用ARouter跳转伪代码,这个代码并未改动过怎么会发生异常呢?
ARouter.getInstance().build(“/xx/xx")
.withFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
.navigation()
对抛出异常的位置进行分析,这个问题出现要满足3个条件
- 没有设置FLAG_ACTIVITY_NEW_TASK
- 必须是targetSdk小于26,大于等于28
- 没有指定activity的任务栈
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in. A bug was existed between N and O-MR1 which allowed this to work. We
// maintain this for backwards compatibility.
final int targetSdkVersion = getApplicationInfo().targetSdkVersion;
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& (targetSdkVersion < Build.VERSION_CODES.N
|| targetSdkVersion >= Build.VERSION_CODES.P)
&& (options == null
|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
我们的跳转代码满足这个异常的第一和第三个条件,正好这次版本升级也将targetSDK从27调整到了28,正好触发了这个异常的条件。
引发的思考
1. targetSDK升级为什么会引发这个问题?为什么没调整之前没有问题?
targetSDK从系统角度来说,是用前兼容前面版本的,从开发者角度来说,是用来专门适配某一版本。通过这个注释知道这个地方为了兼容老版本,在P版本以后依然对适配这两个版本的应用保持老的行为,即不会导致崩溃,所以targetSDK为27(O)时不会发生这样的问题。
2. 项目中好多地方也没有指定activity的上下文就使用navigation方法,也没有设置Intent.FLAG_ACTIVITY_NEW_TASK的FLAG,为什么也不崩溃?
因为ARouter会对不是Activity的类型自动添加这个FLAG,不过前提就是没有设置过FLAG
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {
// Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
3. 为什么Activity之外的上下文startActivity需要FLAG_ACTIVITY_NEW_TASK这个标记?
Activity必须由任务栈(TaskRecord)管理,非Activity环境并不存在任务栈,所以使用该Flag来创建一个新的Activity栈。
如何避免此类问题
调整targetSDK需要对新特性有全面了解并进行适配,并做全面的兼容性测试。