引用上文生命周期和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的实验结束, 我们来看下相关结论:
FLAG_ACTIVITY_NEW_TASK并不像官方文档所说的等同与singleTask.
在没有任何其他flag组合和taskAffinity设置的情况下, 同一应用内FLAG_ACTIVITY_NEW_TASK启动另外一个Activity, 不会在新的Task中创建实例, 也不会有实例复用.
FLAG_ACTIVITY_SINGLE_TOP作用等同与singleTop, 当Task的top Activity是该Activity时, Activity复用.
FLAG_ACTIVITY_CLEAR_TOP会clear top, 也就是说如果Task中有ABCD, 在D中启动B, 会clear掉B以上的CD. CD销毁.
注意, FLAG_ACTIVITY_CLEAR_TOP并不意味着重用, 默认Activity为standard模式的话, 只是会clear其top的其他Activity实例, 该Activity并不会重用, 而是也会销毁, 然后创建一个新的该Activity实例来响应此Intent.
在没有设置taskAffinity的情况下, 同一应用内FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP组合启动另外一个Activity, 作用和单独使用FLAG_ACTIVITY_CLEAR_TOP是一样.(此点类同与第二点)
如taskAffinity解释的一样, 在我们没有引入taskAffinity的2.1, 2.2, 2.3, 2.4的相关实验中, 同一个应用中, 使用各种Intent flag都并不会创建新的Task.
taskAffinity需结合FLAG_ACTIVITY_NEW_TASK使用, 此时会再新的Task中寻找/创建待启动的Activity实例.
强烈建议FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_CLEAR_TOP结合使用, 单独使用FLAG_ACTIVITY_NEW_TASK可能会遇到2.5.1那样的问题.
Intent Flag并不能代替launchMode, 至少在想重用Activity的情况下, 你需要做的是考虑launchMode而非Intent Flag.
个人理解, Intent Flag更多是倾向于用来做Task中的Activity组织. 而launchMode兼顾Task组织和Activity实例的重用.