Activity任务栈与启动模式

一、任务与任务栈的相关概念

  • 任务:任务是指一系列Activity的集合
  • 任务栈(返回栈):任务中的一系列Activity是以栈(一种后进先出的数据结构)的结构排列的,这个栈就被称为任务栈或者返回栈

通常情况下,我们从Launcher(桌面)点击一个应用图标启动一个app,系统就会为我们创建一个任务栈,这个任务栈的名字默认情况下就是app的包名,当然也可以自己指定,然后将那个被配置为应用入口的Activity比如叫MainActivity实例放入栈中第一个位置。然后从MainActivity启动一个新的Activity(SecondActivity),就会把SecondActivity的实例压入栈中,放在栈中第二个位置也就是MainActivity的上面,这个时候MainActivity并没有被回收,只是处于停止状态。如果用户按下返回键,SecondActivity会被从栈中弹出,并且MainActivity恢复为运行状态,也就是后进先出。用户继续按下返回键,MainActivity也会被从栈中弹出,并且因为这个栈中没有内容了,也将会被销毁,界面上也会回到Home界面。过程示意图如下:
默认情况下任务栈的工作流程
现在有这么一种情况,分别有两款应APP_M和APP_N,其中APP_M中有Activity_A,Activity_B,两个Activity,应用APP_N中有Activity_C,Activity_D两个Activity。首先从Launcher中启动APP_M,Activity_A作为APP_M入口被创建并放入一个栈中,这个栈就叫Stack_M吧。然后从Activity_A中启动Activity_B,Activity_B会被放入Stack_M中,在Activity_A之上。按下Home键,回到Launcher界面,启动APP_N,Activity_C作为APP_N的入口被创建并放入一个新的栈中,这个栈就叫Stack_N吧。这个时候Stack_M将会被转入后台,不过其中的Activity都保持了原有的ui状态,只不过处于停止状态,Stack_N处于前台状态,再从Activity_C启动Activity_D,Activity_D会被放入Stack_N中,在Activity_C之上。通过Home键回到Launcher界面,点击APP_M的启动图标,Stack_M会重新回到前台,Stack_N会进入后台状态,这个时候正在显示的界面就是Activity_B。可以通过adb shell dumpsys activity activities查看相关任务栈的状态。

我们改变一下上面的情况,不通过Launcher启动APP_N,而是从Activity_B启动Activity_C,再从Activity_C启动Activity_D。通过adb命令我们可以得到如下的信息

