从一个崩溃再谈Context

异常信息

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不支持分享功能,所以一直没有发现该异常,最根本原因还是由于代码写的有问题。

下面就来复现一下这个异常。

  1. 界面很简单,就一个EditText

  2. 界面布局利用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的上下文,则视图的主题为应用的主题,可能会造成不一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值