探索Activity之启动Intent Flag和taskAffinity

引用上文生命周期launchMode介绍, Activity的生命周期实际上比我们想象的复杂得多.

本文主要通过实例, 来探索下Activity的启动Intent Flag以及taskAffinity对生命周期和Task/Back Stack的影响. 算是对生命周期launchMode的一个补充, 以便我们在开发过程中灵活组合运用.

照例, 我们先从一些官方解释开始:

1, 相关概念

  • 对生命周期和Task/Back Stack有影响的Intent Flag主要有:

    • FLAG_ACTIVITY_NEW_TASK
    • FLAG_ACTIVITY_CLEAR_TOP
    • FLAG_ACTIVITY_SINGLE_TOP
  • FLAG_ACTIVITY_NEW_TASK

    • 会产生与 "singleTask" launchMode 值相同的行为.
    • 在新任务中启动Activity. 如果已有包含该Activity的任务,则该任务会转到前台并恢复其最后状态,同时该Activity会在onNewIntent()中收到新Intent.
  • FLAG_ACTIVITY_SINGLE_TOP

    • 会产生与 "singleTop" launchMode 值相同的行为.
    • 如果正在启动的Activity是当前Activity(位于返回栈的顶部), 则现有实例会接收对 onNewIntent()的调用,而不是创建 Activity 的新实例.
  • FLAG_ACTIVITY_CLEAR_TOP

    • 如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例.
    • 如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中删除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例.

以上为官方文档解释.

探索Activity之launchMode一文中我们也提到了实际上文档由于"年久失修"没有跟上, 有些解释是不合理的.
我们可以跟随实例一起看下.

2, 开始探索

借用上次探索生命周期的Demo程序.
Github源码地址

通过AActivity, BActivity, CActivity这三个Activity之间的跳转来进行intent flag的探索.

如果没有特别之处, 默认A, B, C三个Activity的launchMode都是默认的standard模式.

2.1, FLAG_ACTIVITY_NEW_TASK

2.1.1, 执行B -> A, B启动A时加FLAG_ACTIVITY_NEW_TASK

实验目的是看下, 在当前系统没有A实例时, 用FLAG_ACTIVITY_NEW_TASK来启动A会不会将A创建在单独的任务中.

BActivity.java中:

startActivity(new Intent(BActivity.this, AActivity.class)
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));

生命周期Log:


