探索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实例的重用.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容