此外,这篇文章的背景来自于周五的时候,有一个小伙伴私聊我一个问题。说实话让我“颇为震惊”:
- 1、问题看起来很简单,关于Activity启动模式…但是的确里边的细节触及了我的知识盲区。
- 2、这个小伙伴发了一个详细的pdf文档,看起来也是经常总结~点赞这种学习态度~
这里在分析启动模式的基础上,或回答他的问题,或验证他的猜想。主要集中在这几个地方,大家也可以在看文章的时候先问问自己能不能回答出啦:
问题一:
验证部分:2.3
问题二:
这是一个很有趣的现象,估计大家都没有注意到吧~解答部分:2.1
问题三:
这里的答案是上面问题的一个延伸,也会在2.1部分中解释。
问题四:
这个问题应该也是众多小伙伴感到“有悖常识”的地方,其实官方文档已经解答了这个问题。解答部分2.4
正文一、理解概念
在学习Ativity的过程中,Task的概念多少是我们无可回避的概念。从官方文档中我们基本能够理解明白Task和Stack的关系…
一张很经典的图:
Activity以Task的概念聚合在一起,而且无论是单一Task中的多个Activity还是多个Task混在一起,它们都是以Stack形式所包含在一起。
但是,不知道有多少小伙伴在dumpsys activity之后陷入了另一个疑惑:TaskRecord是啥?
1.1、Stack、Task、TaskRecord?
其实Task和TaskRecord是同一个概念。TaskRecord是framework层的一个类。它就是Task这个抽象概念的代码实现。让我们看一个dumpsys的图:
使用命令:adb shell dumpsys activity
使用命令:adb shell dumpsys activity activities | sed -En -e '/Stack #/p' -e '/Running activities/,/Run #0/p'
这两幅图是同一个Activity栈的场景,但是可以看出来TaskRecord和Task是同一个概念。
1.2、launchModel
启动模式,这个咱们都很熟悉,张口就来:
- standard
- singleTop
- singleTask
- singleInstance
关于它们的概念,这里就不做累述,毕竟复制粘贴也没有任何意义。(后文会详细阐明四种模式和taskAffinity以及各种Flags的作用)但是这里特别贴一张官方的图,大家感受一下:
这代表了Google对singleTask以及singleInstance的态度,也就是说这俩种模式并不是设计给主流Activity的,因此使用这俩种模式前需要真正的理解它们。
二、从demo中彻底理解
demo很简单定义了5个Activity:
<activity android:name=".test.TestMainLauncherActivity" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".test.TestSingleInstanceLauncherActivity" android:launchMode="singleInstance"/> <activity android:name=".test.TestTopAffinityLauncherActivity" android:launchMode="singleTop" android:taskAffinity="app.mdove"/> <activity android:name=".test.TestSingleTaskLauncherActivity" android:launchMode="singleTask" /> <activity android:name=".test.TestSingleTaskAffinityLauncherActivity" android:taskAffinity="app.mdove.singletask" android:theme="@style/MyAppTheme" android:launchMode="singleTask" />
2.1、理解taskAffinity
首先,咱们看一下文档对android:taskAffinity的描述:
与 Activity 有着相似性的任务。从概念上讲,具有同一相似性的 Activity 归属同一任务(从用户的角度来看,则是归属同一“应用”)。任务的相似性由其根 Activity 的相似性确定。
相似性确定两点内容 :
- Activity 更改父项后的任务(请参阅 allowTaskReparenting 属性,这个属性比较有意思,但是咱们先按下不表)
- 以及通过 FLAG_ACTIVITY_NEW_TASK 标记启动 Activity 时,用于容纳该 Activity 的任务。
仅从文档的描述来看,似乎有些雨里雾里。所以接下来咱们通过实际代码来确认taskAffinity的含义。
这里我先贴结论:
1、taskAffinity单独设置在standard、singleTop是没有任何意义的。因为taskAffinity的效果依赖启动Activity时使用FLAG_ACTIVITY_NEW_TASK标签。(也就是上文官网提到的那一条)
2、以taskAffinity模式启动成功后,新的Activity会处在新的Task。并且由此启动的Activity(无taskAffinity)皆处于此Task。
接下来咱们用户实际效果验证这个结论(操作路线):
- TestMainLauncherActivity启动TestTopAffinityLauncherActivity
- 然后在TestTopAffinityLauncherActivity上启动TestMainLauncherActivity
- 在TestMainLauncherActivity上通过FLAG_ACTIVITY_NEW_TASK标签,启动TestTopAffinityLauncherActivity
- 最后由TestTopAffinityLauncherActivity启动TestMainLauncherActivity
不知道大家能否从脑海里,构建出Task的结构?这里我贴一下dumpsys的图:
简单解释一下:
1、看一下Task #21,由于我们启动TestTaskAffinityLauncherActivity时没有增加FLAG_ACTIVITY_NEW_TASK,因此它的Task并没有变化。
2、在Task #22中,TestTaskAffinityLauncherActivity通过FLAG_ACTIVITY_NEW_TASK直接进入了新的Task中,并且后续启动的Activity也和它“共处一室”。
咱们看一个很有趣的现象(解答问题二)
如果我们通过FLAG_ACTIVITY_NEW_TASK taskAffinity启动了一个Activity1,并且用这个Activity1启动一个普通的Activity2,如果此时在Activity2上启动Activity1,会发现没有任何反应!!
其实不能说是没反应,“系统的反应”是吧Activity1所在的task移到了前台。说时候这个细节我一直没有注意到,要不是这个小伙伴提醒,我可能很长一段时间都不会注意到这个问题。
复现了这个现象的时候,我也很懵逼。第一想法是翻一翻文档,是不是文档早有提示…然后大失所望,并没有发现任何关于此现象的解答。
既然文档没有解释,那咱们就翻一翻源码…不过我刚点击去FLAG_ACTIVITY_NEW_TASK,有看到了大段的注释,其中有这么一段话:
这里注释着:如果这个Task中run了你要satrt的Activity,那么将不会起一个新的Activity,而是将此Task移至前台。这个注释正是我们遇到的现象…
而对于FLAG_ACTIVITY_NEW_TASK来说,它本质是去找要启动的taskAffinity所声明的Task,如果没有,它的处理方式和普通起一个Activity没有本质区别,所以就不会做特殊处理。
那么问题来了:为啥要这么设计的?实话我也不知道…我猜应该是为了提供这么一种能力,毕竟PM的想象力是无限的。(如果我们想要在这种情况下,显示要start的Activity,别忘了还有FLAG_ACTIVITY_CLEAR_TOP呢)
2.2、理解singleTask
singleTask下,只有设置了taskAffinity才会为对应启动的 Activity创建一个新的Task。并且后续的Activity同进入这个Task。
我相信这个大家都很熟悉…
2.3、理解singleInstance
大家对singleInstance,应该都比较清晰。因为比较特殊:永远独享一个Task。
这里我们看俩个有意思的特性:
1、以通过singleInstance启动的Activity会默认带上FLAG_ACTIVITY_NEW_TASK。因此如果通过singleInstance启动带有taskAffinity的singleTop,会起新的Task。
这里有一个细节:仅带有FLAG_ACTIVITY_NEW_TASK,不带有taskAffinity。是会将启动的Activity放到根栈里。因此这里会出现一个细节:
那就是如果由singleInstance启动一个Activity,由于会放入根栈,那么此时根Task会被拉到前台,那么此时back键就会显示根Task下面的Activity,也就是如下的情况:
此时back掉TestMainLaunncherAcctivity,显示的仍然是TestMainLaunncherAcctivity。因为他们同属一个#43的栈。
2.4、FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP = singleTask?
很多博客/资料都会提到一个说法:FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP = singleTask。
但是这句话并不全对,这不是我说的,而是文档说的:
注意红线圈住的内容:也就是意味着用这俩个FLAG对于standard模式来说,并不会触发onNewIntent(),而是销毁重建。
尾声OK,这篇文章到此就结束了…所以叭叭整了很多内容,但是从Google的态度也能看出来,singleTask已经singleInstance是一种慎重使用的提示。
似乎standard和singleTop已经可以满足我们的需求了…不过,似乎真的有点小瞧四种启动模式了…
,