Task/Back Stack信息:

  Stack #1 mStackId=3:
    Task id #35
    * TaskRecord{42b60ae0 #35 A=com.anly.samples U=0 sz=3}
      numActivities=3 rootWasReset=false userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
      affinity=com.anly.samples
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
      realActivity=com.anly.samples/.MainActivity
      Activities=[ActivityRecord{4285c1b0 u0 com.anly.samples/.MainActivity t35}, 
      ActivityRecord{42decc00 u0 com.anly.samples/.activity.BActivity t35}, 
      ActivityRecord{4372b9e8 u0 com.anly.samples/.activity.AActivity t35}]

可以看到:
B以FLAG_ACTIVITY_NEW_TASK启动A, A仍然和B处在同一个Task中.

2.1.2 执行A -> B -> A, B启动A时加FLAG_ACTIVITY_NEW_TASK

实验目的是想验证下官方文档对FLAG_ACTIVITY_NEW_TASK的解释, 在A实例已经存在的情况下, 以FLAG_ACTIVITY_NEW_TASK启动A会发生什么.

生命周期Log:


Task/Back Stack信息:

  Stack #1 mStackId=2:
    Task id #34
    * TaskRecord{42bfb088 #34 A=com.anly.samples U=0 sz=4}
      numActivities=4 rootWasReset=false userId=0 mTaskType=0 numFullscreen=4 mOnTopOfHome=true
      affinity=com.anly.samples
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
      realActivity=com.anly.samples/.MainActivity
      Activities=[ActivityRecord{42568318 u0 com.anly.samples/.MainActivity t34}, 
      ActivityRecord{42725050 u0 com.anly.samples/.activity.AActivity t34}, 
      ActivityRecord{42dab240 u0 com.anly.samples/.activity.BActivity t34}, 
      ActivityRecord{42e293f8 u0 com.anly.samples/.activity.AActivity t34}]

可以看到:
1, 在B启动A之前, Task #34中本来就有A, 但是B加FLAG_ACTIVITY_NEW_TASK启动A时, A并未重用, 而是在本Task #34中在此创建了一个A的实例. (这点和文档描述不一致)
2, 此时Task #34中的Activity实例为ABA.
3, 但是如果A的lunchMode是singleTask的话, 如lunchMode一文2.2.3所示, 此时应该销毁A以上的实例, Task中只剩下A.
4, 综上, FLAG_ACTIVITY_NEW_TASK并不等同与singleTask. 且FLAG_ACTIVITY_NEW_TASK感觉并为起作用(在A已经存在一个实例的情况下).

2.2, FLAG_ACTIVITY_SINGLE_TOP

2.2.1, 执行A -> B -> B, 其中B启动B时加FLAG_ACTIVITY_SINGLE_TOP

BActivity启动B时加:

startActivity(new Intent(BActivity.this, BActivity.class)
                        .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP));

生命周期Log:


Task/Back Stack信息:

  Stack #1 mStackId=6:
    Task id #38
    * TaskRecord{43665a30 #38 A=com.anly.samples U=0 sz=3}
      numActivities=3 rootWasReset=true userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
      affinity=com.anly.samples
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.anly.samples/.MainActivity}
      realActivity=com.anly.samples/.MainActivity
      Activities=[ActivityRecord{42bbfea0 u0 com.anly.samples/.MainActivity t38}, 
      ActivityRecord{433b6130 u0 com.anly.samples/.activity.AActivity t38}, 
      ActivityRecord{4324ef18 u0 com.anly.samples/.activity.BActivity t38}]

可以看到:
1, B复用了, 通过onNewIntent, 走onResume流程, 复用之前的B实例.
2, 此时Task #38中的Activity实例为AB.

2.3, FLAG_ACTIVITY_CLEAR_TOP

2.3.1, 执行A -> B -> A, 启动B启动A时加FLAG_ACTIVITY_CLEAR_TOP

实验目的是为了看下A会不会重用, 且B会不会被Clear.

BActivity启动A的代码:

startActivity(new Intent(BActivity.this, AActivity.class)
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));

生命周期Log:


Task/Back Stack信息:

 Stack #1 mStackId=7:
    Task id #39
    * TaskRecord{4274e020 #39 A=com.anly.samples U=0 sz=2}
      numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=true
      affinity=com.anly.samples
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
      realActivity=com.anly.samples/.MainActivity
      Activities=[ActivityRecord{42742598 u0 com.anly.samples/.MainActivity t39}, 
      ActivityRecord{4274eb28 u0 com.anly.samples/.activity.AActivity t39}]

可以看到:
1, A并没有重用, 而是新建了一个实例. 这个和文档是一致的, 参见FLAG_ACTIVITY_CLEAR_TOP概念第二点.
2, B被销毁了(Clear Top).
3, 此时Task #39中只有A(一个新的A).

2.4, 组合使用

以上是简单使用, 然后实际场景中会有很多组合使用Intent Flag以及Intent Flag与taskAffinity结合使用的情况. 其中官方文档就提到了:

FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。

下面我们来实验下这种组合:

2.4.1, FLAG_ACTIVITY_CLEAR_TOP + FLAG_ACTIVITY_NEW_TASK

执行A -> B -> A, 其中B启动A时, Intent flag加FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK

B启动A的代码:

startActivity(new Intent(BActivity.this, AActivity.class)
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK));

生命周期Log:


