在Activity外使用startActivity()方法报错原因与解决办法

相信Android开发都遇到过这样一个报错信息

04-09 15:55:08.165: E/AndroidRuntime(3403): 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的Context调用startActivity方法时,Intent添加一个FLAG_ACTIVITY_NEW_TASK的flag,代码如下

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

但是细心的同学会发现Android7.0及以上的机型即使没有添加以上代码,也不会崩溃。这是怎么回事呢?

这就涉及到activity的startActivity和context的startActivity方法之间的区别,Activity也是Context的子类,但是Activity本身实现了startActivity方法,所以两个是不同的。

activity的startActivity方法最终会调用Activity类的startActivityForResult方法,代码如下

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

context的 startActivity方法最终会调用ContextImpl的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);
}

7.0及以下

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

从上面代码可以看出,Activity的startActivity方法是不会抛出文章开始说的那个错的;问题集中在非Activity的Context调用startActivity方法,在7.0之前如果Intent没有添加flag:FLAG_ACTIVITY_NEW_TASK,程序是必崩的,7.0及以上如果调用startActivity(intent)方法,是不会崩的,因为此时options为空。一般为了兼容7.0以下版本,我们还是得添加flag的。

startActivity(intent, null);

注意7.0增加了两个判断条件

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?");
}

到这里基本上文章就该结束了,但是我们项目在线上还是遇到文章开始说的那个问题,有一些崩溃,都是7.0以下的机型。经过一番寻找,终于定位到有问题的代码,原因是我们用到了Intent的一个方法createChooser(Intent target, CharSequence title)

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_TEXT, text);
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
shareIntent.setType("text/plain");
Utils.getContext().startActivity(Intent.createChooser(shareIntent, "分享到"));

从代码上看到我们明明给Intent添加了flag,还是会崩溃

shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

我们跟进Intent.createChooser方法里看看

public static Intent createChooser(Intent target, CharSequence title, IntentSender sender) {
    Intent intent = new Intent(ACTION_CHOOSER);
    intent.putExtra(EXTRA_INTENT, target);
    if (title != null) {
        intent.putExtra(EXTRA_TITLE, title);
    }
​
    if (sender != null) {
        intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
    }
​
    // Migrate any clip data and flags from target.
    int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
            | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
            | FLAG_GRANT_PREFIX_URI_PERMISSION);
    if (permFlags != 0) {
        ClipData targetClipData = target.getClipData();
        if (targetClipData == null && target.getData() != null) {
            ClipData.Item item = new ClipData.Item(target.getData());
            String[] mimeTypes;
            if (target.getType() != null) {
                mimeTypes = new String[] { target.getType() };
            } else {
                mimeTypes = new String[] { };
            }
            targetClipData = new ClipData(null, mimeTypes, item);
        }
        if (targetClipData != null) {
            intent.setClipData(targetClipData);
            intent.addFlags(permFlags);
        }
    }
​
    return intent;
}

首先这个方法先把原Intent存到新Intent的extra上,

intent.putExtra(EXTRA_INTENT, target);

然后permFlags值为0,就这样,新Intent的flag就没了,所以返回来的Intent的flag值为0,虽然extra里的Intent的flag值为Intent.FLAG_ACTIVITY_NEW_TASK,但是没有,取flag值时不是从extra里的Intent取值。

int permFlags = target.getFlags() & (FLAG_GRANT_READ_URI_PERMISSION
        | FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_PERSISTABLE_URI_PERMISSION
        | FLAG_GRANT_PREFIX_URI_PERMISSION);

有点坑吧,所以如果使用了Intent.createChooser()方法后还得加上

addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Utils.getContext().startActivity(Intent.createChooser(shareIntent, "分享到").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));

建议以后如果有非Activity的Context调用startActivity()方法,一定要用7.0以下的机型测试一下,我们手上的测试大部分是7.0的,加上那段代码的使用场景很少,所以导致了线上出现这个bug。
来源:历史课代表

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值