探索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了, 如下:

结论

根据以上实验, 可以得出以下结论:

  1. standard模式的Activity, 每次启动都会创建一个新的实例, 放到启动他的那个Activity所在的Task中.

  2. singleTop模式的Activity, 仅当该Activity已经在Task的顶部了, 才会复用. 复用时onPause, 然后onNewIntent唤起, 走onResume流程. 否则都要创建新的实例, 放进Task中.

  3. singleTask模式的Activity, 同一个Task中只会存在一个实例. 如果Task中还没有, 则新建, 放在Task顶部; 如果Task中已经有该Activity实例, 则复用.

  4. singleTask模式的Activity的复用模式:

    • 如果已经在Task顶部, 如同singleTop的复用模式;
    • 如果不在Task顶部, 则销毁Task中该Activity顶部的所有其他Activity, 通过onNewIntent唤起该Activity, 走onRestart流程.
  5. singleInstance模式的Activity, 会运行在一个单独的Task中, 且整个系统中只有一个该Activity实例. 相当于单例模式. 复用模式和singleTask一样.

  6. 回答文首提出的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是可以一直返回的.
  7. 多次实验, 大家也看到了onPause的重要性, 各种流程, onPause都是必不可少的. 这也给了我们App处理的很多启示. 例如重要数据的保存, 另外, 如生命周期一文中所说, 也反映了别的Activity启动是需要等到上一个Activity onPause执行完毕的.

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

推荐阅读更多精彩内容