启动模式
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()方法之后;
网友评论