Calling startActivity() from outside of an Activity context requires FLAG_ACTIVITY_NEW_TASK 引发的思考和分析

最近在开发过程中遇到这么个问题,报错如下

当我使用传入context的Intent来启动Activity,app崩溃了,而且报了一个我以前重来没见过的错误。

错误log的意思大概为,我使用了一个不属于Activity的Context来调用startActivity方法,需要设置一个FLAG_ACTIVITY_NEW_TASK的Flag才可以正常运行,而且最后给我来个了疑问句,问我这是不是我所期待的也是挺骚的。

问题分析

(如果急于求解,不管过程和原因的话,可以跳过分析过程,直接看后面的 解决方法)

分析一下问题的原因,首先找到报错的代码,抛开这个context不说,这就是一个很普通的启动Activty,所以要着重看一下这个Context是哪里来的。

注:我所要启动的Activity是在Manifest中已经标记为栈内复用了

按住Ctrl左键点击context查看来源,发现这是从外部传进来的,找到调用的地方则发现传入的Context是BroadcastReceiver的Context,这下问题就来了,为什么四大组件之一 广播的context会引起这样错误?

扫描二维码关注公众号,回复: 12473534 查看本文章

这时候问题又变成了:BroadcastReceiver的Context和Activity的Context有什么不一样吗?

又到了需要看源码的时候了。。。

Ctrl 左键点击查看BroadcastReceiver的onReceive方法,追踪到源码部分

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void executePendingBroadcasts() {
        while (true) {
            final BroadcastRecord[] brs;
            synchronized (mReceivers) {
                final int N = mPendingBroadcasts.size();
                if (N <= 0) {
                    return;
                }
                brs = new BroadcastRecord[N];
                mPendingBroadcasts.toArray(brs);
                mPendingBroadcasts.clear();
            }
            for (int i=0; i<brs.length; i++) {
                final BroadcastRecord br = brs[i];
                final int nbr = br.receivers.size();
                for (int j=0; j<nbr; j++) {
                    final ReceiverRecord rec = br.receivers.get(j);
                    if (!rec.dead) {
                        rec.receiver.onReceive(mAppContext, br.intent);    //在这里,onReceive方法传入了mAppContext
                    }
                }
            }
        }
    }

继续查看mAppContext是如何赋值的,

    private LocalBroadcastManager(Context context) {
        mAppContext = context;    //当初始化LocalBroadcastManager调用这个构造函数时,会将传入的Context赋值给这个mAppContext
        mHandler = new Handler(context.getMainLooper()) {

            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_EXEC_PENDING_BROADCASTS:
                        executePendingBroadcasts();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        };
    }

找到调用构造函数的地方,就会发现传入的Context是通过getApplicationContext()获得的,也就是说我最终在广播BroadcastReceiver的onReceive(Context context, Intent intent)方法内获得的Context是通过调用context.getApplicationContext()方法获得的。

@NonNull
    public static LocalBroadcastManager getInstance(@NonNull Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext());  //调用LocalBroadcastManager的构造函数,传入的Context是通过getApplicationContext()获得的
            }
            return mInstance;
        }
    }

看到了这里,捋一下思路,我们广播BroadcastReceiver的onReceive方法中得到的Context是通过调用LocalBroadcastManager构造函数,传入的context.getApplicationContext()得到的,就是说我们通过广播得到的Context是Application的Context而不是Activity的Context,所以问题的原因一下就找到了,正因为我在广播内,使用了广播的Context来startActivity,所以会报出这个错误。

注:不仅广播,在Service服务内使用服务的Context来启动Activity亦然如此。

这下以后都会记住广播内onReceive()方法内传入的Context是Application的Context了,一轮下来也是有所收获,又是一个小细节。

解决方法

分析过后,就要着重于解决问题,大概有两种方法来解决这个问题:

  • 给你要启动Activity的Intent添加一个FLAG_ACTIVITY_NEW_TASK的Flag(个人不推荐)
  • 使用Activity的Context来调用该方法

1、以我的代码为例子,给Intent添加Flag,如下:

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

但是不要盲目地使用这个方法,因为设置这个flag会影响你的Activity任务栈,这可能会影响以后Activity关闭和开启,导致Activity达不到你想要的启动和关闭效果。

对于非Activity启动的Activity(比如Service或者通知中启动的Activity)需要显式设置Intent.FLAG_ACTIVITY_NEW_TASK,而singleTask及singleInstance在AMS中被预处理后,隐形的设置了Intent.FLAG_ACTIVITY_NEW_TASK,而启动模式是standard及singletTop的Activity不会被设置Intent.FLAG_ACTIVITY_NEW_TASK,除非通过显式的intent.setFlag进行设置。

可以看官方文档的介绍:https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK

但是官方文档的介绍也不一定好理解,要更好地理解Activity的启动方式,个人推荐看看这篇博客:https://www.jianshu.com/p/b3a95747ee91

2、不在Service,BroadcastReceiver内获取Context启动Activity,使用外部传入的Activity的Context来启动

方法有很多,调用时传入方法或者类以外Activty的Context,或者使用全局获取的Activity的Context(类似定义一个公共静态方法来专门获取公共的Context)。

共勉!

猜你喜欢

转载自blog.csdn.net/Nobody_else_/article/details/109283369