Task/Back Stack信息:

  Stack #1 mStackId=8:
    Task id #40
    * TaskRecord{429c96b0 #40 A=com.anly.samples U=0 sz=2}
      numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=true
      affinity=com.anly.samples
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
      realActivity=com.anly.samples/.MainActivity
      Activities=[ActivityRecord{427907d0 u0 com.anly.samples/.MainActivity t40}, 
      ActivityRecord{42dd24b8 u0 com.anly.samples/.activity.AActivity t40}]

可以看到:
1, 结果和2.3.1单独使用Intent.FLAG_ACTIVITY_CLEAR_TOP是一样的.

2.5, taskAffinity补充实验

有2.1.1, 2.1.2以及2.4.1这三个包含Intent.FLAG_ACTIVITY_NEW_TASK的实验, 可以看到, 字面上Intent.FLAG_ACTIVITY_NEW_TASK的意思是在新的task启动Activity, 然而事实上, 新Activity还是在原来的task上创建的.

这里有必要提出官网关于taskAffinity的解释了:

taskAffinity指示Activity优先属于哪个task. 默认情况下, 同一应用中的所有 Activity 彼此关联. 因此, 默认情况下, 同一应用中的所有 Activity 优先位于相同任务中.
taskAffinity在两种情况下会起作用:
--- 启动 Activity 的 Intent 包含 FLAG_ACTIVITY_NEW_TASK 标志.
--- Activity 将其 allowTaskReparenting 属性设置为 "true".

让我们来结合taskAffinity做下实验:

2.5.1, taskAffinity + FLAG_ACTIVITY_NEW_TASK

设置A和B不同的taskAffinity, 执行Main -> A -> B -> A -> B -> A, 其中B启动A使用FLAG_ACTIVITY_NEW_TASK

为什么要执行两次B -> A? 我们跟随实验结果, 稍后来看.

设置A的taskAffinity为com.anly.aactivity, B默认(包名).

<activity
  android:name=".activity.AActivity"
  android:label="A-Activity"
  android:taskAffinity="com.anly.aactivity"
  >
</activity>

<activity
  android:name=".activity.BActivity"
  android:label="B-Activity"
  >
</activity>

B启动A:

startActivity(new Intent(BActivity.this, AActivity.class)
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));

生命周期Log:


Task/Back Stack信息:

Stack #1 mStackId=10:
    Task id #44
    * TaskRecord{43085768 #44 A=com.anly.aactivity U=0 sz=2}
      numActivities=2 rootWasReset=false userId=0 mTaskType=0 numFullscreen=2 mOnTopOfHome=false
      affinity=com.anly.aactivity
      intent={flg=0x10000000 cmp=com.anly.samples/.activity.AActivity}
      realActivity=com.anly.samples/.activity.AActivity
      Activities=[ActivityRecord{4303fe00 u0 com.anly.samples/.activity.AActivity t44}, ActivityRecord{4324bb10 u0 com.anly.samples/.activity.BActivity t44}]
      
    Task id #43
    * TaskRecord{426d0a78 #43 A=com.anly.samples U=0 sz=3}
      numActivities=3 rootWasReset=true userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
      affinity=com.anly.samples
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.anly.samples/.MainActivity}
      realActivity=com.anly.samples/.MainActivity
      Activities=[ActivityRecord{4256ae80 u0 com.anly.samples/.MainActivity t43}, 
      ActivityRecord{4372db08 u0 com.anly.samples/.activity.AActivity t43}, 
      ActivityRecord{4273f478 u0 com.anly.samples/.activity.BActivity t43}]

可以看到:
1, Stack #1中有两个Task, #43(affinity=com.anly.samples)和#44(affinity=com.anly.aactivity).
2, 第一轮Main -> A -> B时, 再Task #43中产生了Main,A,B三个Activity实例.
3, 接着B -> A时, A在一个新的Task #44中创建了新的A实例.
4, 然后A -> B, 因为B不加任何参数(启动模式, affinity, flag等), B会创建在启动他的Activity也就是A所在的Task.
5, 此时Task #44中就有了A, B.
6, 再次在B中点击启动A(携带Intent.FLAG_ACTIVITY_NEW_TASK)时. 并没有任何反应.

为什么会出现第6点描述的这样的问题呢?
我理解:
此时B启动A, 因为携带Intent.FLAG_ACTIVITY_NEW_TASK, 且A的taskAffnity为"com.anly.aactivity". 系统会在affinity=com.anly.aactivity的Task中找有没有已经存在的A的实例, 发现Task #44中有. 于是乎, 想重用A. 然而并没有能销毁B, 让A弹出来接收新的Intent.
所以说, 这种情况下, Intent.FLAG_ACTIVITY_NEW_TASK必须结合Intent.FLAG_ACTIVITY_CLEAR_TOP来一起用.

让我们再做个实验验证下想法.

2.5.1, taskAffinity + FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP

设置A和B不同的taskAffinity, 执行Main -> A -> B -> A -> B -> A, 其中B启动A使用FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP

A的affinity还是设置成com.anly.aactivity, B默认.

B启动A的代码:

startActivity(new Intent(BActivity.this, AActivity.class)
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP));

