Android 组件系列 -- Activity 栈、taskAffinity、intent/flag

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cjh_android/article/details/82024029
    本章知识点
  • Activity栈 简介
  • Activity栈 和 taskAffinity 之间的关系
  • intent/flag

Activity栈

  • task 是一个具有栈结构(先进后出)的容器,它是一组 Activities 的集合,一组Activities被Stack(back stack)所管理,栈中 Activity 的顺序就是按照它们被打开的顺序依次存放的。
    • 栈是一种抽象的概念。
    • 正如老罗在他的文章Android应用程序内部启动Activity过程(startActivity)的源代码分析最后所提到的:既不是我们在Linux系统中所理解的进程(Process),也不是线程(Thread),它是用户为了完成某个目标而需要执行的一系列操作的过程的一种抽象。这是一个非常重要的概念,它从用户体验的角度出发,界定了应用程序的边界,极大地方便了开发者利用现成的组件(Activity)来搭建自己的应用程序,就像搭积木一样,而且,它还为应用程序屏蔽了底层的进程,即一个任务中的Activity可以都是运行在同一个进程中,也中可以运行在不同的进程中。
    • 脱离 Activity 来谈栈,是毫无意义的。

场景一 : 从App尚未打开,打开现实 A Activity,从 A 跳转至 B,然后按回退键至退出 App。

    如下图所示:
  • 普通启动模式下,所有的Activity都在一个栈内。
  • 正如之前描述的那样,栈的存储结构是先进后出,当所有的Activity都销毁,栈自然也就不存在了。


