美文网首页
startActivityForResult遇到的一系列问题

startActivityForResult遇到的一系列问题

作者: 风月寒 | 来源:发表于2021-06-19 13:58 被阅读0次
启动模式

Android提供了四种启动模式,即standard、singleTop、singleTask和singleInstance。

standard

每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。被创建的实例,都符合正常情况下Activity创建的生命周期。

singleTop

如果该Activity已经在栈顶了,那么不会创建新的实例,即该Activity的oncreate、onStart方法不会被调用,但是它的onNewIntent方法会被调用。如果该Activity没有在栈顶,那么它一样会被创建新的实例并压入栈顶。

singleTask

此模式启动的Activity,在一个栈中只会有一个实例存在。以此模式启动的Activity A,首先会去寻找是否存匹配的任务栈(即任务相关性相同,后面会讲到),如果不存在则创建新的任务栈,同时把A入栈。如果存在匹配的任务栈,那么就会查找栈中是否存在A的实例,如果存在,那么在A之前的任务都会被清除出栈,然后把A显示到栈顶,同时调用onNewIntent方法;如果不存在A,那么创建A的实例。

这种启动模式我们可以在我们的app的mainActivity中设置。

singleInstance

这是一种加强的singleTask,它的加强之处在具有此模式的Activity,只能单独地位于一个新的任务栈中,然后该Activity独自在栈中,后续的请求均不会创建该Activity的新实例,它是在整个系统是全局的,除非该任务栈被系统销毁。

这种启动模式可以像闹铃这个应用。

TaskAffinity

任务相关性。这个参数标识了一个Activity任务栈的名字,默认情况下,所有Activity任务栈的名字为应用的包名。但是我们可以自己设置这个TaskAffinity参数,取一个和包名不同的名字,然后抵用singleTask启动模式,创建一个新的栈。

==注意TaskAffinity只能和singleTask启动模式同时使用才有作用。==

        <activity
            android:name="com.xxx.activity.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity 
            android:name="com.xxx.activity.SecondActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.xxx.task1">
            
        </activity>
        
        <activity 
            android:name="com.xxx.activity.ThirdActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.xxx.task1">
            
        </activity>

先启动A,然后在A中启动B,在B中启动C,在C中启动A,然后在A中再启动B,点击back键会发现,出现A,再点击back键,退出桌面。

首先常规启动A,创建任务栈task0;然后singleTask模式启动B,创建任务栈task1;创建C,入栈task1;standard模式创建A,入栈task1,现在任务栈task1为BCA,任务栈task0为A;singleTask创建B,于是C和A需出栈task1,task1中Acticity为B;按back键后,task1中B出栈后,task1就空了并被销毁,此时只能调度后台任务栈task0,并把task0中的A显示出来;再按back键,此时task0清空,最后只能调度桌面所在的任务栈,并且把launcher桌面显示出来。

intent flag

FLAG_ACTIVITY_NEW_TASK:单独的FLAG_ACTIVITY_NEW_TASK并不等价于启动模式 singleTask,它仅表示寻找activity所需的任务栈压入,FLAG_ACTIVITY_NEW_TASK+FLAG_ACTIVITY_CLEAR_TOP也不等价于启动模式singleTask

在FLAG_ACTIVITY_NEW_TASK+FLAG_ACTIVITY_CLEAR_TOP的情况下,AndroidManifest.xml中设置activity的启动模式为standard或singleTask时activity入栈方式是不一样的。分为如下3个情况:

当启动模式为standard时,如果activity所需的栈中已经存在该activity的实例了,那么这个实例连同它之上的activity都要出栈,然后再新建一个activity实例入栈。

当启动模式为singleTask时,如果activity所需的栈中已经存在该activity的实例了,那么系统会调用该实例的onNewIntent()方法,且只将该实例之上的activity出栈。

如果activity所需的栈中不存在该activity的实例,则不论启动模式为standard还是singleTask,都是新建activity实例直接入栈。

AndroidManifest.xml中设置activity的启动模式为singleTask时,则不论是FLAG_ACTIVITY_NEW_TASK+FLAG_ACTIVITY_CLEAR_TOP还是只有FLAG_ACTIVITY_NEW_TASK效果一样,因为singleTask模式中默认就带有FLAG_ACTIVITY_CLEAR_TOP标识。

FLAG_ACTIVITY_CLEAR_TOP:跟FLAG_ACTIVITY_SINGLE_TOP配合相当于singleTask。

FLAG_ACTIVITY_SINGLE_TOP:和singleTop模式一样

FLAG_ACTIVITY_NEW_TASK