生命周期Log:


Task/Back Stack信息:

 Stack #1 mStackId=11:
    Task id #46
    * TaskRecord{4338bc08 #46 A=com.anly.aactivity U=0 sz=1}
      numActivities=1 rootWasReset=false userId=0 mTaskType=0 numFullscreen=1 mOnTopOfHome=false
      affinity=com.anly.aactivity
      intent={flg=0x14000000 cmp=com.anly.samples/.activity.AActivity}
      realActivity=com.anly.samples/.activity.AActivity
      Activities=[ActivityRecord{42d88890 u0 com.anly.samples/.activity.AActivity t46}]

    Task id #45
    * TaskRecord{42eee4d0 #45 A=com.anly.samples U=0 sz=3}
      numActivities=3 rootWasReset=false userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=true
      affinity=com.anly.samples
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.anly.samples/.MainActivity}
      realActivity=com.anly.samples/.MainActivity
      Activities=[ActivityRecord{42ed9690 u0 com.anly.samples/.MainActivity t45}, ActivityRecord{42e507b8 u0 com.anly.samples/.activity.AActivity t45}, ActivityRecord{42714cd0 u0 com.anly.samples/.activity.BActivity t45}]

可以看到:
果然, 此时第二次B -> A, 有效果了, 跳转到A了.
然而, 我们发现, 虽然Task #46中只有一个A(B被clear top,销毁了). 然而A并不是重用的, 而是先销毁了然来的A实例, 重建了一个A实例.
参见相关概念Intent.FLAG_ACTIVITY_CLEAR_TOP的第二点解释, 的确是这样, 因为A是默认的standard模式, 所以必须新创建实例.

3, 结论

至此, 我们关于Intent flag和taskAffinity的实验结束, 我们来看下相关结论:

  1. FLAG_ACTIVITY_NEW_TASK并不像官方文档所说的等同与singleTask.

  2. 在没有任何其他flag组合和taskAffinity设置的情况下, 同一应用内FLAG_ACTIVITY_NEW_TASK启动另外一个Activity, 不会在新的Task中创建实例, 也不会有实例复用.

  3. FLAG_ACTIVITY_SINGLE_TOP作用等同与singleTop, 当Task的top Activity是该Activity时, Activity复用.

  4. FLAG_ACTIVITY_CLEAR_TOP会clear top, 也就是说如果Task中有ABCD, 在D中启动B, 会clear掉B以上的CD. CD销毁.

  5. 注意, FLAG_ACTIVITY_CLEAR_TOP并不意味着重用, 默认Activity为standard模式的话, 只是会clear其top的其他Activity实例, 该Activity并不会重用, 而是也会销毁, 然后创建一个新的该Activity实例来响应此Intent.

  6. 在没有设置taskAffinity的情况下, 同一应用内FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP组合启动另外一个Activity, 作用和单独使用FLAG_ACTIVITY_CLEAR_TOP是一样.(此点类同与第二点)

  7. 如taskAffinity解释的一样, 在我们没有引入taskAffinity的2.1, 2.2, 2.3, 2.4的相关实验中, 同一个应用中, 使用各种Intent flag都并不会创建新的Task.

  8. taskAffinity需结合FLAG_ACTIVITY_NEW_TASK使用, 此时会再新的Task中寻找/创建待启动的Activity实例.

  9. 强烈建议FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP结合使用, 单独使用FLAG_ACTIVITY_NEW_TASK可能会遇到2.5.1那样的问题.

  10. Intent Flag并不能代替launchMode, 至少在想重用Activity的情况下, 你需要做的是考虑launchMode而非Intent Flag.

  11. 个人理解, Intent Flag更多是倾向于用来做Task中的Activity组织. 而launchMode兼顾Task组织和Activity实例的重用.

推荐阅读更多精彩内容