android 应用程序中,一般都会发生activity的跳转和返回键的点击操作,而这就会涉及到activity启动模式的问题。



 



1、启动一个应用,系统就会创建一个Task用来存放主activity。 每个Activity都有一个taskAffinity属性,这个属性定义了用来存放它的Task,如果一个Activity没有显式的定义taskAffinity,那么它的taskAffinity属性的值就为Application的taskAffinity,如果Application也没有指明,那么它的taskAffinity的值就为应用程序的包名。



在默认情况下,以后的每次跳转,都会创建一个新的activity实例并放到同一个任务栈中。



2、Task遵循后进先出的规则,即后打开的activity会处于Task的顶部,当点击返回键时,后打开的activity首先被清除出Task。



 



activity有四种启动模式:standard、singleTop、singleTask 和 singleInstance,默认是standard的。可以在清单文件中通过属性android:launchMode来设置。



 



假设现在有3个Activity:MainActivity、FirstActivity 和 SecondActivity,它们的布局文件和逻辑都一样,只在界面上放一个button用来跳转。



 



在Activity类中,有一个getTaskId()方法,我们可以通过它获取当前activity所在的任务栈的ID,getTaskId()方法的API注释为:Return the identifier of the task this activity is in.This identifier will remain the same for the lifetime of the activity.



然后,在各个生命周期方法中打印log,如 onCreate()方法中打印  Log.d("HWGT", "onCreate----"+getTaskId()+"----"+this.toString());



在onNewIntent()方法中打印 Log.d("HWGT", "onNewIntent----"+getTaskId()+"----"+this.toString());



结合activity生命周期的各个方法来分析activity的四种启动模式。



 



standard:每一次都重新创建实例



 



现在设置3个Activity中Button的跳转逻辑为:



MainActivity —> FirstActivity



FirstActivity —> SecondActivity



SecondActivity —> MainActivity



3个Activity的启动模式都为standard,



则,连续跳转时,log打印情况如下:



android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_启动模式



getTaskId()的值保持不变,并且每一次跳转都在创建新的实例



现在更改SecondActivity中Button的跳转逻辑为:



SecondActivity —> SecondActivity ,则:



android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_移动开发_02



依然是 getTaskId()的值保持不变,并且每一次跳转都在创建新的实例,此时任务栈中从栈底到栈顶依次为:



MainActivity  FirstActivity  SecondActivity  SecondActivity  SecondActivity





singleTop:即使栈内有,只要栈顶没有就重新创建,栈顶有就不创建


现在更改SecondActivity的启动模式为singleTop,



SecondActivity中Button的跳转逻辑为:SecondActivity —> SecondActivity ,则:




android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_移动开发_03


getTaskId()的值保持不变,栈顶有SecondActivity的实例时,就直接复用,onCreate()方法不再执行,并且onNewIntent()方法得到执行,在连续打开SecondActivity的过程中,SecondActivity的onPause和onResume方法也会得到执行。




singleTask :栈内唯一,且会将位于该实例之上的activity全部销毁


现在更改SecondActivity中的跳转逻辑为:SecondActivity —> FirstActivity , 并且更改 FirstActivity的启动模式为 singleTask  ,MainActivity和SecondActivity为standard,则 依次打开MainActivity  FirstActivity  SecondActivity ,再从SecondActivity 跳转到FirstActivity  ,  log打印情况如下:


android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_启动模式_04

可以看到,在从SecondActivity跳到FirstActivity时,SecondActivity被销毁了。

此时任务栈中从栈底到栈顶依次为:MainActivity  FirstActivity




准确的说,在使用singleTask时,还需要考虑这个activity是否设置了TaskAffinity属性,


当加载一个singleTask模式的Activity时,首先会检查是否存在与该Activity的taskAffinity相同的Task。 

    2、如果不存在,那么就重新创建Task,并入栈。 


singleInstance同理,不同的是,singleInstance的Activity所在的Task不允许存在其他Activity。


 


singleInstance:创建一个新的Task来存放activity


如果一个activity的启动模式被设置为singleInstance,那么启动时,会为该activity单独创建一个Task。


现在将SecondActivity的启动模式改为singleInstance,MainActivity和FirstActivity的启动模式改为standard,


3个Activity中Button的跳转逻辑为:


MainActivity —> FirstActivity


FirstActivity —> SecondActivity


SecondActivity —> MainActivity

