任务和回退栈

本文深入解析Android应用中任务和回退栈的工作原理,详细讲解了Activity如何启动、任务如何创建及管理,以及用户在不同场景下的交互体验。包括Activity的启动模式、任务间的关联方式、清理策略等核心概念,旨在帮助开发者更好地理解和利用Android系统提供的任务与回退栈特性。

任务和回退栈

  一个应用通常有多个Activity。每个Activity围绕着用户要执行的具体行为被设计,其可以启动其他的Activity。例如,邮件应用有一个Activity用来展示新消息列表。当用户点击一个消息项,打开一个新Activity来查看该条消息详情。
  一个Activity也能启动一个设备上的其他应用的Activity。例如,如果你的应用想要发一封邮件,则你可以定义一个执行”发送”行为且包含需要数据(比如邮件地址和消息体)的Intent。其他应用程序声明了处理这种的类型Intent的Activity将被打开。上面的情况,定义的Intent是要发送邮件,所以邮件应用的“撰写”Activity启动(如果有多个Activity支持这一相同的Intent,则系统会让用户选择使用哪一个)。当邮件被发送,你的Activity将恢复,这看起来好像邮件Activity是你的应用的一部分。尽管activity来自不同的应用,Android通过保持所有的Activity在一个相同的任务栈来维持无缝的用户体验。
  任务是当执行一项具体工作时用户与之交互的Activity的集合。这些Activity按打开的顺序被放置在一个栈里(回退栈)。
  设备的主屏幕是大部分任务启动的地方。当用户在桌面上点击一个应用程序图标时,这个应用程序的任务被调到前台。如果这个应用程序没有任务存在,则会创建一个新的任务,并且该应用的“主”Activity将被打开作为该任务栈的根Activity。
  当当前的Activity启动另一个Activity时,这个新的Activity被压入到任务栈的栈顶。前一个Activity仍在任务栈里,只是被标记为停止状态。当一个Actvity停止,系统保持着用户交互的当前状态。当点击返回键时,当前的Activity从任务栈的栈顶弹出(该Activity被销毁),之前的Activity恢复。Activity绝不会在任务栈中重新排列,仅会将启动的Activity压入栈中,将点击返回键后的Activity弹出栈中。下图展示了Activity在时间进程上的行为:
这里写图片描述

  如果用户持续点击返回键,则每次在栈顶的Activity会被弹出且展示之前的Activity,直到返回到主界面。当所有的Activity都从任务栈中移除,该任务不再存在。
  当用户开启一个新的任务或通过Home键回到主屏幕时,任务作为一个整体的单元被移动到后台。当处于后台,该任务栈中所有Activity被停止,但任务栈仍在,其只是简单地失去了焦点。如下图所示:
这里写图片描述

  当用户重新拉起应用,则其任务会重新回到前台。
  注意后台可以同时保持多个任务。但是,如果用户在后台同时运行着多个任务,则系统可能会销毁后台的Activity以回收内存,这会是这些Activity的状态丢失(关于更多状态保持与恢复的内容,请查看Android状态保存与恢复)。
  因为在任务栈中的Activity绝不会重新排列,所以如果应用运行用户启动一种具体的Activity多余一个,则这种Activity的新的实例将被创建并压入任务栈中,而不是将之前的该Activity的实例移到栈顶。像这样,你的应用中的一个Activity可能会被实例化多次,甚至是在不同的任务栈中。如下图所示:
这里写图片描述

  然而,如果你不想一个Activity被实例化多次,则可以修改这种行为。这将在后面的“管理任务栈”一节讲到。
  下面总结一下Activity和任务的默认行为:

  • 当Activity A启动Activity B,Activity A停止,但系统仍会保持它的状态。如果用户在Activity B点击了返回键,则Activity A从保存的状态中恢复。
  • 当用户通过点击Home键离开任务,当前Activity停止且它的任务栈调度到后台。系统保持了任务栈中每一个Activity的状态。如果随后用户点击了桌面图标,则该任务会被调度到前台并且恢复栈顶的Activity。
  • 如果用户点击了返回键,当前Activity将从任务栈中弹出并被销毁。在栈中的之前的Activity会被恢复。
  • Activity可以被实例化多次,且可以在不同任务栈中。

