Android - Activity的启动模式

时间:2022-02-18 16:47:46
android 应用程序中,一般都会发生activity的跳转和返回键的点击操作,而这就会涉及到activity启动模式问题。 首先引入任务栈Task的概念,(本篇文章不过多解释Task和Back Stack,没太多影响)Task可以理解为是一个容器,启动一个应用,系统就会创建一个Task用来存放主activity, 1、在默认情况下,以后新打开的activity都会放在同一个Task中, 2、即使Task里边已经有了某个activity的实例,在每一次跳转到该activity时,都会再创建一次。 3、Task遵循后进先出的规则,即后打开的activity会处于Task的顶部,当点击返回键时,后打开的activity首先被清除出Task。
上边的第3点是恒定不变的,第1和第2点,则可以在清单文件中通过设置属性android:launchMode的值即设置activity的启动模式来进行改变。 activity有四种启动模式:standard、singleTop、singleTask 和 singleInstance,如不配置,默认是standard的。
在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.
下边用一个demo来说明activity的四种启动模式: 共3个Activity:MainActivity、FirstActivity 和 SecondActivity,它们的布局文件都一样,只在最底部放一个button用来跳转: [java] view plain copy Android - Activity的启动模式Android - Activity的启动模式
  1. <span style="background-color: rgb(255, 255, 255);"><span style="font-family:Microsoft YaHei;font-size:14px;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     tools:context="com.hwgt.launchmode.MainActivity" >  
  6.     <Button   
  7.         android:id="@+id/bt_Jump"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:layout_alignParentBottom="true"  
  11.         android:layout_marginBottom="13dp"  
  12.         android:layout_centerHorizontal="true"  
  13.         android:text="跳转到 FirstActivity"  
  14.         android:textSize="15dp"/>  
  15. </RelativeLayout></span></span>  
3个Activity中逻辑都一样:第一、处理button的点击事件:[java] view plain copy Android - Activity的启动模式Android - Activity的启动模式
  1. <span style="background-color: rgb(255, 255, 255);"><span style="font-family:Microsoft YaHei;font-size:14px;">//MainActivity:  
  2. Intent intent = new Intent(MainActivity.this, FirstActivity.class);  
  3. MainActivity.this.startActivity(intent);  
  4. //FirstActivity:  
  5. Intent intent = new Intent(FirstActivity.this, SecondActivity.class);  
  6. FirstActivity.this.startActivity(intent);  
  7. //SecondActivity:  
  8. Intent intent = new Intent(SecondActivity.this, MainActivity.class);  
  9. SecondActivity.this.startActivity(intent);</span></span>  
第二、onCreate()方法中打印 Log.d("HWGT", "onCreate----"+getTaskId()+"----"+this.toString());onNewIntent()方法中打印 Log.d("HWGT", "onNewIntent----"+getTaskId()+"----"+this.toString());
standard:每一次都重新创建实例3个Activity的启动模式都为standard时,log打印情况如下:Android - Activity的启动模式getTaskId()的值保持不变,并且每一次跳转都在创建新的实例现在更改SecondActivity中的跳转逻辑为:[java] view plain copy Android - Activity的启动模式Android - Activity的启动模式
  1. <span style="background-color: rgb(255, 255, 255);"><span style="font-family:Microsoft YaHei;font-size:14px;">Intent intent = new Intent(SecondActivity.this, SecondActivity.class);  
  2. SecondActivity.this.startActivity(intent);</span></span>  
log打印情况如下:Android - Activity的启动模式依然是 getTaskId()的值保持不变,并且每一次跳转都在创建新的实例
singleTop:即使栈内有,只要栈顶没有就重新创建,栈顶有就不创建
现在更改SecondActivity的启动模式为singleTop,则log打印情况如下Android - Activity的启动模式
getTaskId()的值保持不变,栈顶有SecondActivity的实例时,就直接复用,并且onNewIntent()方法得到执行
singleTask :栈内唯一,且会将位于该实例之上的activity全部销毁现在更改SecondActivity中的跳转逻辑为:[java] view plain copy Android - Activity的启动模式Android - Activity的启动模式
  1. <span style="background-color: rgb(255, 255, 255);"><span style="font-family:Microsoft YaHei;font-size:14px;">Intent intent = new Intent(SecondActivity.this, FirstActivity.class);  
  2. SecondActivity.this.startActivity(intent);</span></span>  
并且更改FirstActivity的启动模式为singleTask ,则 log打印情况如下:Android - Activity的启动模式 可以看到,在从SecondActivity跳到FirstActivity时,SecondActivity被销毁了。
singleInstance:创建一个新的Task来存放activity如果一个activity的启动模式被设置为singleInstance,那么启动时,会为该activity单独创建一个Task。现在将SecondActivity的启动模式改为singleInstance,MainActivity和FirstActivity的启动模式改为standard,三个activity中的跳转逻辑改为:[java] view plain copy Android - Activity的启动模式Android - Activity的启动模式
  1. <span style="background-color: rgb(255, 255, 255);"><span style="font-family:Microsoft YaHei;font-size:14px;">//MainActivity:  
  2. Intent intent = new Intent(MainActivity.this, FirstActivity.class);  
  3. MainActivity.this.startActivity(intent);  
  4. //FirstActivity:  
  5. Intent intent = new Intent(FirstActivity.this, SecondActivity.class);  
  6. FirstActivity.this.startActivity(intent);  
  7. //SecondActivity:  
  8. Intent intent = new Intent(SecondActivity.this, MainActivity.class);  
  9. SecondActivity.this.startActivity(intent);</span></span>  
