android中activity设置标志FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_CLEAR_TOP详解和taskAffinity引发的有趣问题

来源:互联网 发布:恒生指数行情软件 编辑:程序博客网 时间:2024/04/30 23:26

今天解决一个比较有意思的问题

问题描述,假设有一个application中包含两个activity A和B,此时先打开A 然后按Home键退回到MainHome,在framework中回退mainHome是通过startActivity方式打开mainHome的(我们的android platform是自己定制的,对按键重新设计,可能和原生系统不一样),然后通过一个HotKey(就是一个外设的某一个按键)打开B,打开方式为StartActivity 设置两个Flag 分别是 FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK,此时奇怪的事情就发生了,当B被打开后,我们按back键回退,回退的地方竟然是A 而不是MainHome。如果我们换一种打开B的方式,不设置上面的两个Flag,那么从B就可以正常回退到MainHome,那么问题显然是出在了两个设置标志的地方上。然后翻阅文档和API。

将资料贴上:

FLAG_ACTIVITY_NEW_TASK:

一个Activity一般通过调用startActivity()启动并加入到Task中。它同调用者一样,进入同一个Task。
   然而,如果传递给startActivity()的Intent对象中包含FLAG_ACTIVITY_NEW_TASK时,系统会搜索一个新的Task来容纳新的Activity。
   通常,如标志的名字所示,是一个新的Task。然而,并不是必须是。如果已经存在一个Task与新Activity的affinity相同,这个Activity就会加入到那个Task中。如果不是,启动一个新的Task。
  如果启动它的acitve和新Activity的affinity相同,那么新Activity的会进入启动它的acitve所在的Task.

FLAG_ACTIVITY_CLEAR_TOP:

 如果设置,并且这个Activity已经在当前的Task中运行,因此,不再是重新启动一个这个Activity的实例,而是在这个Activity上方的所有Activity都将关闭,然后这个Intent会作为一个新的Intent投递到老的Activity(现在位于顶端)中。
    例如,假设一个Task中包含这些Activity:A,B,C,D。如果D调用了startActivity(),并且包含一个指向Activity B的Intent,那么,C和D都将结束,然后B接收到这个Intent,因此,目前stack的状况是:A,B。
    上例中正在运行的Activity B既可以在onNewIntent()中接收到这个新的Intent,也可以把自己关闭然后重新启动来接收这个Intent。如果它的启动模式声明为 “standard”(默认值),
    并且你没有在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,那么它将关闭然后重新创建;对于其它的启动模式,或者在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,都将把这个Intent投递到当前这个实例的onNewIntent()中。
    这个启动模式还可以与FLAG_ACTIVITY_NEW_TASK结合起来使用:用于启动一个Task中的根Activity,它会把那个Task中任何运行的实例带入前台,然后清除它直到根Activity。这非常有用,例如,当从Notification Manager处启动一个Activity

 

也就是说对我们上例启决定性作用的是FLAG_ACTIVITY_NEW_TASK这个flag.

原因是,当我们的打开B时,因为使用了new_task属性,所以系统就会为B寻找一个最具
affinity的activity(也就是A)如果找到了,系统会将此activity(也就是A)设置为前台状态,就是background,然后将B归并到A的Task中去,并且压入顶(如果B之前已经存在于A的Task中那么CLEAR_TOP属性将会把B之上得所有activity清除掉,然后将B压入栈顶).问题分析到这里,应该已经很清楚了,也就是说A和B一定具有相同的affinity,查阅文档发现,因为A和B存在于同一个.apk下也就是同一个package下,所以默认具有相同的affinity.下面贴上affinity的相关文档:

    什么是Affinity

在某些情况下,Android需要知道一个Activity属于哪个Task,即使它没有被启动到一个具体的Task里。这是通过任务共用性(Affinities)完成的。任务共用性(Affinities)为这个运行一个或多个Activity的Task提供了一个独特的静态名称,默认的一个活动的任务共用性(Affinity)是实现了该Activity的.apk包的名字。



当开始一个没有 Intent.FLAG_ACTIVITY_NEW_TASK标志的Activity时,任务共用性affinities不会影响将会运行该新活动的 Task:它总是运行在启动它的Task里。但是,如果使用了NEW_TASK标志,那么共用性(affinity)将被用来判断是否已经存在一个有相同共用性(affinity)的Task。如果是这样,这项Task将被切换到前面而新的Activity会启动于这个Task的顶层。



这种特性在您必须使用NEW_TASK标志的情况下最有用,尤其是从状态栏通知或桌面快捷方式启动活动时。结果是,当用户用这种方式启动您的应用程序时,它的当前Task将被切换到前台,而且想要查看的Activity被放在最上面。



你可以在程序清单(Manifest)文件的应用程序application标签中为.apk包中所有的活动分配你自己的任务共用性Affinites,或者在活动标记中为各个活动进行分配。

在前面的文章“Android四种Activity的加载模式”我们提到:Activity的加载模式受启动Activity的Intent对象中设置的Flag和manifest文件中Activity的<activity>元素的特性值交互控制。


跟 Task 有关的 manifest文件中Activity的特性值介绍

android:allowTaskReparenting
    用来标记Activity能否从启动的Task移动到有着affinity的Task(当这个Task进入到前台时)

  “true”,表示能移动,“false”,表示它必须呆在启动时呆在的那个Task里。

    如果这个特性没有被设定,设定到<application>元素上的allowTaskReparenting特性的值会应用到Activity上。默认值为“false”。

    一般来说,当Activity启动后,它就与启动它的Task关联,并且在那里耗尽它的整个生命周期。当当前的Task不再显示时,你可以使用这个特性来强制Activity移动到有着affinity的Task中。典型用法是:把一个应用程序的Activity移到另一个应用程序的主Task中。
    例如,如果 email中包含一个web页的链接,点击它就会启动一个Activity来显示这个页面。这个Activity是由Browser应用程序定义的,但是,现在它作为email Task的一部分。如果它重新宿主到Browser Task里,当Browser下一次进入到前台时,它就能被看见,并且,当email Task再次进入前台时,就看不到它了。

   Actvity的affinity是由taskAffinity特性定义的。Task的affinity是通过读取根Activity的affinity 决定。因此,根Activity总是位于相同affinity的Task里。由于启动模式为“singleTask”和“singleInstance”的Activity只能位于Task的底部,因此,重新宿主只能限于“standard”和“singleTop”模式。

android:alwaysRetainTaskState
    用来标记Activity所在的Task的状态是否总是由系统来保持。

    “true”,表示总是;“false”,表示在某种情形下允许系统恢复Task到它的初始化状态。默认值是“false”。

    这个特性只针对Task的根Activity有意义;对其它Activity来说,忽略之。
    一般来说,特定的情形如当用户从主画面重新选择这个Task时,系统会对这个Task进行清理(从stack中删除位于根Activity之上的所有Activivity)。典型的情况,当用户有一段时间没有访问这个Task时也会这么做,例如30分钟。
    然而,当这个特性设为“true”时,用户总是能回到这个Task的最新状态,无论他们是如何启动的。这非常有用,例如,像Browser应用程序,这里有很多的状态(例如多个打开的Tab),用户不想丢失这些状态。

       所以最终的解决方式就是为B或者A设置不同的affinity这样A 就不会被吸引到background状态了,而B 也会在另外的Task中打开。在B中按back键就会返回到MainHome界面。