探索Activity之launchMode
之前有简单探索了Activity的生命周期, 也提到, Activity的生命周期实际场景远非那么简单, 诸如launch mode, intent flag, activity属性等都会对生命周期流程产生影响.
本想将launchMode, intent flag, activity任务相关属性(affinity, clearTaskOnLaunch, finishOnTaskLaunch等)一起结合task和back stack探究以下, 发现关于intent flag的使用和官方文档有很多出入, 在此先探索下启动模式launchMode.
google发现, 国外很多人对Android官方文档的task and back stack这块有质疑, 也有Android的开发人员出来解释是由于代码更新, 文档没有跟上~~
看来果然是尽信书不如无书啊, 下次结合实例深入研究下这块.
1, 相关概念
根据官方解释:
The launchMode attribute specifies an instruction on how the activity should be launched into a task.
大意是说launchMode指示一个activity是以何种形式被启动(放置)到一个task中的.
还是涉及到task了, 先了解下吧:
- Task
- 任务
- 是指在执行特定作业时与用户交互的一系列 Activity.
- Back Stack
- 返回栈
- 组成任务的这些Activity按照各自的打开顺序排列在堆栈中.
以上是官方文档的解释.
这里有一个疑问, Task和Back Stack的对应关系是怎样的呢, 一一对应, 还是说系统就一个back stack, 所有的activity都在stack中呢? 这个疑问我们在探索launchMode的过程中也会有解答~
-
launchMode
- 有四种(standard, singleTop, singleTask, singleInstance), 默认standard.
-
standard
- Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。
-
singleTop
- 如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例.
- Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例.
-
singleTask
- 系统创建新任务并实例化位于新任务底部的 Activity. 但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 onNewIntent() 方法向其传送 Intent,而不是创建新实例.
- 一次只能存在 Activity 的一个实例.
-
singleInstance
- 与 "singleTask" 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中.
- 该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开.
以上概念解释都来自官方文档.
我们接下来要做的就是编写实例加以验证, 并对我们之前探索的Activity的生命周期加以补充.
2, 开始探索
借用上次探索生命周期的Demo程序.
Github源码地址
我们有三个activity: AActivity, BActivity, CActivity.
下面我们围绕这三个activity开展一系列实验.
2.1, Standard模式
2.1.1, 都为standard, 执行A -> B -> C -> C
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=55:
Task id #193
* TaskRecord{4325d0a0 #193 A=com.anly.samples U=0 sz=5}
numActivities=5 rootWasReset=true userId=0 mTaskType=0 numFullscreen=5 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{432654c8 u0 com.anly.samples/.MainActivity t193},
ActivityRecord{4366fbd0 u0 com.anly.samples/.activity.AActivity t193},
ActivityRecord{429e0cc0 u0 com.anly.samples/.activity.BActivity t193},
ActivityRecord{43590f20 u0 com.anly.samples/.activity.CActivity t193},
ActivityRecord{427f6d98 u0 com.anly.samples/.activity.CActivity t193}]
可以看到:
1, C启动C时, 新建了一个C的实例.
2, 此时Task #193 中的实例如同我们的启动顺序依次是ABCC.
2.2 SingleTop模式
2.2.1, C设置为singleTop, 执行A -> B -> C -> C
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=24:
Task id #155
* TaskRecord{432ae270 #155 A=com.anly.samples U=0 sz=5}
numActivities=5 rootWasReset=false userId=0 mTaskType=0 numFullscreen=5 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{427bf238 u0 com.anly.samples/.MainActivity t155},
ActivityRecord{427c1c28 u0 com.anly.samples/.activity.AActivity t155},
ActivityRecord{427c2b80 u0 com.anly.samples/.activity.BActivity t155},
ActivityRecord{427c34c8 u0 com.anly.samples/.activity.CActivity t155}]
可以看到:
1, C启动C时, 并未重建一个C, 还是使用了之前的实例, 通过onNewIntent的方式唤起.
2, 需要注意的时, 就算是C复用了, 还是会执行C的onPause, 然后再onNewIntent --> onResume的.
3, 此时Task #155的Activity实例顺序是ABC.
2.2.2, B设置为singleTop, 执行A -> B -> C -> B
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=57:
Task id #195
* TaskRecord{42fe9718 #195 A=com.anly.samples U=0 sz=5}
numActivities=5 rootWasReset=false userId=0 mTaskType=0 numFullscreen=5 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{427f48c8 u0 com.anly.samples/.MainActivity t195},
ActivityRecord{43355cd8 u0 com.anly.samples/.activity.AActivity t195},
ActivityRecord{429984d8 u0 com.anly.samples/.activity.BActivity t195},
ActivityRecord{4484a590 u0 com.anly.samples/.activity.CActivity t195},
ActivityRecord{43953fe8 u0 com.anly.samples/.activity.BActivity t195}]
可以看到:
1, 从C启动B时, 又新建了一个B的实例.
2, 此时Task #195的Activity实例顺序为ABCB, 看起来, 这个和2.1.1的standard模式实验一样, 都是新建了实例.
2.3 SingleTask模式
2.3.1 设置C为singleTask, 执行A -> B -> C -> C
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=26:
Task id #157
* TaskRecord{43025e70 #157 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{427bbd48 u0 com.anly.samples/.MainActivity t157},
ActivityRecord{43bc91c8 u0 com.anly.samples/.activity.AActivity t157},
ActivityRecord{42a239b8 u0 com.anly.samples/.activity.BActivity t157},
ActivityRecord{430e21c0 u0 com.anly.samples/.activity.CActivity t157}]
可以看到结果和2.2.1一样
1, C复用了. Task里的Activity依次为ABC.
2, C先onPause, 然后onNewIntent唤起, 走onResume.
2.3.2, 设置B为singleTask, 执行A -> B -> C -> B
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=27:
Task id #158
* TaskRecord{42ccb098 #158 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{427bbd48 u0 com.anly.samples/.MainActivity t158},
ActivityRecord{43082eb0 u0 com.anly.samples/.activity.AActivity t158},
ActivityRecord{43aaea90 u0 com.anly.samples/.activity.BActivity t158}]
可以看到:
1, B是复用的, onNewIntent唤起, 走的onRestart流程.
2, Task #158的Activity实例依次是AB.
3, C没有主动销毁(Back, finish), 但是被移除了.
2.4, SingleInstance模式
2.4.1, 设置C为singleInstance, 执行A -> B -> C -> C
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=31:
Task id #163
* TaskRecord{43308db8 #163 A=com.anly.samples U=0 sz=1}
numActivities=1 rootWasReset=false userId=0 mTaskType=0 numFullscreen=1 mOnTopOfHome=false
affinity=com.anly.samples
intent={cmp=com.anly.samples/.activity.CActivity}
realActivity=com.anly.samples/.activity.CActivity
Activities=[ActivityRecord{432f03c0 u0 com.anly.samples/.activity.CActivity t163}]
Task id #162
* TaskRecord{42d702b0 #162 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{427bc7e8 u0 com.anly.samples/.MainActivity t162},
ActivityRecord{42995d98 u0 com.anly.samples/.activity.AActivity t162},
ActivityRecord{4326ee40 u0 com.anly.samples/.activity.BActivity t162
可以看到:
1, 第一次B启动C时, C运行在了另一个Task #163中.
2, 从C再次启动C室, C复用了, 类似的, onNewIntent唤起, 走onResume流程.
3, 特别注意: task信息中, 可以看到Stack和Task的关系. 此时, Stack #1中有两个Task, 分别是Task #162(AB)在Stack底部, Task #163(C)在Stack顶部.
2.4.2, 设置A为singleInstance, 执行A -> B -> C -> A
生命周期Log:
Task/Back Stack信息:
Stack #1 mStackId=34:
Task id #169
* TaskRecord{431fd9e0 #169 A=com.anly.samples U=0 sz=1}
numActivities=1 rootWasReset=false userId=0 mTaskType=0 numFullscreen=1 mOnTopOfHome=false
affinity=com.anly.samples
intent={flg=0x400000 cmp=com.anly.samples/.activity.AActivity}
realActivity=com.anly.samples/.activity.AActivity
Activities=[ActivityRecord{43a58140 u0 com.anly.samples/.activity.AActivity t169}]
Task id #168
* TaskRecord{437328f0 #168 A=com.anly.samples U=0 sz=3}
numActivities=3 rootWasReset=true userId=0 mTaskType=0 numFullscreen=3 mOnTopOfHome=false
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{43114528 u0 com.anly.samples/.MainActivity t168},
ActivityRecord{42ff3f20 u0 com.anly.samples/.activity.BActivity t168},
ActivityRecord{42abe0a0 u0 com.anly.samples/.activity.CActivity t168}]
可以看到结果和2.4.1类似
1, A单启在一个Task #169中, 再次启动不重建, onNewIntent唤起, 走onRestart流程.
2, 需要注意的是, 从A启动B的时候, B是跟MainActiviy在一个task的, 也就是说A所在的task容不下别人.
3, 需要强调的是, A是复用的, 这个时候如果一直按Back键返回, 不会再看到A了, 如下:
结论
根据以上实验, 可以得出以下结论:
-
standard模式的Activity, 每次启动都会创建一个新的实例, 放到启动他的那个Activity所在的Task中.
-
singleTop模式的Activity, 仅当该Activity已经在Task的顶部了, 才会复用. 复用时onPause, 然后onNewIntent唤起, 走onResume流程. 否则都要创建新的实例, 放进Task中.
-
singleTask模式的Activity, 同一个Task中只会存在一个实例. 如果Task中还没有, 则新建, 放在Task顶部; 如果Task中已经有该Activity实例, 则复用.
-
singleTask模式的Activity的复用模式:
- 如果已经在Task顶部, 如同singleTop的复用模式;
- 如果不在Task顶部, 则销毁Task中该Activity顶部的所有其他Activity, 通过onNewIntent唤起该Activity, 走onRestart流程.
-
singleInstance模式的Activity, 会运行在一个单独的Task中, 且整个系统中只有一个该Activity实例. 相当于单例模式. 复用模式和singleTask一样.
-
回答文首提出的Task和Back Stack的关系:
- 系统中会存在多个Task, 多个Back Stack.
- 其中一个Back Stack中可以有多个Task.
- Task可以理解为一次交互的Activities的组合(一般来说一个Application的所有Activity运行在一个Task).
- Back Stack可以理解为从Launcher界面进入某一个应用开始交互, 可能有很多操作, 这些操作可能分成不同任务的, 例如在编辑联系人的时候跳转到相册了, 可能新启了一个Task, 但是这整个交互流程都在一个Back Stack.
- 简单来说, 个人理解, Back Stack的重点是在Back, 就是说同一Stack的Activity是可以一直返回的.
-
多次实验, 大家也看到了onPause的重要性, 各种流程, onPause都是必不可少的. 这也给了我们App处理的很多启示. 例如重要数据的保存, 另外, 如生命周期一文中所说, 也反映了别的Activity启动是需要等到上一个Activity onPause执行完毕的.