管理任务栈

  同上面的描述,Android管理任务和回退栈的方式是将连续启动的Activity放置在同一任务栈中,对于大多数的应用,这可以工作的很好,你不需要担心你的Activity和任务之间是怎样关联的或它们是怎样存在于任务栈中的。然而,你可以决定是否打断这种正常的行为。也许你想你的应用中的一个Activity在启动时创建一个新的任务,或是,当启动一个Activity时,你想将一个已经存在的实例移到栈顶,再或是,你想在离开任务时,将任务栈中除root Activity中的所有activity清除。
  你可以使用manifest中的<activity>元素的属性或传递到startActivity()的Intent中的flags,来实现这些需求。
  
与其相关的,主要的<activity>属性如下:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

与其相关的主要的Intent flags有:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

在下一节中,将给出怎样使用<activity>属性和Intent flags来定义activity和任务之间的关系与它们在回退栈中的行为表现。

  注意,大部分应用不应该打断Activity和任务之间的默认行为。如果你认为修改默认行为是必要的,那一定要测试该Activity的使用情况是否如你所愿,以确保其不会打断用户期待的行为。

定义启动模式

  启动模式允许你定义一个Activity的新实例怎样与当前任务关联。你可以用下面两种方式定义启动模式:

使用manifest文件

  当你在manifest文件中声明Activity时,你可以定义其启动时与任务的关联方式。
  具体可以使用<activity>元素的lauchMode属性来定义该Activity应该怎样在任务中创建。有四种不同启动模式可以设置给lauchMode属性。

  • standard
    默认设置。每次启动该种Activity时,系统在任务中创建一个新的Activity实例。这种Activity能够被实例化多次,每个实例属于不同的任务,每个任务可以有多个实例。
  • singleTop
    如果该Activity的一个实例存在与当前任务栈的栈顶,系统将启动Intent通过onNewIntent()传递给该实例,而不会新建实例。如果已有该实例不在栈顶,则会新建实例。该Activity会被实例化多次,每个实例可以属于不同的任务,每个任务可以有多个实例。
    注意,当该Activity的一个新实例被创建,用户可以点击返回键回到之前的Activity。但当一个Activity的已存在实例处理了这个启动Intent时,用户点击返回键不能回到调用onNewIntent()进行处理之前的状态。
  • singleTask
    该种启动模式的Activity仅会有一个实例。如果已有任务栈中存在该Activity实例,则系统会将启动它的Intent通过调用其onNewIntent()方法传递给它,而不会再创建一个新的实例,且该Activity上面的所有Activity将以合适的方式自动销毁。如果还没有该Activity实例,则其表现受taskAffinity属性影响,如果taskAffinity属性标识的任务不存在,则会新建一个任务,并将该Activity作为根Activity,如果taskAffinity属性标识的任务存在,则将该Activity压入该任务栈。
    注意,尽管Activity会被启动在一个新的任务中,点击返回键仍然能返回到之前的Activity。
  • singleInstance
    除了系统不会再将其他Activity放入该Activity所在任务,其他同singleTask一样。即该Activity是其所在任务的唯一成员,任何由它启动的Activity将被放置在分隔的任务栈中。

不管一个Activity是否启动在一个新的任务中还是在与启动它的Activity相同的任务栈中,点击返回键时,都会回到前一个Activity。然而,如果你启动的是singleTask模式的Activity,且如果其实例存在于后台,则整个任务会被带到前台。这时,回退栈将包含所有从后台带到前台的Activity,并且被放置在栈顶。如下图所示:
这里写图片描述

使用Intent flag

  当你调用startActivity()时,你可以在Intent中设置flag来声明启动的Activity与任务的关联方式。
  像这样,如果Activity A启动了Activity B,Activity B能在manifest中定义它应该与当前任务栈怎样关联,并且Activity A也可以请求Activity B怎样与当前任务关联。如果两个全部定义了,则Activity A的请求是优于Activity B的请求。
  可以用来修改默认行为的flag有:

  • FLAG_ACTIVITY_NEW_TASK
    其行为同launchMode设置为“singleTask”。
  • FLAG_ACTIVITY_SINGLE_TOP
    其行为同launchMode设置为“singleTop”。
  • FLAG_ACTIVITY_CLEAR_TOP
    如果被启动的Activity已经存在于当前任务中,则其上的所有Activity都被销毁,启动Intent传递到onNewIntent()方法,来恢复该Activity。
    没有对应的launchMode属性。

