Context.startActivity() 与 Activity.startActivity() 究竟有什么不同

先看下 Context.java 中的函数定义:

public abstract void startActivity(@RequiresPermission Intent intent);

/**
 * Launch a new activity.  You will not receive any information about when
 * the activity exits.
 *
 * 
Note that if this method is being called from outside of an
 * {@link android.app.Activity} Context, then the Intent must include
 * the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag.  This is because,
 * without being started from an existing Activity, there is no existing
 * task in which to place the new activity and thus it needs to be placed
 * in its own separate task.
 *
 * 

This method throws {@link ActivityNotFoundException}
 * if there was no Activity found to run the given Intent.
 *
 * @param intent The description of the activity to start.
 * @param options Additional options for how the Activity should be started.
 * May be null if there are no options.  See {@link android.app.ActivityOptions}
 * for how to build the Bundle supplied here; there are no supported definitions
 * for building it manually.
 *
 * @throws ActivityNotFoundException  
 *
 * @see #startActivity(Intent)
 * @see PackageManager#resolveActivity
 */
public abstract void startActivity(@RequiresPermission Intent intent,
        @Nullable Bundle options);

从注释中可以很清楚的看到,如果 Context is not an Activity,则通过 Context.startActivity() 的时候,必须加上一个 Intent.FLAG_ACTIVITY_NEW_TASK 标志。因为非 Activity环境启动Activity 的时候没有 Activity 栈,所以需要加上这个标志位新建一个栈。

ContextImpl.java -> Context.java

找到实现类 ContextImpl.java 「Api Level 27」对这两个接口的实现:

@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);
}

上面的代码中 if () 语句有三个判断条件。

(intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
options != null
ActivityOptions.fromBundle(options).getLaunchTaskId() == -1
当这三个条件都满足了才会 throw AndroidRuntimeException

这里看到一个问题,当没有对 intent 设置 Intent.FLAG_ACTIVITY_NEW_TASK 这个 flag,然后 options 这个参数为 null 的时候,是不会进入 if () 的函数块的,而是直接执行到了 execStartActivity() .

这不会有什么问题吗? 前面说到通过 Context.startActivity() 的时候需要设置NEW_TASK 标识,因为它是没有 activity 任务栈的,那通过上面的代码分析,发现这种启动方式并没有抛出异常。难道系统有 bug?

特意找了一个 API Level27的手机写了一个 demo 跑了一下,发现确实没有抛出 AndroidRuntimeException,而是正常启动了 activity。

这是为啥?????
在这里插入图片描述
仔细查看 logcat 发现一条关键的日志:
W/ActivityManager: startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: Intent { cmp=com.jacksen.demo.view/.MainActivity }

含义就是,通过非 Activity startActivity 的时候,会强制添加 Intent.FLAG_ACTIVITY_NEW_TASK 这个 flag。
在这里插入图片描述
谷歌闹着玩呢?!!

如果都是通过 Context.startActivity(Intent) 这种方式来启动 activity 的话,岂不是每个 activity 都是一个任务栈,if() 里面的 「throw AndroidRuntimeException 」也没机会被执行到。

废物代码吗?

。。。。

然后找了一个 API Level 28 的手机跑同一个 demo,发现直接 crash 了!

从这里就能猜到不同Android 系统版本,对这种情况的处理不一致。

找一下 API level 28 系统下 ContextImpl.startActivity() 的源码如下:

@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);
}

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)
A bug was existed between N and O-MR1 which allowed this to work. We maintain this for backwards compatibility.

从这段注释很清楚的看到,从 N (API Level 24) 到 O-MR1 (API Level 27) 的系统版本有一个 bug ?。

谷歌官方的处理是保持向下兼容, 也就是 bug 依然存在  ̄□ ̄||。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值