则 log打印情况如下:Android - Activity的启动模式并且在第一次跳转到SecondActivity时,查看打开的应用列表可以发现,名为launchmode的应用图标出现了两个,这两个图标分别对应着id为44和45的Task,随便点击一个进入到launchmode应用,这个时候可以发现,点击跳转或返回键的时候,activity的清除顺序已经由对应的Task所存储activity实例的顺序决定了,singleInstance这种启动模式适用于多个应用共享一个activity的情况。
总结一下:activity的四种启动模式解决了什么问题呢?答案是,在Task遵循后进先出的规则(即后打开的activity会处于Task的顶部,当点击返回键时,后打开的activity首先被清除出Task)的前提下,1、定义了要不要重新创建一个Task来存储新打开的activity。2、定义了新打开一个activity时,需不需要再创建一个实例(即使已经存在)。3、定义了新打开一个activity之后,任务栈Task里边activity实例的堆叠顺序,这将直接决定连续点击返回键时的退出顺序。那么,activity的四种启动模式没有解决的有什么问题呢?1、如果要用不同于 打开新activity的activity所在的那个Task  的Task来存储新打开的activity,那么,该用哪个Task?用已经存在的,还是重新创建一个?如果用已经存在的,那么用哪个?依据是什么?2、比如有一个activity A,我们已经在清单文件中规定了,它的启动模式为standard,目前任务栈中的堆叠顺序为:ABCD,现在的需求是,从D跳转到A,并且跳转到A之后,任务栈中的堆叠顺序为:A,即只留下A,这又怎么实现呢?当然,直接把BCD finish掉也可以,但一定会遇到其他的只靠启动模式不好解决的问题,即启动模式有些时候不够灵活。
由于activity的四种启动模式没有解决的这些问题,我们引入了以下两个概念:1、affinity 一个activity的affinity 用来标识它属于哪个Task,可以在清单文件中设置taskAffinity的值来改变,理论上拥有相同affinity 的activity同属于一个Task,如果不进行设置的话,都从application的affinity 继承而来,而application的affinity 默认为清单文件中的包名2、intent 的 flag就是在用Intent开启一个Activity时,在Intent中加入flag标志。如果同时设置了要打开的activity的启动模式,则flag的优先级更高。两种方式的差别在于,前者在于描述自己,声明自己应该以何种方式被加载,而后者则是主动声明要以何种方式加载一个Activity。最常用的几个flag为:FLAG_ACTIVITY_NEW_TASK:(下边文字摘自网络,谢谢作者!)当Intent对象包含这个标记时,系统会寻找或创建一个新的task来放置目标Activity,寻找时依据目标Activity的taskAffinity属性进行匹配,如果找到一个task的taskAffinity与之相同,就将目标Activity压入此task中,如果查找无果,则创建一个新的task,并将该task的taskAffinity设置为目标Activity的taskActivity,将目标Activity放置于此task。注意,如果同一个应用中Activity的taskAffinity都使用默认值或都设置相同值时,应用内的Activity之间的跳转使用这个标记是没有意义的,因为当前应用task就是目标Activity最好的宿主。

FLAG_ACTIVITY_SINGLE_TOP

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

FLAG_ACTIVITY_CLEAR_TOP

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

 FLAG_ACTIVITY_BROUGHT_TO_FRONT

这个网上很多人是这样写的。如果activity在task存在,拿到最顶端,不会启动新的Activity。这个有可能会误导大家! 他这个FLAG其实是这个意思! 比方说我现在有A,在A中启动B,此时在A中Intent中加上这个标记。此时B就是以  FLAG_ACTIVITY_BROUGHT_TO_FRONT 这个启动的,此时在B中再启动C,D(正常启动C,D),如果这个时候在D中再启动B,这个时候最后的栈的情况是 A,C,D,B. 特别注意的是,我上面说的网上人描述的这个FLAG,会很容易让人误解成这样,A,B,C,D都是标准加载,然后我在D中启动A,这个intent加上FLAG_ACTIVITY_BROUGHT_TO_FRONT  ,就会误认为变成B,C,D,A!!其实不是,这个时候应该是A,B,C,D,A.不信的人大家试试看。不过下面这个标记和这个标记就会让大家明白了!

FLAG_ACTIVITY_REORDER_TO_FRONT

就按在 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT 最后说的,如果在A,B,C,D正常启动的话,不管B有没有用FLAG_ACTIVITY_BROUGHT_TO_FRONT启动,此时在D中启动B的话,还是会变成A,C,D,B的。

FLAG_ACTIVITY_NO_HISTORY

用这个标记顾名思义! 意思就是说用这个FLAG启动的Activity,一旦推出,他就不会存在于栈中,比方说!原来是A,B,C 这个时候再C中以这个FLAG启动D的 , D再启动E,这个时候栈中情况为A,B,C,E。