FLAG_ACTIVITY_CLEAR_TOP经常和FLAG_ACTIVITY_NEW_TASK一起使用。当它们一起使用时,这些flags是将一个已经存在的Activity放置到另一个任务中,并将其放在一个能响应该Intent的位置上。
注意,如果被指定的Activity的启动模式是“standard”,则它将从任务栈中移除,并在它的位置处创建一个新的实例来处理这个Intent。这是因为,当启动模式是“standard”时,对于每一个新的Intent都会创建一个新的实例。

  注意,manifest中可获取的启动模式不一定在Intent的flag中可获取,同样的,Intent的flag中可获取的启动模式在manifest中不一定可以定义。

处理关系

  “关系”指示一个Activity属于哪个任务。默认的,所有来自同一个应用程序的Activity有相同的“关系”。所以默认情况下,同一应用程序的所有Activity属于统一任务。但是,你可以修改一个Activity的关系。在不同应用程序的Activity可以共享同一个“关系”,同一应用程序的的Activity可以分配不同的“关系”。
可以使用<activity>元素的taskAffinity属性来修改给定Activity的“关系”。
taskAffinity属性的值是一个字符串。系统使用包名来标识一个应用程序的默认任务“关系”。为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task。
  “关系”用于下面两种情况:

  • 当创建Activity的Intent包含FLAG_ACTIVITY_NEW_TASK标签时。
    默认情况下,一个新的Activity被创建在调用startActivity()方法的Activity所在任务中。它被压入调用者所在的回退栈中。然而,如果传递到startActivity()方法中的Intent的包含FLAG_ACTIVITY_NEW_TASK标签,则系统会根据条件找寻一个合适的任务来放置这个新的Activity。通常,它是一个新的任务。但是,不总是。如果已经存在一个与这个新的Activity标识的“关系”相同的任务,则这个Activity被创建到这个任务中。否则,启动一个新任务。
    有些实体(比如通知栏管理器)会一直在一个外部任务中启动Activity,所以它们一直在传递到startActivity()方法中的Intent中设置FLAG_ACTIVITY_NEW_TASK标签。如果你有一个Activity被外部使用启动,则可能使用了这个标签,注意这是用户有独立的方式返回到启动它的任务,比如点击桌面图标。

  • 当Activity的allowTaskReparenting属性设置为true时。
    这种情况下,当该Activity标识的“关系”的任务返回到前台时,该Activity可以从启动它的任务移动到同其“关系”的任务中。
    例如,假设一个报导天气条件的Activity被定义为一个旅游应用的一部分,且其具有其所在应用程序的其他Activity相同的“关系”(应用程序的默认关系)并允许使用allowTaskReparenting属性设置允许re-pareting。当你的Activity之一启动了这个天气预报Activity时,它初始属于你的Activity的任务。但是,当这个旅游应用的任务返回前台时,这个天气预报Activity被重新安排到旅游应用的任务中并展示它。

清理回退栈

  如果用户长时间离开一个任务,则系统会清理掉任务中除根Activity之外的所有Activity。但用户再次回到任务中,仅根Activity是被恢复。系统表现这种行为的原因是,用户离开很久后,可能已经放弃了他们之前正在进行的操作,所以返回到任务开始一个新的操作。
  有很多的Activity属性可以用来修改这个行为:

  • alwaysRetainTaskState
    如果任务中的根Activity的这个属性被设置为true,则上面被描述的默认行为不会发生。很长一段时间后,该任务仍保持所有Activity。
  • clearTaskOnLaunch
    如果任务中的根Activity的这个属性被设置为true,则当用户离开任务并返回时,该任务栈被清理直到根Activity。用户会一直以初始状态返回任务,甚至只是离开一小会。
  • finishOnTaskLaunch
    这个属性很像 clearTaskOnLaunch,但它操作在一个单独的Activity上,而不是整个任务上。它会导致任何Activity销毁,包括根Activity。当它被设置为true,该Activity仅存在当前会话任务中。如果用户离开后返回该任务,则它不在存在。
启动任务

  可以通过给一个Activity设置”android.intent.action.MAIN”和”android.intent.category.LAUNCHER”Intent 过滤器来将其设置为一个任务的入口。例如:

<activity ... >
    <intent-filter ... >
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    ...
</activity>

  这种类型的Intent过滤器会为该Activity产生一个图标和标签展示在应用程序启动器中,以提供给用户一个方式去创建该Activity或在它创建后可以任意次回到其创建的任务。
  其第二个能力是重要的:用户必须能够在离开任务后,又能使用该Activity启动器回到它。因为这个原因,作为启动器的Activity应该仅在该Activity有ACTION_MAIN 和CATEGORY_LAUNCHER过滤器时,才使用singleTasksingleInstance这两个启动模式。例如,如果这两个过滤器没有添加,一个Intent创建了一个singleTaskActivity,初始化了一个任务,且用户在该任务上执行了很多操作,这时用户点击Home键。这个任务被放到后台不可见,这时用户没有了返回到这个任务的方法。这是因为,它不会显示在应用程序启动器中。

