Android targetSDK 升级引发的线上问题

584 阅读2分钟

问题场景

先介绍一下业务场景,我们产品中有一个类似微信收款的功能,用户付款后会跳转到成功收款的页面。我们最近的优化版本将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个条件

  1. 没有设置FLAG_ACTIVITY_NEW_TASK
  2. 必须是targetSdk小于26,大于等于28
  3. 没有指定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需要对新特性有全面了解并进行适配,并做全面的兼容性测试。

文章参考: Android面试官装逼失败之:Activity的启动模式