##1.任务栈介绍
在Android中,任何Activity都被放在某一个任务栈中,Android通过任务栈来管理Activity的启动和关闭次序。例如:在关闭当前Activity后,系统可以根据任务栈中的记录来显示前一个Activity;通过启动模式和任务栈,可以判断是复用旧的Activity实例还是创建新的实例。
同一个任务栈中可以包含不同APP的Activity,任务栈更像是从用户使用角度来组织管理Activity的一种方式,从用户角度来看,他按顺序打开一堆APP的页面,这些页面互相跳转,他点back键返回,就回到上一个页面,用户并不关心这些页面在底层到底属于哪些APP。
虽然同一个任务栈中可以包含不同APP的Activity,但是这些Activity仍然运行在各自APP对应的进程中。Activity运行在哪个进程是一个维度,任务栈是另一个维度,任务栈主要针对的是这些Activity的使用流程顺序。
用户最初是从桌面(启动器页面)开始,点击启动某个应用,启动了其对应任务栈,然后各种启动、关闭、切换操作,实质上对应的是各种任务栈以及其中Activity的创建、销毁和切换。点击Home键时,切换到桌面,当前Activity切换到后台,并未被销毁。
从桌面打开一个应用时,会开启新的任务栈,默认与当前包名相同,把APP首个打开的Activity放入其中。
当从一个App使用Standard或SingleTop模式调用另一个APP的Activity时,并不会新建任务栈,而是会把Activity放入当前任务栈。
每当点击“Home”键时,就会切换到桌面Activity(启动器launcher Activity),这是系统提供的一个Activity,它独立占据着一个任务栈,效果比较像像单例模式。每次从桌面启动App都会打开新的任务栈,每次点击Home键都会跳转回桌面Activity,而原来的前台任务栈切换到后台。
##2.在ActivityManagerService中对应的管理机制
在Android中,实际是由ActivityManagerService(AMS)来实现和管理任务栈相关功能,它的真实实现逻辑在native层的。
每个Activity在AMS中都对应一个ActivityRecord,ActivityRecord就像是Activity的一个映像,记录着该Activity的信息,例如: 所在进程名称,App包名,所在任务栈的taskAffinity等。
一个TaskRecord对应一个任务栈,管理一组ActivityRecord,它是栈式结构,后进先出。AMS中记录着所有TaskRecord的最近使用顺序,而TaskRecord中又记录着Activity的入栈顺序。
当前正在显示的Activity所在的任务栈是前台任务栈,其它都是后台任务栈。默认情况下,每次点击back键,此刻的前台任务栈会弹出栈顶Activity,恢复显示下面的Activity,当一个任务栈为空时,就会被系统回收。系统按照最近使用顺序,将前一个TaskRecord对应的任务栈切换到前台。切换任务栈前后台时,会更改这些TaskRecord的最近使用顺序,从而影响Activity回退时的顺序。
*补充:AMS中任务栈相应的数据结构说明:
AMS中ActivityStackSupervisor管理多个ActivityStack,一个ActivityStack管理多个任务栈(TaskRecord),一个任务栈管理多个Activity的信息(ActivityRecord)。
其中ActivityStack名字有欺骗性,它并不是栈式结构的,内部是用列表来组织TaskRecord,当任务栈前后台切换时,列表中的顺序可能发生变化。APP与ActivityStack之间并没有确定的对应关系,有可能一对多,也有可能多对一,不同的系统版本上不同。
可以用adb shell dumpsys activity activities命令,来查看一个调试中的Android手机中的当前任务栈信息。
##3.Activity的四种启动模式:
通过Activity的启动模式,可以充分地复用Activity实例。
1.标准模式(Standard):
每次都重新创建一个实例,放到目标任务栈的栈顶;
2.单顶模式(SingleTop):
若目标任务栈的栈顶已经是该Activity的实例A,则直接跳转到A,不再创建新的实例。跳转到A时,会触发onNewIntent()回调方法。
即,当前任务栈的顶部只会有一个该Activity的实例。
3.单栈模式(SingleTask):
若目标任务栈中已存在该Activity的实例A,则直接跳转到A,此过程中任务栈中A上面的Activity都会出栈,最后A位于栈顶,同样,会触发A的onNewIntent()回调方法。若当前任务栈中没有A的实例,则会新创建一个任务栈,放在栈顶。
但特殊情况是,若在AndroidManifest.xml中通过为Activity设置taskAffinity属性指定了目标任务栈,且与当前任务栈不同,则新创建的实例会放到目标任务栈中。
即,目标任务栈中只会有一个当前Activity的实例。
4.单例模式(SingleInstance):
若系统中已经存在该Activity的实例A,则直接跳转到A,触发其onNewIntent()方法;否则创建该Activity的实例B,放到一个单独的新任务栈中,并且跳转到实例B。
即,整个系统中只会有一个当前Activity的实例,它位于一个单独的任务栈中。
(新的任务栈中只会包含B一个Activity,当从B以Standard或SingleTop启动别的Activity实例C时,也不会把新的Activity放入该任务栈的栈顶,而是会新创建一个任务栈把C放入其中。)
##4.Activity的目标任务栈:
Activity会被放入哪个任务栈?
每个任务栈都有对应的taskAffinity值,可以翻译为任务相关性,该值等于栈底Activity的taskAffinity值。
Activity在AndroidManifest.xml中可以配置taskAffinity属性值,若不配置,则默认与App包名相同。这个taskAffinity值决定了Activity的意向放入的任务栈,但并非一定会放入taskAffinity值相同的任务栈,还要根据具体的情况来看:
1.当Activity以SingleInstance模式启动时,会新建一个任务栈,并将Activity放入其中。
(通过adb shell dumpsys activity activities指令查看任务栈结构发现,这个任务栈的taskAffinity与该Activity的taskAffinity相同,当然有可能就是包名本身。但因为该栈只能放这一个Activity,所以只会有一个栈的taskAffinity与App包名相同能放多个Activity。)
排除掉情况1,以下都是非SingleInstance模式启动时的情况:
2. 当Activity以SingleTask或带有FLAG_ACTIVITY_NEW_TASK标记启动时,会被放入taskAffinity值相同的任务栈。
(系统会根据taskAffinity值来寻找有没有可放入的目标任务栈,没有该任务栈就创建新的。)
3.当从桌面首次打开APP时,若App中的Activity之前没有被其它APP调用打开过,也会被放入taskAffinity值相同的任务栈。
当调用方Activity是以SingleInstance模式启动时,因为当前任务栈只会放着唯一的Activity,故新启动的Activity 也会被放入taskAffinity值相同的任务栈。(实际验证,例如:当以默认包名为taskAffinity,会被放到默认包名任务栈中。)
(相当去从调用方Activity所在栈来划分的情况)
4.其它情况下,Activity会被放入调用方Activity当前所在的任务栈。例如从App A调用App B的Activity时。
5.特殊情况,android:allowTaskReparenting=“true”时,该属性就像其字面意思一样,允许Activity回归其本来意向的任务栈。
主要使用场景:App A调用App B的Activity C,若非SingleInstance模式启动C,则C会放入App A的任务栈。如果Activity C配置了allowTaskReparenting=“true”,此时回到桌面,点击App B的图标启动B的任务栈时,系统会将C从App A的任务栈移动到App B的任务栈,此时会显示Activity C,而非App B原本应启动的初始页面Activity。
<activity
... ... ... ...
android:taskAffinity=“per.sample.task” //如果设置该参数,则会在指定的任务栈中启动。
默认不用设置,运行在包名对应的任务栈中。
必须配合singleTask、FALG_ACTIVITY_NEW_TASK、allowTaskReparenting
至少之一使用才有效
android:allowTaskReparenting=“true”
... ... ... ...
</activity>
##5.补充:
1.通过Java代码和AndroidManfest.xml都可以设置一个Activity启动时的启动模式。若二者同时存在,则在执行中第二种方式的优先级更高。
但注意,java代码模式无法设置SingleTask、SingleInstance模式,xml方式无法设置出FLAG_ACTIVITY_CLEAR_TOP等一些标记位对等的效果。
xml中设置方法:android:launchMode=“xxx"
Java中设置方法:Intent.setFlags(xxx|xxx);
个人的理解:
AndroidManfest.xml静态设置的参数,与Activity绑定,表示每次都以什么方式启动;而java代码中设置Intent标记位参数只跟“本次启动操作”绑定。
所以xml可以设置任意启动模式,而java方式无法设置SingleTask、SingleInstance模式,因为xml方式设置成任何模式,每次启动都是自洽的; 而java方式同一个Activity的不同实例启动时,所用的启动模式有可能不同,使用SingleInstance、SingleTask模式启动时可能与其它模式启动时产生冲突。
2.SingleTop模式只是去检测栈顶是否有相应实例,并做相应操作。
3.当启动Activity时,若创建了新的A实例,则触发onCreate()->onStart()流程;若未创建新的A实例,而是SingleTop、SingleTask、SingleInstance等模式,则触发onNewIntent()->onStart()流程。
4.java代码中设置FLAG_ACTIVITY_NEW_TASK,作用并不等同xml中设置android:lanchMode=singleTask,实际运行的时候发现单独设置该标记位,什么特殊作用都没发生。
当设置FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP时,作用才类似xml中设置singleTask。不过这种方式不能保证一个栈中只有一个目标示例,它只是在栈中找到最上面的那个实例,并把上面的其它Activity实例都弹出。
在xml中设置android:lanchMode=singleTask, 才能保证栈中单实例,因为这样每次启动该Activity时,都是以singleTask方式启动的。
5.点击back键回退,关闭Activity A恢复Activity B时,生命周期流程为:
A:onPause() -> B:onRestart() -> B: onStart() -> B: onResume() -> A : onStop()-> A : onDestroy()
(根据前面所讲可以很容易理解)
##6.Activity启动时常用标记位(Flags)
启动Activity时,在Intent中可添加的标记位种类很多。
常用标记位:
1.FLAG_ACTIVITY_SINGLE_TOP:
指定为SingleTop模式,该标记位的效果与单顶模式相同。
2.FALG_ACTIVITY_NEW_TASK:
一般配合android:taskAffinity属性一起用,把Activity放入taskAffinity对应的任务栈中,若不存在对应任务栈,则创建新的任务栈。
单独使用FALG_ACTIVITY_NEW_TASK,不会有什么效果。
3.FLAG_ACTIVITY_CLEAR_TOP:
若任务栈中尚无Activity A的实例,不会有特别处理,只是放到顶部;
若任务栈中已存在Activity A的实例,当A不位于栈顶时,弹出A所属任务栈中A之上的所有Activity,触发onNewIntent();
当A位于栈顶时,分两种情况:若以默认方式启动A,则弹出A,创建一个新的实例放入栈顶,所以会触发onCreate();若附加FLAG_ACTIVITY_SINGLE_TOP标记位来启动A(可理解为以单顶模式启动A),则A不弹出,触发onNewIntent()方法。
另外,SingleTask的Activity实例,默认是带有FLAG_ACTIVITY_CLEAR_TOP标记位的。
4.FLAG_ACTIVITY_EXCLUDE_FROM_RECENT:
带有该标记位的Activity,不会显示在历史记录列表中(就是点击底部菜单键弹出的那一堆选择列表)。