startActivity兼容性问题总结

startActivity兼容性问题总结


最近事情比较多,也遇到了很多坑,特别是最近android不同手机跳转scheme兼容性问题,在测试中发现android 6.0.1以下的手机不可以正常跳转,7.0-8.0的系统可以正常跳转,9.0的系统又无法正常跳转,为了找出真正的内部原因,今天特意查看相关的源代码

首先说说 android跳转scheme的代码如下

String url = "fungo:xxxxxx";
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse(url));
startActivity(intent);

通过捕捉可以发现OPPO A57(android 6.0.1)手机在控制台中打印如下错误信息

Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag.Is this really what you want?

为什么不同型号的手机有的会出现有的不会出现?最综定位如下:

在执行在执行startActivity这个函数的时候需要依赖Context对象,如果Context刚好是Activity对象,那恭喜你,执行不会出现问题,如果不是activity,而是系统的Context那就有可能会出现问题了,特别是当我们封装跳转startActivity的函数的时候,参数往往是Context,下面的函数不是很熟悉, 这个时候如果将该函数作为 公共方法,那就特别容易出问题了,加上开发人员的测试手机刚刚是android7-8系统的,所以问题就被埋没了。

public void startActivity(Context contet,Uri uri){
    Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse(url));
    startActivity(intent);
}

说一下定位过程,知道startActivity函数的问题,那就要看不同系统版本内部该函数的实现逻辑,通过定位,可以发现startActivity真正的实现类是ContextImpl

如下android 7.0-android 8.0系统startActivity的源码,可以发现下面这个判断 跳转scheme,跳转scheme我们调用的是startActivity(intent);options参数是传递null,按照下面的判断可以知道,即使我们使用的系统的Context,也不会进逻辑判断里面去,因为如果option参数为null的时候,是不满足下面的逻辑判断的,这就是为什么android 7.0-android 8.0能正常跳转的问题

if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
                    
                }

//android 7.0-android 8.0系统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 6.0.1以下的逻辑判断,仅仅是下面的判断,使用的系统的Context 刚好满足所以会出错

if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {}
    //android 6.0 系统startActivity的内部实现
    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 9.0.0的源码,对比android 7.0-8.0的可以很快的发现增加了options为null的判断,叉叉,怪不得9.0的手机也会出现

options == null|| ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)

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

至此,总结基本完毕。
在新项目中,遇到这类问题的可能比较少,但是如果是一个老项目,并且经过n次迭代,并将函数封装到公共库中,新人调用的话,就会遇到了,其实最好的做法就是对context参数进行instanceof判断,如果content是Activity对象,可以不用添加FLAG_ACTIVITY_NEW_TASK,如果不是则添加FLAG_ACTIVITY_NEW_TASK参数即可

你可能感兴趣的:(闲谈,android,开发)