则 :

android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_移动开发_05

可以看到,从FirstActivity跳转到SecondActivity时,重新开启了一个ID为67的任务栈,查看最近打开的应用程序列表:

android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_移动开发_06

(图一)

可以发现,这时有两个任务栈,并且SecondActivity对应的任务栈处于前台(区别于图二)。

再从图一的应用程序列表回到SecondActivity,log打印情况如下:

android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_任务栈_07

从SecondActivity跳转到MainActivity:

android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_应用程序_08

再次查看最近打开的应用程序列表:

android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_移动开发_09

(图二)

再回到MainActivity,log打印情况如下:

android service启动的时候 拉起activity 判断activity是否运行 android启动activity方法_应用程序_10

从图二中我们可以发现,此时,MainActivity所对应的任务栈处于前台,并且该任务栈中的情况是:MainActivity-FirstActivity-MainActivity,点击3次返回键则退出该任务栈。




总结一下:


 


我们所要解决的有两个问题:


1、一个activity在他所处的任务栈中应该如何被加载?


答案是:standard总是创建、singleTop栈顶没有才创建、以及singleTask栈内没有就创建 ,singleInstance独享一个任务栈


2、一个activity应该被加载进哪个任务栈中?


一个activity的affinity 用来标识它属于哪个Task,可以在清单文件中设置taskAffinity的值来改变,理论上拥有相同affinity 的activity同属于一个Task,如果不进行设置的话,都从application的affinity 继承而来,而application的affinity 默认为清单文件中的包名。


默认情况下,从哪个任务栈中开启一个activity,这个activity就被加载进哪个任务栈中。


当Intent.FLAG_ACTIVITY_NEW_TASK和android:taskAffinity结合或者


android:taskAffinity和android:launchMode="singleTask"结合使用时,会导致使用另外一个任务栈来存放新打开的activity。


 


注:当Intent对象包含Intent.FLAG_ACTIVITY_NEW_TASK这个标记时,系统会寻找或创建一个新的task来放置目标Activity,寻找时依据目标Activity的taskAffinity属性进行匹配,如果找到一个task的taskAffinity与之相同,就将目标Activity压入此task中,如果查找无果,则创建一个新的task,并将目标Activity放置于此task。


 


上边提到的四种启动模式、affinity和Intent.FLAG_ACTIVITY_NEW_TASK的使用仍然具有一定的局限性,比如


1、 我们在清单文件中规定了A的启动模式,假设为standard,则代表总是以standard的方式打开A,可是,有些时候,我们需要在打开A的时候,实现和其他的启动模式同样的效果,即启动模式有些时候不够灵活。


 


intent 的常用 flag


就是在用Intent开启一个Activity时,在Intent中加入flag标志。如果同时设置了要打开的activity的启动模式,则flag的优先级更高。两种方式的差别在于,前者在于描述自己,声明自己应该以何种方式被加载,而后者则是主动声明要以何种方式加载一个Activity。最常用的几个flag为:


 


FLAG_ACTIVITY_SINGLE_TOP

这个FLAG就相当于加载模式中的singletop,比如说原来栈中情况是A,B,C,D,使用FLAG_ACTIVITY_SINGLE_TOP在D中启动D,栈中的情况还是A,B,C,D

FLAG_ACTIVITY_CLEAR_TOP

这个FLAG就相当于加载模式中的SingleTask,使用这种FLAG启动Activity,会把要启动的Activity之上的Activity全部清除出栈。比如:原来栈中的情况是A,B,C,D,这时使用FLAG_ACTIVITY_CLEAR_TOP从D中跳转到B,这个时候栈中的情况就是A,B了

FLAG_ACTIVITY_NO_HISTORY

这个不太好翻译,If set, the new activity is not kept in the history stack. As soon as the user navigates away from it, the activity is finished. This may also be set with the noHistory attribute.

比如原来栈内是A,B,C 这个时候使用FLAG_ACTIVITY_NO_HISTORY在C中启动D , 再在D中启动E,这个时候栈中情况就为A,B,C,E。


2、有时候,我们需要把一个应用程序的Activity移到另一个应用程序的Task中。这时,需要用到一个属性,allowTaskReparenting,它用来标记Activity能否从启动的Task移动到taskAffinity对应的Task,默认是继承至application中的allowTaskReparenting=false,如果为true,则表示可以更换;false表示不可以。