我们知道,在activity内跳转到另一个页面,直接startActivity即可,但是在service或者BroadCast 中跳转的时候需要调用context.startActivity(),并且需要加上下面这句代码:

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

这是为什么了?

ContextImpl中的

@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. A bug was existed between N and O-MR1 which allowed this to work. We
        // maintain this for backwards compatibility.
        final int targetSdkVersion = getApplicationInfo().targetSdkVersion;

        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0//1
                && (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?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

从注释1可以看到,如果intent中没有添加FLAG_ACTIVITY_NEW_TASK这个属性,那么就会报错。

而在activity中重写了startActivity(),

@Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }
@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

所以并不需要设置那个flag.

在同一个app内跳转
requeCode < 0 且设置 FLAG_ACTIVITY_NEW_TASK
Intent intent = new Intent(MainActivity.this,Otheractivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent,RESULT_OK);

出现的现象:

onActivityResult中收不到返回的信息

跳转回来的生命周期:onRestart() -> onStart()-> onResume,不会走onActivityResult回调。

requeCode < 0 且不设置 FLAG_ACTIVITY_NEW_TASK

现象跟上面的一样

requeCode > 0 且设置 FLAG_ACTIVITY_NEW_TASK

生命周期:onPause -> onActivityResult -> onResume -> onPause -> onStop ->onRestart() -> onStart()-> onResume

返回的时候仍不走onActivityResult回调,因此onActivityResult中收不到返回的信息

requeCode > 0 且不设置 FLAG_ACTIVITY_NEW_TASK

生命周期: onActivityResult ->onRestart() -> onStart()-> onResume

返回的时候走onActivityResult回调,因此onActivityResult中收到返回的信息

在不同的app内跳转

不同app之间跳转有三种方式:

1、通过包名以及指定的activity的全路径跳转

Intent intent = new Intent();
ComponentName cn = new ComponentName(pkg, "com.example.dell.myapplication.OtherActivity");
intent.setComponent(cn);
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//startActivityForResult(intent,RESULT_OK);
startActivityForResult(intent,1);*/

B应用需要在manifest文件对应Activity添加

<activity android:name=".OtherActivity"
            android:exported="true"
            >

我们在网上看到的代码都说需要添加intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),实践证明千万不能添加,因为不会走onActivityResult,那样我们返回的东西就会接收不到。
还有就是requeCode > 0。不然也会不走onActivityResult。

2、包名拉起

Intent intent1 = getPackageManager().getLaunchIntentForPackage(pkg);
//intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent1,1);

这种方式不管怎么设置,都不走onActivityResult,所以需要拿到返回的东西,不要用这种方式。

3、url拉起

Intent intent = new Intent();
intent.setData(Uri.parse("csd://pull.csd.demo/cyn"));
//intent.putExtra("", "");//这里Intent当然也可传递参数,但是一般情况下都会放到上面的URL中进行传递
//intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent,1);

需要在跳转的页面进行设置

<activity android:name=".OtherActivity"
            android:exported="true"
            >
            <intent-filter>
                <data android:host="pull.csd.demo"
                    android:path="/cyn"
                    android:scheme="csd"/>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>
        </activity>

需要注意的是跟第一种一样,不能添加intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),以及requeCode > 0才能收到回调。

在参考一些的资料总结得到,我们也不能将启动模式设置成singleInstance。这样onActivityResult也不会正常回调。

A 启动 B ,B 中何时执行 setResult ? setResult 是否可以位于 finish 之后?
private void finish(int finishTask) {
        if (mParent == null) {
            int resultCode;
            Intent resultData;
            synchronized (this) {
                resultCode = mResultCode;
                resultData = mResultData;
            }
            if (false) Log.v(TAG, "Finishing self: token=" + mToken);
            try {
                if (resultData != null) {
                    resultData.prepareToLeaveProcess(this);
                }
                if (ActivityTaskManager.getService()
                        .finishActivity(mToken, resultCode, resultData, finishTask)) {
                    mFinished = true;
                }
            } catch (RemoteException e) {
                // Empty
            }
        } else {
            mParent.finishFromChild(this);
        }

        // Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
        // be restored now.
        if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
            restoreAutofillSaveUi();
        }
    }

setResult()方法放在finish()方法之前执行,一般置于onCreate()或者onResume()方法中;从finish()源码中可以看到finish()方法中有对resultCode以及resultData的操作;因此不能将setResult方法放在finish()方法之后;

相关文章

网友评论

      本文标题:startActivityForResult遇到的一系列问题

      本文链接:https://www.haomeiwen.com/subject/onoryltx.html