场景二 : 从App尚未打开,打开现实 A Activity,从 A 跳转至 B(与场景一不同的是 B 的启动模式是 singleInstance),从 B 跳转 C ,然后按回退键至退出 App。
如下图所示:

  • 特殊的启动模式下,并不是所有的Activity都在一个栈内,图中就存在两个栈。
  • Android系统对于栈的行为,也是一个一个退出,就如图中,尽管是 A->B->C的跳转顺序,但是在回退时,是先退出C所在的栈后,再退出B所在的栈:C->A->B。


  • taskAffinity

      taskAffinity 是 Activity 的一个重要的属性:
    • taskAffinity表示一个 task,这个任务就是当前 activity 所在的 task
    • 在概念上,具有相同的 affinity 的 activity(即设置了相同 taskAffinity 属性的 activity)属于同一个 task
    • 一个 task 的 affinity 决定于这个 task 的 根 activity(root activity)的 taskAffinity
    • 默认情况下,一个应用中的所有activity具有相同的taskAffinity,即应用程序的包名。我们可以通过设置不同的taskAffinity属性给应用中的activity分组,也可以把不同的应用中的activity的taskAffinity设置成相同的值。
    • 该属性只对 singleTask 模式下的 Activity 起作用

      • 在前面介绍启动模式的文章中 Android 组件系列 – Activity 启动模式,限于当时 taskAffinity 和 task 的知识点还没有介绍到,仅仅只介绍了 singleTask 栈内复用的特性。
          事实上 singleTask 的真实作用包含了两个
        • 同一栈内复用(也是唯一)
        • 将该 Activity 设置为可以在新 task 中启动,至于是否启动新的 task,还需要看 taskAffinity 属性的值。
        singleTask(通过对singleTask模式的完整描述,来认识taskAffinity):
          • 当启动一个singleTask模式下的Activity时,系统会先判断当前Activity是否有单独设置过taskAffinity,
          • 如果没有设置过,那么会使用默认的taskAffinity(包名);
          • 如果设置过了就会按照单独设置的taskAffinity为准。
          • 但是不管是默认的taskAffinity,还是特殊的taskAffinity,系统都需要去判断拥有该taskAffinity的栈是否已经存在了:
          • 如果存在,那么就会判断该Activity是否在栈内已经实例化,如果存在就清除该Activity上所有Activity,调用该Activity的onNewIntent方法,并显示在栈顶;如果不存在该Activity的实例,就创建一个新的Activity实例入栈。
          • 如果不存在该taskAffinity的栈,那么就会为新创建的该Activity的实例单独新开一个栈。

        intent/flag

        intent/flag其实可以理解为Android系统提供的额外的启动模式,可以组合,可以拆分。与四大启动模式相比,更全面,更自由。
        • 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_CLEAR_TASK
          如果Intent中设置了这个标志,会导致含有待启动Activity的Task在Activity被启动前清空。也就是说,这个Activity会成为一个新的root,并且所有旧的activity都被finish掉。这个标志只能与FLAG_ACTIVITY_NEW_TASK 一起使用。
        • FLAG_ACTIVITY_CLEAR_TOP
          当Intent对象包含这个标记时,如果在栈中发现存在Activity实例,则清空这个实例之上的Activity,使其处于栈顶。例如:我们的FirstActivity跳转到SecondActivity,SecondActivity跳转到ThirdActivity,而ThirdActivity又跳到SecondActivity,那么ThirdActivity实例将被弹出栈,使SecondActivity处于栈顶,显示到幕前,栈内只剩下FirstActivity和SecondActivity。这个SecondActivity既可以在onNewIntent()中接收到传来的Intent,也可以把自己销毁之后重新启动来接受这个Intent。在使用默认的“standard”启动模式下,如果没有在Intent使用到FLAG_ACTIVITY_SINGLE_TOP标记,那么它将关闭后重建,如果使用了这个FLAG_ACTIVITY_SINGLE_TOP标记,则会使用已存在的实例;对于其他启动模式,无需再使用FLAG_ACTIVITY_SINGLE_TOP,它都将使用已存在的实例,Intent会被传递到这个实例的onNewIntent()中。
        • FLAG_ACTIVITY_SINGLE_TOP
          当task中存在目标Activity实例并且位于栈的顶端时,不再创建一个新的,直接利用这个实例。
        • FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
          如果一个Intent中包含此属性,则它转向的那个Activity以及在那个Activity其上的所有Activity都会在task重置时被清除出task(前提:FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)。当我们将一个后台的task重新回到前台时,系统会在特定情况下为这个动作附带一个FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,意味着必要时重置task,这时FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就会生效。经过测试发现,对于一个处于后台的应用,如果在主选单点击应用,这个动作中含有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,长按Home键,然后点击最近记录,这个动作不含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,所以前者会清除,后者不会。
        • FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
          如果一个Intent中包含此属性,则它转向的那个Activity以及在那个Activity其上的所有Activity都会在task重置时被清除出task(前提:FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)。当我们将一个后台的task重新回到前台时,系统会在特定情况下为这个动作附带一个FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,意味着必要时重置task,这时FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET就会生效。经过测试发现,对于一个处于后台的应用,如果在主选单点击应用,这个动作中含有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,长按Home键,然后点击最近记录,这个动作不含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,所以前者会清除,后者不会。(如下图,从网上抄来的)


        • FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
          这个标记在以下情况下会生效:1.启动Activity时创建新的task来放置Activity实例;2.已存在的task被放置于前台。系统会根据affinity对指定的task进行重置操作,task会压入某些Activity实例或移除某些Activity实例。我们结合上面的CLEAR_WHEN_TASK_RESET可以加深理解。
        • FLAG_ACTIVITY_NO_HISTORY
          定义:如果设置,新的Activity将不再历史stack中保留。用户一离开它,这个Activity就关闭了。这也可以通过设置noHistory特性。
            事实上以上的定义,是有歧义的,我在很多文章中都看到过这个说法。关于 不再历史stack中保留用户一离开它,这个Activity就关闭了 这两句话:
            场景: A -> B(FLAG_ACTIVITY_NO_HISTORY)-> C
            事实上,在C显示到用户眼前的时候,栈里面Activity的数量(默认都是在同一个栈内)依然是3,并不是所谓的 A->C,而是 A->B->C;而当用户离开C时,此时栈会在回收C的同时,将B也回收,用户会直接看到A,栈内Activity的数量会直接变为1。


        到此,Intent Flag 相关知识就告一段落了,不出意外的话,以后会不间断的补充 Flag,对于不常用的 Flag,我觉得没有必要清楚记得,只要有个印像,就算没印像,查一下就好。
        彩蛋
          Intent.FLAG_ACTIVITY_NEW_TASK 这个属性除了可以启动新的 Task,还有一个隐晦的特性:
          应用的启动 Task 我记录为 Task1,其中有 Activity A->B->C;
          由TaskA所启动的新栈为 Task2,Task2中有 Activity H->I->J,此时处于栈顶的是Task2;
        • 在ActivityJ中:
          startActivity(Intent(this,ActivityA::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
          那么此时 Task1 被调度到前台,Task2转到后台,并且此时的 Task1 中的 Activity : A->B->C->A。
          而如果此时你从 Task1 中的 ActivityA 中调用如下代码:
          startActivity(Intent(this, ActivityH::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
          Task 的转换不会有什么问题:自然是 Task1 被调度到后台,Task2 再次转到前台;
          但是此时你会发现此时的Task2中,Activity 依然是 H->I->J,并没有因为 startActivity 而变成 H->I->J->H;
          而如果我改一下之前的跳转逻辑,我设置 Task2 的跳转依然会将 Task1 转到前台:
          startActivity(Intent(this, ActivityD::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
          其中 ActivityD 的 affinity 与Task1 一致,并且D 会跳转到 I:
          startActivity(Intent(this, ActivityI::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
          那么此时 Task2 再次转到前台,而且栈中 Activity 的顺序则变成了:H->I->J->I。
        • 结论:除了 APP 默认的 affinity 的 Task ,其他 衍生出来的 Task 当 其 Root Act 被其他 Task 在次调用,衍生 Task 中 Activity 数量,位置不会产生变化,简而言之此时的startAct 的操作,就只是为了将 Root Act 的Task 调度到前台,但是却不改变 Task,当然衍生 Task 中其他 Act 另当别论。



        到此,本文结束:其实本文的重点在于对 task 和 taskAffinity 的理解。Intent Flag 列出了常用的一些Flag。而最后的彩蛋,其实从某种意义上来说,是想提醒自己,Android里面依然很有多细节,不管是开发还是学习都需要谨慎对待。

        参考文章

        Android中,Intent.setFlags();几个常用的属性

    猜你喜欢

    转载自blog.csdn.net/cjh_android/article/details/82024029