异常信息
AndroidRuntime: FATAL EXCEPTION: main Process: com.aspook.contexttest,
PID: 22578
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:672)
at android.app.ContextImpl.startActivity(ContextImpl.java:659)
at
android.content.ContextWrapper.startActivity(ContextWrapper.java:331)
at android.widget.TextView.shareSelectedText(TextView.java:9493)
at android.widget.TextView.onTextContextMenuItem(TextView.java:9211)
at
android.widget.Editor TextActionModeCallback.onActionItemClicked(Editor.java:3249)atcom.android.internal.policy.PhoneWindow DecorView ActionModeCallback2Wrapper.onActionItemClicked(PhoneWindow.java:3540)atcom.android.internal.view.FloatingActionMode 3.onMenuItemSelected(FloatingActionMode.java:85)
at
com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:761)
at
com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
at
com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:904)
at
com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:894)
at
com.android.internal.view.FloatingActionMode 4.onMenuItemClick(FloatingActionMode.java:111)atcom.android.internal.widget.FloatingToolbar FloatingToolbarMainPanel 1.onClick(FloatingToolbar.java:1015)atandroid.view.View.performClick(View.java:5204)atandroid.view.View PerformClick.run(View.java:21155)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5422)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
上面是整个异常的堆栈信息,我们先来看主要的部分:
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
这个异常相信大家都见过,也知道其发生的原因以及如何修复。不过我猜测大家遇到此异常多是因为在自己的代码里没有用Activity的上下文去startActivity且没有添加FLAG_ACTIVITY_NEW_TASK这个flag。而我遇到的这个异常却不是由于人为使用错误的Context去startActivity造成的,而是系统自己调用startActivity引起的。
原因分析
其实从用代码去启动Activity的层面上讲,自己写的话不会犯上述错误。情况是这样的,对于一个EditText,长按其内容可以弹出系统的上下文菜单,支持复制、全选等,然后在新的系统里面开始支持分享了。当选中一些文本,点击分享的时候,本应该调起系统的分享界面,而这时候就报此异常了,异常堆栈信息也指出了该事实:
at android.widget.TextView.shareSelectedText(TextView.java:9493)
由于老版本的Android不支持分享功能,所以一直没有发现该异常,最根本原因还是由于代码写的有问题。
下面就来复现一下这个异常。
- 界面很简单,就一个EditText
- 界面布局利用LayoutInflater引入,代码如下
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater mLayoutInflater = LayoutInflater.from(getApplicationContext()); View root = mLayoutInflater.inflate(R.layout.activity_second, null); setContentView(root); }
最终效果如下图:
上述代码乍一看没什么问题,如果不使用长按分享功能,也一切正常。但选中文本后进行分享,当调起系统分享界面时就发生了崩溃。我们可能有以下经验:
在Activity中通过startActivity(intent)可以正常启动另一Activity,而通过getApplicationContext().startActivity(intent)则会报上述相同错误(如果没设置FLAG_ACTIVITY_NEW_TASK的话)。因此可以判断原因必定是由于使用了错误的Context引起。仔细检查上述代码,并没有显式调用startActivity的代码,只有
LayoutInflater mLayoutInflater = LayoutInflater.from(getApplicationContext());
这一句引入了Context对象,因此可以推断是通过LayoutInflater引入布局时错用了Application的Context,而这里应该使用Activity的上下文对象。
将上面错误代码改写如下后,果然不再报错。
LayoutInflater mLayoutInflater = LayoutInflater.from(this);
关于Context的分析及使用,网上已有太多文章,这里就不再分析,请参考Context都没弄明白,还怎么做Android开发?或者自己去看源码,下面也是来自这篇文章的一张总结图片:
刨根问底
既然我们知道原因是由于LayoutInflater时传入了错误的Context,那么LayoutInflater中的Context是怎样传到TextView(EditView继承自TextView)中的呢?
从错误日志信息
at android.widget.TextView.shareSelectedText(TextView.java:9493)
定位到TextView的源码:
private void shareSelectedText() {
String selectedText = getSelectedText();
if (selectedText != null && !selectedText.isEmpty()) {
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
getContext().startActivity(Intent.createChooser(sharingIntent, null));
stopTextActionMode();
}
}
倒数第二行
getContext().startActivity(Intent.createChooser(sharingIntent, null));
就是这里调用的系统分享界面。
接着看getContext(),定位到View.java源码中
/**
* Returns the context the view is running in, through which it can
* access the current theme, resources, etc.
*
* @return The view's Context.
*/
@ViewDebug.CapturedViewProperty
public final Context getContext() {
return mContext;
}
继续跟踪发现,mContext是在View的构造函数中赋值的:
/**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
* access the current theme, resources, etc.
*/
public View(Context context) {
mContext = context;
……
}
那么我们接下来就是要分析LayoutInflater生成View的过程了,主要代码都在LayoutInflater.java中,逻辑细节比较繁琐,但最终是通过反射调用View的构造函数创建一个View对象,因此Context就传到了View中。如果对LayoutInflater生成View的流程感兴趣,可以参考LayoutInflater&LayoutInflaterCompat源码解析,这里不再分析,对于LayoutInflater生成View的需求,建议使用Activity的上下文对象,除了可以避免上述隐藏的崩溃外,还可以让视图的主题跟Activity保持一致,如果使用Application的上下文,则视图的主题为应用的主题,可能会造成不一致。