Context.startActivity() 与 Activity.startActivity() 究竟有什么不同?
Posted 月盡天明
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.
*
* <p>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.
*
* <p>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 依然存在  ̄□ ̄||。
以上是关于Context.startActivity() 与 Activity.startActivity() 究竟有什么不同?的主要内容,如果未能解决你的问题,请参考以下文章
Context.startActivity() 与 Activity.startActivity() 究竟有什么不同?