Android之taskAffinity属性
栈包含了对象,如果一个栈有多于一个相同的Activity的子类的实例打开——比如,多个地图查看器——这个栈分别拥有每个实例的入口。栈中的activity不能重新排列,只能压入和弹出。任务是一些activity组成的栈,不是清单文件中的类或元素。所以没有办法在独立于它包含的activity的条件下,设置它的值。任务的值作为一个整体设置在根activity中。例如,下一节将讨论“任务的亲和性”;这个值是从根activity亲和性中读取出来的。
一个任务中的所有activity一起作为一个单元。整个任务(整个activity栈)可以移动到前台或者后台.假设,例如,当前的任务有四个 activity在栈中——三个在当前的activity之下。用户按下了HOME键,进入了应用程序加载器,选择了一个新的程序(实际上,是一个新的任务)。当前的任务进入了后台,新任务的根activity显示出来。然后,过了一会,用户退回到主界面,又重新选择了前一个应用程序(前一个任务),栈中有四个activity的那个任务,现在出现在了前台。当用户按下BACK按键,屏幕就不会再显示用户刚刚离开的那个activity,而是删除栈顶的 activity,同任务中的前一个activity将被显示出来。
刚才说明的那些行为,是activity和任务的默认行为。但是有也办法修改它的所有方面。activity和任务的关联,activity在任务中的行为,受控于启动activity的行为对象的标志位和清单文件中的<activity> 元素的属性的互相作用。请求者和相应着都要说明发生了什么。
在这里,主要的行为标志为是:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_ACTIVITY_SINGLE_TOP
主要的<activity> 属性是:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
下面一节将说明这些标志和属性都有什么用,他们之间怎么互相影响,应该用什么样的方案来控制它们的使用。
亲和性(taskAffinity)和新任务
默认情况下,应用程序中的所有activity,都有一个对于其它activity的亲和性—这是一个对于同一个任务中的其他activity的优先权,然后,通过 <activity>元素的 taskAffinity 属性可以分别为每一个activity设置亲和性。不同应用程序定义的activity可以共享同一个亲和性,或者同一个应用程序定义的 activity可以指定不同的亲和性。亲和性在两种情况下发挥作用:当行为对象启动了一个包含 FLAG_ACTIVITY_NEW_TASK标志的activity,和当一个activity的allowTaskReparenting 属性设置为“true”。
FLAG_ACTIVITY_NEW_TASK 标志
正如前面描述的,一个新的activity,默认情况下,被加载进调用startActivity()方法的activity对象所在的那个任务中。它被压入和调用者所在的同一个栈中,但是,如果行为对象在调用startActivity()方法时传递了FLAG_ACTIVITY_NEW_TASK标记,系统将用一个不同的任务来容纳这个新的activity。通常,就像这个标记的名字所代表的。它是一个新任务,但是,它不必非要这样。如果已经存在一个和这个activity亲和性相同的任务,这个activity就会载入到那个任务中,如果不是的话,才会启动新任务。
allowTaskReparenting 属性
如果activity的allowTaskReparenting 属性设置为“true”,它就能从他启动时所在的任务移动到另一个出现在前台的任务。例如,假设有一个activity可以根据选择的城市包括天气情况,它作为一个旅行应用程序的一部分。它和同一个应用程序中的其他activity有同样的亲和性(默认的亲和性)并且允许重组。你的一个activity开启了天气报告器,所以它属于同一个任务中的这个activity,然而,当旅行应用程序开始运行时,天气报告器将被重新分配并显示到那个任务中。
启动模式
有4中不同的启动模式可以分配给 <activity> 元素的 launchMode 属性。
"standard" (默认的模式)
"singleTop "
"singleTask"
"singleInstance"
1、standard :系统的默认模式,一次跳转即会生成一个新的实例。假设有一个activity命名为Act1,执行语句:
startActivity(new Intent(Act1.this, Act1.class));
后Act1将跳转到另外一个Act1,也就是现在的栈里面有 Act1 的两个实例。按返回键后你会发现仍然是在Act1(第一个)
2、singleTop:singleTop 跟standard 模式比较类似。唯一的区别就是,当跳转的对象是位于栈顶的activity(应该可以理解为用户眼前所 看到的activity)时,程序将不会生成一个新的activity实例,而是直接跳到现存于栈顶的那个activity实例。拿上面的例子来说,当Act1 为 singleTop 模式时,执行跳转后栈里面依旧只有一个实例,如果现在按返回键程序将直接退出。这个貌似用得比较少。
3、singleTask: singleTask模式和后面的singleInstance模式都是只创建一个实例的。在这种模式下,无论跳转的对象是不是位于栈顶的activity,程序都不会生成一个新的实例(当然前提是栈里面已经有这个实例)。
4、singleInstance: 可以在这个单一的Task栈中,放入别人的Activity,比如google地图,这样用户看过地图按回退键的时候,会退栈回到调用地图的Activity。对用户来说,并不觉得在操作多个应用。这就是Task的作用。但是,有这样的需求,多个Task共享一个Activity(singleTask是在一个task中共享一个Activity)。
现成的例子是google地图。比如我有一个应用是导游方面的,其中调用的google地图Activity。那么现在我比如按home键,然后到应用列表中打开google地图,你会发现显示的就是刚才的地图,实际上是同一个Activity。
如果使用上面三种模式,是无法实现这个需求的。google地图应用中有多个上下文Activity,比如路线查询等的,导游应用也有一些上下文Activity。在各自应用中回退要回退到各自的上下文Activity中。singleInstance模式解决了这个问题(绕了这么半天才说到正题)。让这个模式下的Activity单独在一个task栈中。这个栈只有一个Activity。导游应用和google地图应用发送的intent都由这个Activity接收和展示。
这里又有两个问题:
1)如果是这种情况,多个task栈也可以看作一个应用。比如导游应用启动地图Activity,实际上是在导游应用task栈之上 singleInstance模式创建的(如果还没有的话,如果有就是直接显示它)一个新栈,当这个栈里面的唯一Activity,地图Activity 回退的时候,只是把这个栈移开了,这样就看到导游应用刚才的Activity了;
2)多个应用(Task)共享一个Activity要求这些应用都没有退出,比如刚才强调要用home键从导游应用切换到地图应用。因为,如果退出导游应用,而这时也地图应用并未运行的话,那个单独的地图Activity(task)也会退出了。
“singleInstance”独占一个task,其它activity不能存在那个task里;如果它启动了一个新的activity,不管新的activity的launch mode 如何,新的activity都将会到别的task里运行(如同加了FLAG_ACTIVITY_NEW_TASK参数)。 而另外三种模式,则可以和其它activity共存。
Activity的归属,也就是Activity应该在哪个Task中,Activity与Task的吸附关系。我们知道,一般情况下在同一个应用中,启动的Activity都在同一个Task中,它们在该Task中度过自己的生命周期,这些Activity是从一而终的好榜样。
那么为什么我们创建的Activity会进入这个Task中?它们会转到其它的Task中吗?如果转到其它的Task中,它们会到什么样的Task中去?
解决这些问题的关键,在于每个Activity的taskAffinity属性。
每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该 Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果 Application也没有指明,那么该taskAffinity的值就等于包名。而Task也有自己的affinity属性,它的值等于它的根 Activity的taskAffinity的值。
taskAffinity的值是个唯一的String字符串,来自于manifest文件中声明的包名。
官方文档中说了如果一个singleTask的Activity没有已存在的实例,就会新建一个task来存这个activity的实例,而写程序测试的例子是标准的ActivityA启动singleTask的ActivityB,你会发现他们的TaskId是一样的,也就是说创建ActivityB后,并没有把它放到新的task中。如果结合affinity介绍,就好理解一些了:
App1(com.df.test1): ActivityA -> ActivityB -> ActivityC -> ActivityB App2(com.df.test2): Activity1 -> ActivityB(调用App1中的ActivityB)
1、假设ActivityB设置了launchMode为"singleTask", taskAffinity没有设置(即为包名),
(1.1)在App1中它被启动ActivityB时,先去查找有没有与它的taskAffinity匹配的task存在,因为ActivityA与ActivityB的taskAffinity相同,所以会在当前的task中启动ActivityB,此时再启动ActivityC,然后再启动ActivityB,按“Back",会发现回到了ActivityA,即ActivityC启动ActivityB时,在栈中查到了AcvitityB,让它上面的都出栈并激活ActivityB,它们打印出的taskId都一样,这个跟上面两篇文章介绍的一致。
(1.2)在App2中启动ActivityB时:
如果App1没有在后台运行,此时查找与ActivityB的taskAffinity相同的task,发现不存在,会新建一个task启动ActivityB,此时你会发现它们的taskId值不一样;
如果App1在后台运行,系统查找到与ActivityB相同taskAffinity的task,会在这个task中启动(如果App1中栈顶为ActivityC,它会被出栈并激活ActivityB, 如果栈顶为ActivityA,会新建ActivityB),此时,进入顺序为Activity1->ActivityB Back顺序为ActivityB->ActivityA->Activity1.(先退出原来的栈,再回到原本的task)
由此基本可以得出一个结论:back只能在同一个task栈里面回退,只有当这个栈里面没有activity时,才会跳到另外一个栈里面,这样就类似于弹出了一个task栈。
(1.3)如果App2在后台运行,栈顶为ActivityB,运行App1,你会发现App1直接进入了ActivityB,按Back,直接退出,再激活App2,发现当前界面为Activity1,这个看起来就有些奇怪。
2、假设ActivityB设置了launchMode为"singleTask",taskAffinity设置为com.df.test2(与App2相同) (2.1)在App1中启动ActivityB时,
如果App2没有在后台运行,此时系统查找不到与ActivityB的taskAffinity相同的task,会创建新的task并在其中启动ActivityB,此时,会发现ActivityB的taskId与ActivityA不一样,但与ActivityC的taskId是一样的,因为是它启动的ActivityC,而ActivityC的launchMode又为默认的,此时它的taskAffinity跟task中的Root Activity相同。
如果App2已经在后台运行,系统查找到与ActivityB相同的task,如果ActivityB已在这个task中,直接激活,如果没有,就在这个task中启动ActivityB. 此时,进入顺序:ActivityA -> AcitivytB back顺序:ActivityB->Activity1->ActivityA
(2.2)在App2中启动ActivityB,会发现它的taskId与Activity1是一样的。
(2.3)如果App1已在后台运行,而且ActivityB已经在栈中,此时运行App2,发现会直接进行ActivitB或ActivityC,在ActivityB界面按Back,直接退出App2.

浙公网安备 33010602011771号