Android lanchMode -- 不要再简洁明了了

参考

任务栈和Activity启动模式
Android 面试黑洞——当我按下 Home 键再切回来,会发生什么?

前言

之前在网上检索一些lanchMode的知识,发现一点不言简意赅,有些把实验和结论混合一起的,想法很好,但阅读起来其实很混乱,理解起来也麻烦,没有整体的框架逻辑去看这些的时候,好像学到了啥,其实整体来对比又一团混乱,不体系。

  • 建议

本文就只参考了上面两个连接,就完全理解lanchMode的整体框架,建议直接看完本篇以后,再去看这个Android 面试黑洞——当我按下 Home 键再切回来,会发生什么?视频,看看是否自己理解清楚了。理解了这个框架,再去关于Flag的,就都只是些策略问题了。

以下是一些总结和问题,可以看完以后再回看这些,看看自己是否是真的理解了

  • 默认(standard)和singleTop:多用于App内部

  • singleTask:内部交互和外部交互都会用上,singleInstance:多用于开放给外部App来共享使用

  • singeTop = standard + 约束

  • singleInstance = singTask + 约束

  • 在最近任务里看见的Task不一定活着

  • 在最近任务里看不见的Task不一定死了

  • singleTask流程?

  • singleInstance怎么能做到独占一个任务栈

预热

在具体开始启动模式相关知识的时候,首先需要搞清楚一下两点

  1. Task任务栈
  2. taskAffinity

认清任务栈

首先一个简单的关系图:

ActivityStack.png
  • 任务栈结构

任务栈,最直观的表现就是打开最近任务的时候,展示的一个个任务,其实就是一个个task任务栈

安卓系统管理着不同模式下的多个ActivityStack(比如在home launcher界面需要有一个ActivityStack,画中画模式,分屏模式等)。

  • 一个ActivityStack可以包含很多个TaskRecord。
  • 一个TaskRecord又可以包含很多个ActivityRecord。
  • 每一个ActivityRecord都会有一个Activity与之对应,一个Activity可能会有多个ActivityRecord,因为Activity可能被多次实例化。

一系列相关的ActivityRecord组成了一个TaskRecord,TaskRecord是存在于ActivityStack中,ActivityStackSupervisor是用来管理这些ActivityStack的。
launcher也有自己的task,

系统是根据task进行管理的,而不是ActivityStack

  • 前后台task

task又分为前台task和后台task,前台task(也叫当前task)就是栈顶是和用户交互的activity的task,后台task就是非前台task。

Activity可以在Task内部叠成栈,不同task之间也可以叠成栈,不过只针对前台task,前台叠加的多个task,在进入后台会立刻被拆开,这也就是造成"singleTask"模式下回退路径变化的问题。

当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按"返回"按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。

如果用户继续按"返回",堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到 所有 Activity 均从堆栈中移除后,任务即不复存在,回到桌面,但此时打开最近任务依然能看到该task的一个“残影”

taskAffinity

清单文件中Application和Activity标签都可以使用"android:taskAffinity"标记
它代表这个Activity所希望归属的Task,也就是分组,在默认情况下,同一个app中的所有Activity拥有共同的Affinity,即manifest中定义的package。

一个Activity的taskAffinity的优先级:activity设置的 -> Application设置的 -> packageName

taskAffinity与Task、Activity的关系

前面说了,taskAffinity其实就是一个分组标记。

  • Activty有"android:taskAffinity"可以更改标记,默认取自Application的标记, Application默认又取自包名。
  • 对于Task的affinity标记则取决于它的根部Activity。

多个task可以拥有相同的taskAffinity,但最近列表只会展示最新展示过的那一个task

taskAffinity在两种情况下起作用:

知道taskAffinity是标记作用,就很好理解下面的情况了。

  • 当启动Activity的Intent中带有FLAG_ACTIVITY_NEW_TASK标志时。

在默认情况下,目标Activity将与startActivity的调用者处于同一task中。但如果用户特别指定了FLAG_ACTIVITY_NEW_TASK,表明它希望为Activity重新开设一个Task。这时就有两种情况:

  1. 假如当前已经有一个Task,它的affinity与新Activity是一样的,那么系统会直接用此Task来完成操作,而不是另外创建一个Task;
  2. 否则系统需要创建一个Task。
  • 当Activity中的allowTaskReparenting属性设置为true时。

在这种情况下,Activity具有"动态转移"的能力。举个前面的"短信"例子,在默认情况下,该应用程序中的所有Activity具有相同的affinity。
当另一个程序启动了"短信编辑"时,一开始这个Activity和启动它的Activity处于同样的Task中。但如果"短信编辑"Activity指定了allowTaskReparenting,且后期"短信"程序的Task转为前台,此时"短信编辑"这一Activity会被"挪"到与它更亲近的"短信"Task中。

注意:allowTaskReparenting在安卓9.0和10.0可能有问题

启动模式