回退任务

  在启动一系列Activity后,可能会创建几个任务,其中每个任务在回退栈中作为一项,在用户点击返回键时,回退栈的默认回退情况(为对返回键处理重写等)是,先获得回退栈中栈顶的任务,然后弹出该任务栈栈顶的Activity实例,当该任务栈中的所有Activity实例都被弹出,则该任务从回退栈中弹出,并销毁,执行这个回退过程,直到回退到桌面。

四种启动模式的关键结论验证

launchMode:standard

  standard是默认的启动模式,所以Activity不设置launchMode,则其启动模式为standard

被启动Activity和启动Activity的taskAffinity相同
FirstActivity注册
 <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>
SecondActivity注册
<activity android:name=".SecondActivity" />

验证1:该种Activity不启动新的任务,被启动的Activity被压入启动它的Activity所在的任务栈中

  执行FirstActivity启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
TaskRecord{27db6384 #3208 A=com.example.sunxiaodong.taskandbackstack U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity (has extras) }
        Hist #1: ActivityRecord{395c8c83 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3208}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{cd3c76d 16254:com.example.sunxiaodong.taskandbackstack/u0a379}
        Hist #0: ActivityRecord{3240b0d0 u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3208}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity (has extras) }
          ProcessRecord{cd3c76d 16254:com.example.sunxiaodong.taskandbackstack/u0a379}

  从任务栈情况可以得到,SecondActivity被压入FirstActivity所在的任务栈中。

验证2:每次启动该种Activity,系统会在任务中创建一个新的Activity实例

  执行FirstActivity启动SecondActivity,SecondActivity中再次启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
TaskRecord{2ab63d02 #3211 A=com.example.sunxiaodong.taskandbackstack U=0 sz=3}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #2: ActivityRecord{3c598601 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3211}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{18925050 13122:com.example.sunxiaodong.taskandbackstack/u0a379}
        Hist #1: ActivityRecord{2d1693 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3211}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{18925050 13122:com.example.sunxiaodong.taskandbackstack/u0a379}
        Hist #0: ActivityRecord{ddb9d9 u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3211}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{18925050 13122:com.example.sunxiaodong.taskandbackstack/u0a379}

  从任务栈情况可以得到,任务栈中已存在SecondActivity实例的情况下,再启动SecondActivity还会创建一个新的SecondActivity实例压入栈顶。

被启动Activity和启动Activity的taskAffinity不同

验证1:如果被启动Activity的taskAffinity所标识的任务不存在,则该种Activity不能创建新任务

FirstActivity注册
 <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>
SecondActivity注册
<activity
            android:name=".SecondActivity"
            android:taskAffinity="com.example.sunxiaodong.taskandbackstack.second" />

  执行FirstActivity启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
TaskRecord{1abd4247 #3369 A=com.example.sunxiaodong.taskandbackstack U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #1: ActivityRecord{17e1a735 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3369}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{1c02e7c3 18114:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{3d5eef14 u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3369}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{1c02e7c3 18114:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈情况得到,standard启动模式的Activity不会创建taskAffinity标识的新的任务。

验证2:如果被启动Activity的taskAffinity所标识的任务已存在,则被启动Activity的实例会放入启动其Activity所在任务栈中,而不管启动Activity是否在被启动Activity的taskAffinity标识任务中

FirstActivity注册
 <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>
SecondActivity注册
<activity
            android:name=".SecondActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.sunxiaodong.taskandbackstack.second" />
ThirdActivity注册
<activity android:name=".ThirdActivity" />

  执行FirstActivity启动SecondActivity,SecondActivity启动ThirdActivity。

任务栈情况(执行adb shell dumpsys activity
Task id #3374
      TaskRecord{1ac34ecf #3374 A=com.example.sunxiaodong.taskandbackstack.second U=0 sz=2}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
        Hist #1: ActivityRecord{18fa9f27 u0 com.example.sunxiaodong.taskandbackstack/.ThirdActivity t3374}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.ThirdActivity }
          ProcessRecord{3c0de43a 29526:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{43cba37 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3374}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{3c0de43a 29526:com.example.sunxiaodong.taskandbackstack/u0a387}
    Task id #3372
      TaskRecord{2a45d05c #3372 A=com.example.sunxiaodong.taskandbackstack U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #0: ActivityRecord{253e4bf4 u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3372}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{3c0de43a 29526:com.example.sunxiaodong.taskandbackstack/u0a387}

  任务栈情况,符合验证的结论。

launchMode:singleTop

FirstActivity注册
 <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>
SecondActivity注册
<activity
            android:name=".SecondActivity"
            android:launchMode="singleTop" />

验证1:被启动Activity在启动其Activity所在任务栈的栈顶,则不会创建被启动Activity的新实例

  执行FirstActivity启动SecondActivity,SecondActivity再启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
TaskRecord{35412fbd #3376 A=com.example.sunxiaodong.taskandbackstack U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #1: ActivityRecord{1fbbf35c u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3376}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{11ea5d03 30105:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{3456a7b u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3376}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{11ea5d03 30105:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈情况得到,启动模式为singleTop的被启动Activity在启动其Activity所在任务栈的栈顶时,不会创建新实例。

验证2:被启动Activity不在启动其Activity所在任务栈的栈顶,则会在启动Activity所在任务栈的栈顶创建新的被启动Activity实例

ThirdActivity注册
<activity
            android:name=".ThirdActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.sunxiaodong.taskandbackstack.third" />

  执行FirstActivity启动SecondActivity,SecondActivity启动ThirdActivity,ThirdActivity再启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
Task id #3381
      TaskRecord{20eb4d89 #3381 A=com.example.sunxiaodong.taskandbackstack.third U=0 sz=2}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.ThirdActivity }
        Hist #1: ActivityRecord{1fdcca07 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3381}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{163795bc 11943:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{1cdc6d17 u0 com.example.sunxiaodong.taskandbackstack/.ThirdActivity t3381}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.ThirdActivity }
          ProcessRecord{163795bc 11943:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3379
      TaskRecord{36c5f18e #3379 A=com.example.sunxiaodong.taskandbackstack U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #1: ActivityRecord{3fc54f79 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3379}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{163795bc 11943:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{104fbd9a u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3379}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{163795bc 11943:com.example.sunxiaodong.taskandbackstack/u0a387}

  启动模式为singleTop的其他启动情况与启动模式为standard的情况相同,不再给出验证。

launchMode:singleTask

验证1:如果被启动Activity的taskAffinity属性标识的任务为启动Activity所在任务,则不会新建任务

FirstActivity注册
 <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>
SecondActivity注册
<activity
            android:name=".SecondActivity"
            android:launchMode="singleTask" />

  执行FirstActivity启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
TaskRecord{220c4e08 #3406 A=com.example.sunxiaodong.taskandbackstack U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #1: ActivityRecord{2606f228 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3406}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{1ced3387 30484:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{27f048cb u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3406}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{1ced3387 30484:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈情况可以得到,启动模式为singleTask的Activity并不是每次启动都会新建任务,当其taskAffinity属性所标识的任务为启动Activity所在任务时,不会新建任务,即其taskAffinity属性所标识的任务已存在时,不会创建新的任务。

验证2:如果被启动Activity的taskAffinity属性标识的任务不存在,则会新建任务

SecondActivity注册
<activity
            android:name=".SecondActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.sunxiaodong.taskandbackstack.second" />

  执行FirstActivity启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
Task id #3410
      TaskRecord{3298bcfc #3410 A=com.example.sunxiaodong.taskandbackstack.second U=0 sz=1}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
        Hist #0: ActivityRecord{1c628411 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3410}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{1ed6d60b 23610:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3409
      TaskRecord{3b856f85 #3409 A=com.example.sunxiaodong.taskandbackstack U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #0: ActivityRecord{29642a8a u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3409}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{1ed6d60b 23610:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈情况可以得到,如果被启动Activity的taskAffinity属性标识的任务不存在,则会新建任务。

验证3:如果被启动Activity在栈中上面还有其他Activity实例,则这些Activity将以合适的方式自动销毁

FirstActivity注册
<activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>
SecondActivity注册
<activity
            android:name=".SecondActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.sunxiaodong.taskandbackstack.second" />
ThirdActivity注册
<activity android:name=".ThirdActivity" />
FourthActivity注册
<activity
            android:name=".FourthActivity"
            android:launchMode="singleTask"
            android:taskAffinity="com.example.sunxiaodong.taskandbackstack.fourth" />

  执行FirstActivity启动SecondActivity,SecondActivity启动ThirdActivity,ThirdActivity启动FourthActivity。

任务栈情况(执行adb shell dumpsys activity
Task id #3416
      TaskRecord{36ee9249 #3416 A=com.example.sunxiaodong.taskandbackstack.fourth U=0 sz=1}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FourthActivity }
        Hist #0: ActivityRecord{252b608a u0 com.example.sunxiaodong.taskandbackstack/.FourthActivity t3416}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FourthActivity }
          ProcessRecord{22850a05 3430:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3415
      TaskRecord{19cb74e #3415 A=com.example.sunxiaodong.taskandbackstack.second U=0 sz=2}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
        Hist #1: ActivityRecord{18f7b294 u0 com.example.sunxiaodong.taskandbackstack/.ThirdActivity t3415}
          Intent { cmp=com.example.sunxiaodong.taskandbackstack/.ThirdActivity }
          ProcessRecord{22850a05 3430:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{1d6f71ff u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3415}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{22850a05 3430:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3413
      TaskRecord{3e8006f #3413 A=com.example.sunxiaodong.taskandbackstack U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #0: ActivityRecord{1648bf23 u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3413}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{22850a05 3430:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈情况可知,当前有3个任务,其中SecondActivity和ThirdActivity 在一个任务中,且ThirdActivity的实例在SecondActivity的实例之上。

  接着上面,执行FourthActivity启动SecondActivity。

任务栈情况(执行adb shell dumpsys activity
Task id #3419
      TaskRecord{20698cd0 #3419 A=com.example.sunxiaodong.taskandbackstack.second U=0 sz=1}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
        Hist #0: ActivityRecord{28bfef87 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3419}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{722a5fc 10546:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3421
      TaskRecord{2eb884c9 #3421 A=com.example.sunxiaodong.taskandbackstack.fourth U=0 sz=1}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FourthActivity }
        Hist #0: ActivityRecord{cd70e2d u0 com.example.sunxiaodong.taskandbackstack/.FourthActivity t3421}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FourthActivity }
          ProcessRecord{722a5fc 10546:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3418
      TaskRecord{197bf7ce #3418 A=com.example.sunxiaodong.taskandbackstack U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #0: ActivityRecord{270d8dc9 u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3418}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{722a5fc 10546:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈的情况可知,SecondActivity启动后,其所在任务中的ThirdActivity销毁了,SecondActivity被放置在了栈顶。

launchMode:singleInstance

FirstActivity注册
<activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
</activity>

验证1:该种Activity的任务中,只会有一个实例,且由该种Activity启动Activity,如果被启动Activity的taskAffinity属性标识的任务存在,则将实例压入该任务中,如果不存在,则新建该任务

ThirdActivity注册
<activity android:name=".ThirdActivity" />

  执行FirstActivity启动SecondActivity,SecondActivity启动ThirdActivity。

任务栈情况(执行adb shell dumpsys activity
Task id #3423
      TaskRecord{193241ab #3423 A=com.example.sunxiaodong.taskandbackstack U=0 sz=2}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #1: ActivityRecord{1b89f8e8 u0 com.example.sunxiaodong.taskandbackstack/.ThirdActivity t3423}
          Intent { flg=0x10400000 cmp=com.example.sunxiaodong.taskandbackstack/.ThirdActivity }
          ProcessRecord{92fe3a1 13471:com.example.sunxiaodong.taskandbackstack/u0a387}
        Hist #0: ActivityRecord{26f251dd u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3423}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{92fe3a1 13471:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3425
      TaskRecord{304529c6 #3425 A=com.example.sunxiaodong.taskandbackstack U=0 sz=1}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
        Hist #0: ActivityRecord{2f0cb55d u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3425}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{92fe3a1 13471:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈中情况可知,SecondActivity启动的ThirdActivity的taskAffinity属性标识的任务存在,则不再新建任务,而是将其压入已存在的任务中。

ThirdActivity注册
<activity
            android:name=".ThirdActivity"
            android:taskAffinity="com.example.sunxiaodong.taskandbackstack.third" />

  给上面的ThirdActivity设置android:taskAffinity属性后,执行FirstActivity启动SecondActivity,SecondActivity启动ThirdActivity。

任务栈情况(执行adb shell dumpsys activity
Task id #3430
      TaskRecord{666e0ca #3430 A=com.example.sunxiaodong.taskandbackstack.third U=0 sz=1}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.ThirdActivity }
        Hist #0: ActivityRecord{a8a6e49 u0 com.example.sunxiaodong.taskandbackstack/.ThirdActivity t3430}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.ThirdActivity }
          ProcessRecord{3d776eb1 21693:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3429
      TaskRecord{3887723b #3429 A=com.example.sunxiaodong.taskandbackstack U=0 sz=1}
      Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
        Hist #0: ActivityRecord{d159cd9 u0 com.example.sunxiaodong.taskandbackstack/.SecondActivity t3429}
          Intent { flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.SecondActivity }
          ProcessRecord{3d776eb1 21693:com.example.sunxiaodong.taskandbackstack/u0a387}
Task id #3427
      TaskRecord{1d7a3f96 #3427 A=com.example.sunxiaodong.taskandbackstack U=0 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
        Hist #0: ActivityRecord{2cc6f9c u0 com.example.sunxiaodong.taskandbackstack/.FirstActivity t3427}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.sunxiaodong.taskandbackstack/.FirstActivity }
          ProcessRecord{3d776eb1 21693:com.example.sunxiaodong.taskandbackstack/u0a387}

  从任务栈中情况可知,SecondActivity启动的ThirdActivity的taskAffinity属性标识的任务不存在,则会新建任务。

<think> 我们之前已经讨论过,在Android中移除未加入回退栈的事务(FragmentTransaction)的核心方法是:**直接放弃未提交的事务对象**。因为未提交的事务只是一个普通的内存对象,并没有与FragmentManager关联,也不会进入回退栈。因此,不需要特别的操作来移除,只需停止使用它,让垃圾回收器回收即可。 ### 关键点回顾 1. **未提交的事务**:仅仅是一个普通的对象,没有与FragmentManager产生关联,不会执行任何操作,也不会影响回退栈[^1]。 2. **已提交但未加入回退栈的事务**:一旦提交(`commit()`),事务就会被加入FragmentManager的执行队列,但因为没有调用`addToBackStack()`,所以不会进入回退栈。此时,无法通过公开的API取消已提交的事务[^1][^4]。 3. **已提交且已加入回退栈的事务**:可以通过`popBackStack()`系列方法来操作回退栈,从而移除事务[^2][^3][^4]。 ### 具体操作 #### 情况1:事务未提交 直接丢弃事务对象,无需任何操作。例如: ```java FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.container, new MyFragment()); // 不调用commit(),直接不再使用此transaction对象 ``` #### 情况2:事务已提交但未执行(在主线程队列中等待执行) 虽然理论上可以通过反射清除主线程的消息队列来移除,但**强烈不推荐**,因为会影响到其他消息的执行: ```java // 提交事务(此时事务在主线程消息队列中等待执行) transaction.commit(); // 不推荐:清除主线程所有待处理消息(包括其他非Fragment事务的操作) Handler handler = new Handler(Looper.getMainLooper()); handler.removeCallbacksAndMessages(null); // 危险操作! ``` **后果**:此操作会移除主线程中所有待执行的`Runnable`消息,可能导致应用其他部分出现异常。 #### 情况3:事务已执行但未加入回退栈 无法移除。因为事务已经执行,对Fragment的修改已经生效。如果希望撤销,只能通过新的Fragment事务来反向操作(例如重新添加被替换的Fragment)。 ### 最佳实践 - **避免提交不必要的事务**:在提交前检查条件,确保事务是必要的。 - **使用事务替代方案**:考虑使用`show()`/`hide()`来切换Fragment,而不是`replace()`,这样可以避免频繁创建Fragment实例,也减少了事务的提交。 - **谨慎使用回退栈**:只有需要用户通过返回键回退的Fragment才使用`addToBackStack()`。 ### 示例:条件检查避免无效事务 ```java // 检查是否已经添加了该Fragment if (getSupportFragmentManager().findFragmentByTag("my_fragment") == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.add(R.id.container, new MyFragment(), "my_fragment"); transaction.commit(); // 确保只在必要时提交 } ``` ### 相关问题 1. **如何判断一个事务是否已经加入回退栈?** 事务本身没有状态标识,但可以通过回退栈的条目数量间接判断: ```java int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount(); ``` 如果事务提交时调用了`addToBackStack(String name)`,则回退栈条目数量会增加[^4]。 2. **已经提交的事务能否取消?** 在事务执行前(即还在主线程消息队列中时),可以通过反射清除主线程消息队列来取消(不推荐)。一旦事务开始执行,就无法取消[^1]。 3. **如何立即执行一个事务?** 使用`commitNow()`方法可以同步执行事务(注意:`commitNow()`不允许加入回退栈)[^1][^4]。 4. **如何处理Activity重建时未加入回退栈的Fragment?** Activity重建时,未加入回退栈的Fragment不会被自动恢复。如果需要保留,可以在`onSaveInstanceState()`中保存必要数据,并在`onCreate()`中重新创建Fragment[^4]。 [^1]: 未提交的事务不会关联到FragmentManager,直接丢弃即可。 [^2]: 回退栈用于管理用户返回导航,未加入栈的事务不受影响。 [^3]: 加入回退栈的Fragment在移除时生命周期暂停,未加入的则直接销毁。 [^4]: 使用`addToBackStack()`将事务加入回退栈,用户返回时可恢复Fragment。 </think> 在 Android 中移除未加入回退栈的 Fragment 事务,需要根据事务的状态采取不同的处理策略: ### 1. **未提交的事务 (未执行任何操作)** ```java FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.container, new MyFragment()); // 直接丢弃事务对象(无需任何操作) // transaction = null; // 可选:解除引用 ``` - ✅ **原理**:未提交的事务只是内存中的普通对象,与 FragmentManager 无关联[^1] - ✅ **效果**:垃圾回收器自动回收,不会执行任何操作 - ✅ **推荐场景**:初始化后不需要执行的事务 ### 2. **已提交但未执行的事务 (在主线程队列中)** ```java transaction.commit(); // 加入主线程执行队列 // 清除主线程待执行任务(高风险操作) try { Handler handler = new Handler(Looper.getMainLooper()); handler.removeCallbacksAndMessages(null); } catch (Exception e) { e.printStackTrace(); } ``` - ⚠️ **限制**:会清除所有待执行的主线程任务,可能导致其他功能异常[^1] - ️ **不推荐**:仅作为最后手段,无官方 API 支持取消特定事务 ### 3. **已执行但未加入回退栈的事务 (无法撤销)** ```java // 事务已执行且 Fragment 已生效 // 无法直接移除,只能通过新事务回滚 FragmentTransaction undoTransaction = getSupportFragmentManager().beginTransaction(); undoTransaction.remove(currentFragment); // 移除已添加的 Fragment undoTransaction.commit(); ``` - ❌ **限制**:无法撤销原始事务,只能创建反向操作的新事务 - ⚠️ **注意**:可能导致界面闪烁或状态不一致 ### 关键区别 | **事务状态** | 是否可移除 | 操作方法 | 风险等级 | |----------------------|------------|------------------------------|----------| | 未提交 | ✅ 是 | 丢弃对象 | 低 | | 已提交但未执行 | ⚠️ 部分 | 清除主线程队列(不推荐) | 高 | | 已执行但未加入回退栈 | ❌ 否 | 只能创建反向事务 | 中 | | 已加入回退栈 | ✅ 是 | `popBackStack()` 系列方法[^4] | 低 | ### 最佳实践 1. **预防性设计**:提交前检查必要性 ```java if (!isFragmentAdded) { transaction.commit(); // 确保必要才提交 } ``` 2. **使用 `show()/hide()` 替代 `replace()`**: ```java // 避免频繁创建事务 transaction.hide(oldFragment).show(newFragment).commit(); ``` 3. **事务状态监控**: ```java // 检查回退栈状态 boolean hasTransactions = getSupportFragmentManager().getBackStackEntryCount() > 0; ``` ### 相关问题 1. **如何判断事务是否已加入回退栈?** 通过 `getBackStackEntryCount()` 检查回退栈条目数量[^4] 2. **已执行的 Fragment 事务如何撤销?** 只能创建反向事务(如用 `remove()` 撤销 `add()`)[^3][^4] 3. `commit()` `commitNow()` 在事务处理上有何区别? `commitNow()` 同步执行但禁止加入回退栈[^1][^4] 4. **Activity 重建时未加入回退栈的 Fragment 会保留吗?** 不会,其生命周期直接 `destroyed` 而不会保留[^4] [^1]: 未提交的事务不会关联到 FragmentManager,直接丢弃即可 [^3]: 未加入回退栈的 Fragment 在移除时直接销毁 [^4]: 加入回退栈的 Fragment 移除时进入 stopped 状态,可恢复
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值