TaskRecord{e677d21 #38 A=com.example.applicationm U=0 StackId=39 sz=4}                                                   
 Run #3: ActivityRecord{c15030d u0 com.example.applicationn/.ActivityD t38}                                              
 Run #2: ActivityRecord{5e24f6b u0 com.example.applicationn/.ActivityC t38}                                             
 Run #1: ActivityRecord{a06a0ac u0 com.example.applicationm/.ActivityB t38}                                             
 Run #0: ActivityRecord{76dd9aa u0 com.example.applicationm/.ActivityA t38}  

结果发现只有Stack_M被创建了,Activity_C和Activity_D都在Stack_M中,从这里我们可以看出,在默认情况下,一个Activity所在的任务栈默认情况下就是启动它的那个Activity所在的任务栈。

二、Activity的启动模式

Activity有多种启动模式,不同的启动模式Activity在返回栈的表现就不一样。启动模式可以通过两种模式设置:

  • manifest方式:在manifest文件中的标签中有一个“launchMode”属性,值是字符串类型。
  • Intent Flag方式:在通过intent启动activity是,给intent设置相关的flag,也可以改变被启动的activity的启动模式,并且这种方式的优先级比通过manifest这种方式的优先级更高。

启动模式总共有四种,分别如下:

  • standard:标准模式,这也是系统默认的模式。每次启动一个Activity都会重新创建一个新的实例,哪怕它们属于同一个Activity类,它所在的任务栈就是启动它的那个Activity所在的栈。
    标准模式下任务栈的情况
  • singleTop:栈顶复用模式。再这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会创建新的实例,同时它的onNewIntent()方法会被回调。如果新的Activity实例已存在,但是不位于栈顶,那么依然会重新创建新的Activity实例并放入栈顶。下面有个特殊案例: 现有一个任务栈M,里面包含AB两个Activity,A位于栈底,B位于栈顶,其中B的启动模式位singleTop。另有一个任务栈N,里面包含CD两个Activity,C位于栈底,D位于栈顶。从D启动B,尽管B的启动模式是singleTop并且位于栈顶,但是这个B所在的栈不是N,在N栈中又没有存在B,那么就会创建新的B的实例,和standard模式一样,新的B的实例所在的栈就是启动它的那个Activity所在的栈。所以现在的情况就是,M栈中依然是AB两个Activity,A位于栈底,B位于栈顶;N中有CDB三个Activity,C位于栈底,B位于栈顶。从这里我们可以知道无论是singleTop还是standard,Activity所在的栈就是启动它的那个Activity所在的栈。
  • singleTask:栈内复用模式。这是一种单实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会创建新的实例,只会回调onNewIntent()方法。下面有几个例子:
  1. 目前任务栈S1中有ABC三个Activity,其中A位于栈底,C位于栈顶。这个时候Activity D以singleTask模式请求启动,其所需要的任务栈是S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例并压入栈S2中。
  2. 目前任务栈S1中有ADBC三个Activity,其中A位于栈底,C位于栈顶。这个时候Activity D以singleTask模式请求启动,其所需要的任务栈是S1,由于任务栈存在且其中存在D的实例,系统会把D上面的Activity都弹出栈,并回调D的onNewIntent()方法。
  3. 目前存在栈S1中有AB两个Activity,其中A位于栈底,B位于栈顶。存在栈S2中有CD两个Activity,其中C位于栈底,D位于栈顶,并且D是singleTask模式。现在从B启动D,由于D所需要的栈S2存在并且启动存在D的实例并且位于栈顶,所以不会创建新的D的实例,只是S2任务栈将会变为前台状态,S1会转位后台状态。这时候连续按返回键,界面将会按照D->C->B->A->Home转换,在C被销毁后及S2被销毁后,S1会转为前台状态。所以猜测任务栈本身也是以栈这种后进先出的数据数据结构存储的,不过在Home界面有点特殊,这个时候按返回键不会执行弹出栈的操作。
  • singleInstance:单实例模式。这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性以外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中。比如,Activity A 是singleTask模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,重复启动A也不会创建新的实例。一个特殊例子:任务栈S1中有A一个Activity,然后从A启动一个启动模式为singleInstance的Activity B,然后再从B启动一个standard Activity C,A,C所需要的任务栈都为S1,通过adb命令可以发现,Activity A,C位于同一个栈S1中,B位于一个单独的任务栈中。所以得出结论,从一个启动模式为singleInstance的Activity A启动一个新的activity B,B不会被压入A所在的栈中,如果B所需要的栈存在,就创建B的实例并压入那个栈中,如果不存在,则创建新的栈,并创建B的实例压入新的栈中。

三、通过Intent Flag启动Activity

通过startActivity(Intent intent)启动一个Activity时,可以通过intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)这个方法指定不同的flag,其中有几个flag可以影响Activity的启动模式。

  • FLAG_ACTIVITY_NEW_TASK: 这个标记位的作用是为Activity指定"singleTask"启动模式,其效果与在manifest文件中指定singleTask模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP: 这个标记位的作用是为Activity指定"singleTop"启动模式,其效果与在manifest文件中指定singleTop模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP: 具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的Activity都要出栈。intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);通过这种方式联用时,其效果和singleTask启动模式一样。如果被启动的Activity采用的standard模式,那么这个Activity本身和它之上的Activity都会出栈,并创新这个Activity新的实例压入栈中。

四、其他与任务栈相关的属性

  1. 任务栈归属的相关属性
  • taskAffinity:前面经常提到一个Activity所需要的任务栈,那么它所需要的任务栈是怎么定义的呢,其实就是manifest文件中<activity>标签的taskAffinity属性,它指定这个activity所需任务栈的名字,系统通过它来标识对应的任务栈,所以为了避免重复,如果需要自定义通常是由包名加上其他字符串组成。默认<activity>标签的taskAffinity属性继承自application标签,而application标签的taskAffinity属性默认值就是包名。
  • allowTaskReparenting:官方文档上描述的意思大概是这样的,当一个应用A启动了应用B的某个Activity C后,那么应用B启动后,由于默认情况下应用B中所有Activity的taskAffinity都一样,所以C所需要的任务栈已经启动,如果这个Activity的allowTaskReparenting属性为true的话,C就会移动到B应用所在的任务栈中。不过经过测试好像没有这种情况发生,懵逼!
  1. 清除任务栈的相关属性
    如果一个用户离开一个任务栈比较长的时间,系统会清除栈中根Activity(栈中最下面的那个Activity)以外的Activity,当用户重新回到这个任务栈的时候,只有根Activity得以显示。通过以下属性可以改变这一默认行为:
  • alwaysRetainTaskState:如果栈中的根Activity这个属性被设置为true,那么上述行为就不会发生,重回任务栈的时候,任务栈中的所有Activity都还存在。
  • clearTaskOnLaunch:如果栈中的根Activity这个属性设置为true,即使用户离开这个栈很短一段时间,上述行为也会发生,只有根Activity得以恢复。
  • finishOnTaskLaunch:这个属性只对被设置了这个属性的Activity有效,不会波及到整个任务栈,如果这个属性设置为true,跟clearTaskOnLaunch有点类似,即使用户离开这个栈很短一段时间,finishOnTaskLaunch为true的Activity就会被弹出栈。虽然官方文档说这个属性对根Activity也有效,然后经过测试发现仅对非根Activity有效。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章