在Android中每个界面都是一个Activity,切换界面操作其实是多个不同Activity之间的实例化操作。在Android中Activity的启动模式决定了Activity的启动运行方式。

有两种方式来声明指定启动模式:

  1. 在清单文件中
  2. 在java代码中

如果 Activity A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B 应该如何与当前任务关联。

如果这两个方式均定义 Activity B 应该如何与任务关联,则 Activity A 的请求(Intent 中所定义)优先级要高于 Activity B 的请求(其清单文件中所定义)。

本篇只讲在清单申明的情况

在清单文件中

不管何种方式启动Activity,被启动的Activity通常都要位于ActivityStack的栈顶。

Activity启动方式launchMode,在清单文件中配置。

<activity android:name=".MainActivity"

android:launchMode="xxx" />

您可以分配给 launchMode 属性的启动模式共有四种:

"standard"(默认模式)

  • 概述:

和任务栈的taskAffinity没有直接相关,启动一次,就会在当前任务栈直接新创建一个实例

也就是说Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个该activity实例。

"singleTop"

  • 概述:

和"standard"模式类似,只更当前任务栈相关,唯一一点不同是,当要启动的Activity和当前任务栈栈顶的Activity一样时,只调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。

例如,假设任务堆栈是 A-B-C-D;D 位于顶部。收到针对 D 类 Activity 的 Intent,

  • 如果 D 具有默认的 "standard" 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。
  • 如果 D 的启动模式是 "singleTop",则 D 的现有实例会通过 onNewIntent() 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 A-B-C-D。
  • 如果收到针对 B 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 "singleTop" 也是如此。

"singleTask"

  • 概述:

这种模式和taskAffinity息息相关,整体逻辑流程如下:

  • 首先根据要启动的Acitivity的taskAffinity找任务栈,分三种情况

    1. 已存在对应taskAffinity的任务栈,而且就是当前任务栈
    2. 已存在对应taskAffinity的任务栈,不是当前任务栈
    3. 不存在对应taskAffinity的任务栈。则创建新的任务栈
      注:这里2,3情况,之后任务栈会叠成栈,这种方式只适用于前台task
  • 然后在找到对应任务栈以后,就会在栈里面找是否已经有这个Activity了,分成两种情况
    a. 已经有了,则把它弹到栈顶(意思是,它如果不在栈顶,就把现在栈顶的一个个弹出,直到它到栈顶为止),并且调用onNewIntent() 方法向其传送 Intent,而不是创建新实例
    b. 没有的话,则创建新的实例压栈

下图这种就是 2a组合的情况:


singleTask Mode.png

对于这个模式,有个回退路变更问题

  • 回退路径,上面提到了任务栈的叠加,因为在切换后台的时候,叠加任务栈会被立刻拆分,所以前台task就只有一个了,回退路径就会发生变化。
    这个时候可以考虑属性allowTaskReparenting,希望创建新任务并实例化Activity,当然会受到taskAffinity的影响,即如果已经有一个和要启动的Activity的taskAffinity相同的task,那么就在这个task中创建实例,相反就是创建一个新任务。并且会将这个任务栈压到当前的任务栈

"singleInstance"

  • 总结

这个模式最复杂,该模式下,与 "singleTask" 类似,只是条件更严苛一点。就是此activity独占一个任务栈,而之所以能保证它自己一个独占一个任务栈,是因为多个任务栈可以拥有同一个taskAffinity

一旦该模式的Activity的实例存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。

singleTask/singleInstance的特别说明

从ActivityStack源码可以看出,singleTask/singleInstance模式会clearTop的效果,

} else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
        || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
        || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {

从ActivityStack源码可以看出,singleTask/singleInstance模式会自动添加FLAG_ACTIVITY_NEW_TASK

} else if (mLaunchSingleInstance || mLaunchSingleTask) {
    // The activity being started is a single instance...  it always
    // gets launched into its own task.
    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}

总结

这里面最好理解的就是standard和singleTop两个模式了,而singleTask和singleInstance两个模式必须先要理解 Task和takeInfinity这两个概念,然后搞清楚singleTask流程就行,singleInstance就只是加了一个独占task的条件。

整体结构就这些,看完这个,再去看b站 抛物线的视频,相信会很容易理解了,结构也更加清晰了

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

推荐阅读更多精彩内容

  • 最近在重新整理Activity的启动模式,顺便也扒了扒任务栈Task,接着又去了解了下Android的概览屏幕,把...
    Find_A_Way阅读 508评论 0 1
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,520评论 16 22
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,562评论 0 11
  • 可爱进取,孤独成精。努力飞翔,天堂翱翔。战争美好,孤独进取。胆大飞翔,成就辉煌。努力进取,遥望,和谐家园。可爱游走...
    赵原野阅读 2,725评论 1 1
  • 在妖界我有个名头叫胡百晓,无论是何事,只要找到胡百晓即可有解决的办法。因为是只狐狸大家以讹传讹叫我“倾城百晓”,...
    猫九0110阅读 3,260评论 7 3