非 Activity Context 启动 Activity 各个系统版本的差异

505 阅读3分钟

起因

今天有收到对接我们 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 的比较少,最近才有这个版本的用户。

最后如果发现文章有错误,请不吝赐教,及时反馈给本人,避免误导他人。