起因
今天有收到对接我们 SDK 的客户反馈 android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? 的崩溃异常。这个大家都很熟悉,就是用非 Acitivity 的 Context 启动 Activity 的场景下需要添加 FLAG_ACTIVITY_NEW_TASK 的 flag,但是我们 SDK 的使用场景不方便加这个,所以我跟客户沟通需要传入 Activity 参数,但客户反馈之前传入 Application 的 Context 都是没问题的。这让我知道我之前的理解是片面的,所以有了这篇文章。
注:文中非 Activity Context 包含:不是 Activity 的 Context,以及 ContextWrapper#getBaseContext 不是 Activity 的 Context;Context 启动 Activity 指的是 Context 类中 startActivity(Intent intent) 一个参数的这个方法,并且不加 FLAG_ACTIVITY_NEW_TASK 这个 flag。
结论
不浪费大家时间,先直接说结论吧。
- 在低于 Android 7.0(24) 版本的系统上直接抛异常
- 大于等于 Android 7.0(24) 并且小于 Android 9.0(28) 版本的系统上启动不会抛异常,可以正常启动
- 大于等于 Android 9.0(28) 版本的系统上会基于 apk 的 targetSdk 版本,根据上面的规则来判断抛异常或正常启动,不在上面的版本中,直接抛异常。
系统源码
非 Activity Context 的 startActivity 的方法最终都是调用 ContextImpl 的 startActivity 的方法,我们可以通过 ContextImpl 源码来了解为什么有以上差异
Android 6.0 系统 ContextImpl startActivity 源码
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
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);
}
Android 7.0 系统 ContextImpl startActivity 源码
注意这个一个参数的这个方法调用两个参数的重载方法 options 传入的是 null
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@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.
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& 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);
}
Android 9.0 系统 ContextImpl startActivity 源码
通过新版本的代码,我猜测应该是 (7.0~ 8.1) 之间的代码有 bug,才导致能正常启动,判断条件应该是 (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && (options == null || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) , 后面版本发现这个问题,但又为了兼容这个老版本 bug (特性),才有了以下代码。
@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);
}
END
反馈这个问题的客户 apk targetSdk 是 26,之前没发现这个问题,应该是现在低于 android7.0 的比较少,最近才有这个版本的用户。
最后如果发现文章有错误,请不吝赐教,及时反馈给本人,避免误导他人。