我以为理解了Android四大启动模式,直到被打脸

4,528 阅读7分钟

前言

这篇文章的背景来自于周五的时候,有一个小伙伴私聊我一个问题。说实话让我“颇为震惊”:

  • 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启动带有taskAffinitysingleTop,会起新的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已经可以满足我们的需求了...不过,似乎真的有点小瞧四种启动模式了...

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身