Android Service 基础知识点
我们都知道Activity中启动Activity,只需要startActivity,如果在Service中启动Activity呢?
先看Context的继承图关系图如下
- 抽象类Context,最重要的两个子类ContextImpl和ContextWrapper
- ContextWrapper只是一个包装类,主要功能实现都是通过调用ContextImpl去实现的
- ContextImpl,是Context功能实现的主要类
- ContextThemeWrapper,包括一些主题的包装,由于Service没有主题,所以直接继承ContextWrapper;但是Activity就需要继承ContextThemeWrapper
先看Service启动Activity,我使用的是 Api level 7.1.2(Android 25)模拟器##
在Service中启动Activity,很多人说在非Activity中启动Activity需要加FLAG_ACTIVITY_NEW_TASK flag,如果我不加会怎么样呢?
//MyService.java
Intent intent = new Intent(this,SecondActivity.class);
startActivity(intent);
运行结果
并没有出现异常和崩溃,跳转也是正常的,但是与之前所说的会崩溃报出异常不符合啊?!难道是与版本有关吗?于是我使用了Android 23去测试果然抛出了异常
在Android 23中出现了AndroidRuntimeException异常
Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
at android.app.ContextImpl.startActivity(ContextImpl.java:912)
at android.app.ContextImpl.startActivity(ContextImpl.java:888)
at android.content.ContextWrapper.startActivity(ContextWrapper.java:379)
at com.arch.startactivity.MyService.onCreate(MyService.java:32)
at android.app.ActivityThread.handleCreateService(ActivityThread.java:3532)
at android.app.ActivityThread.access$1300(ActivityThread.java:199)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1666)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
那么为什么会出现这样的情况呢?版本差异吗?
既然主要实现类是ContextImpl,我们去看它StartActivity()到底做了什么?###
ContextImpl在Android studio中属于隐藏源码,在IDE中可能看不到,那就需要在SDK中去找 我的是:D:\AndroidSdk\sources\android-23\android\app\ContextImpl.java
android-23 中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-26 中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.
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-23中发现没有Intent.FLAG_ACTIVITY_NEW_TASK会直接抛出异常,而android-26中我们在非 Activity 调用 startActivity() 的时候,我们这个 options 通常是 null 的,所以在 26 之间的时候,误把判断条件 options == null 写成了 options != null 导致进不去 if,从而不会抛出异常
附加:在android 24-android 27(即android N-android O)之间出现了bug,也就是说即使没有加Intent.FLAG_ACTIVITY_NEW_TASK也会正常跳转
android 28 中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);
}
可以很明显看到判断条件targetSdkVersion < Build.VERSION_CODES.N || targetSdkVersion >= Build.VERSION_CODES.P 即在24-27
那么Activity中startActivity为啥不会出现这样的异常呢?
看Activity中启动Activity的源码
//Activity.java
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
//我们options传的是null
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
//这里才是实际的调用
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
...
Activity.startActivity() ->startActivityForResult()->mInstrumentation.execStartActivity() ...最终还是Ams去启动Activity 也就是Activity中重写了startActivity()方法所以不会出现这个异常
为什么非Activity启动Activity要强制规定使用参数FLAG_ACTIVITY_NEW_TASK呢?
其实直观很好理解,如果不是在Activity中启动的,那就可以看做不是用户主动的行为,也就说这个界面可能出现在任何APP之上,如果不用Intent.FLAG_ACTIVITY_NEW_TASK将其限制在自己的Task中,那用户可能会认为该Activity是当前可见APP的页面,这是不合理的。举个例子:我们在听音乐,这个时候如果邮件Service突然要打开一个Activity,如果不用Intent.FLAG_ACTIVITY_NEW_TASK做限制,那用户可能认为这个Activity是属于音乐APP的,因为用户点击返回的时候,可能会回到音乐,而不是邮件(如果邮件之前就有界面)
总结
对比源码发现,在我们非 Activity 调用 startActivity() 的时候,我们这个 options 通常是 null 的,所以在 24~27 之间的时候,误把判断条件 options == null 写成了 options != null 导致进不去 if,从而不会抛出异常,如此我们使用 Context.startActivity() 的时候是一定要加上 FLAG_ACTIVITY_NEW_TASK 的,但是在 Android N 到 O-MR1,即 24~27 之间却出现了 bug,即使没有加也会正确跳转
结语:感谢各位大佬的分享,对此有疑问的可以去运行跑一下,看一下相关的源码,如有错误的需要改进的地方,请留言评论指出,谢谢!
参考文章:
- https://blog.csdn.net/fang323619/article/details/74388804
- https://juejin.cn/post/6844903890148655117