终于懂了之 Activity 的四种启动模式

前言

Android 开发者几乎都知道 Activity 有四种启动模式(standard,singleTop,singleTask,singleInstance)。当然,对于刚开始接触 Android 的开发者,可能一开始不了解或不理解,但是工作个一年两年以后,肯定会接触到这个知识点的。网上有太多太多这方面的文章,但是或多或少都不那么准确(当然也有准确的,只是我看到的文章有限……)。最近把四种启动模式,taskAffinity 属性及 FLAG_ACTIVITY_CLEAR_TOP,FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_SINGLE_TOP 这三种Flag 放在一起,仔细的研究了一下,终于弄懂了它们之间错综复杂的关系。

什么是任务(Task)

首先,我们先来简单了解一下在 Android 应用程序中,任务(Task)是个什么样的概念。我们知道,Activity 是 Android 应用程序的四大组件之一,在应用程序运行时,用户为了完成某个功能而执行的一系列操作(页面跳转)就形成了一个 Activity 序列,这个序列在 Android 应用程序中就称之为任务,它是从用户体验的角度出发,把一组相关的 Activity 组织在一起而抽象出来的概念。

Task 是一组相互关联的 Activity 的集合,它是存在于 framework 层的一个概念,控制界面的跳转和返回。这个 Task 存在于一个称为 back stack的数据结构中,也就是说,framework 是以栈的形式管理用户开启的 Activity。这个栈的基本行为是,当用户在多个 Activity 之间跳转时,执行压栈操作,当用户按返回键时,执行出栈操作。

对初学者来说,在开发 Android 应用程序时,对任务的概念可能不是那么的直观,一般我们只关注如何实现应用程序中的每一个 Activity。事实上,Android 系统中的任务更多的是体现是应用程序运行的时候,因此,它相对于 Activity 来说是动态存在的,这就是为什么我们在开发时对任务这个概念不是那么直观的原因。

不同的应用的 Activity 可以在同一个任务里,也就是说 Task 是跨应用的,这样保证了用户操作的连贯性,用户体验比较好。不同任务之间切换,界面会闪一下,不连贯。

神奇的 taskAffinity

不过,我们在开发 Android 应用程序时,可以配置 Activity 的任务属性的,即告诉系统,这个 Activity 是要在新的任务中启动呢,还是在已有的任务中启动,亦或是其它的 Activity 能不能与这个 Activity 共享同一个任务,这个神奇的属性就是 taskAffinity。

在 AndroidManifest.xml ,我们通过属性

android:taskAffinity="july.com.tempdemo.xxx"

可以为每个Activity 设置其 taskAffinity。如果不设置,默认情况下,所有的 Activity 的taskAffinity 都一样,都为app的包名。

这里关于 taskAffinity 我们记住两点就可以:

    1. 一个任务(Task)的 affinity 由这个任务的根 Activity(root activity)的 taskAffinity 决定;
    1. 当 Activity 以FLAG_ACTIVITY_NEW_TASK 标志启动时,根据 taskAffinity 来决定它会被启动到哪个任务中。

三个Flag

Intent 类里定义了很多 FLAG,这里我只说三种:

  • FLAG_ACTIVITY_NEW_TASK
    仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);启动的 Activity 即使 task 里已经有实例了,依然会在栈顶创建一个新的实例。

  • FLAG_ACTIVITY_SINGLE_TOP
    仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);能够实现 singleTop 启动模式的效果,也就是仅在当前栈里判断实例是否处于栈顶,在栈顶就复用,否则新建一个实例。

  • FLAG_ACTIVITY_CLEAR_TOP
    清除目标 Activity上方所有的 Activity,如果目标 Activity 是 standard 的启动模式且启动时 intent 里没有设置FLAG_ACTIVITY_SINGLE_TOP这个flag,那么栈里的目标 Activity 也会被 finish 掉,重新创建一个实例。
    如果目标 Activity 是非 standard 的启动模式或者启动时设置了FLAG_ACTIVITY_SINGLE_TOP,就不会清除目标 Activity,执行其 onNewIntent() 方法。

Flag 组合设置:

  • 仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);这两行代码来启动的 Activity,如果栈里已有 Activity 的实例,那么清空此 Activity 及其以上的 activites,然后新建一个实例。

  • 仅仅通过intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);能够达到跟 singleTask 启动模式的效果,如果栈里已有实例,则清空其上的 activies,然后走 onNewIntent()。

Activity 的四种启动模式

我们在 AndroidManifest.xml 里为 Activity 设置 launchMode:


set launchMode.png

standard 启动模式

这是 Activity 默认的启动模式,这种模式下,每次 startActivity 都会在栈顶创建一个新的实例,在同一个任务中可以存在多个Activity 的实例。

singleTop 启动模式

栈顶复用,也就是说,要启动 singleTop 模式的 Activity,如果它恰好在当前栈顶,那么直接复用,执行其 onNewIntent 方法。否则,就重新创建一个实例入栈。

singleTask 启动模式

设置了"singleTask"启动模式的 Activity,在系统中只有一个实例,当再次启动该 Activity 时,会重用已存在的任务和实例,并且会调用这个实例的 onNewIntent()方法,将 Intent 实例传递到该实例中。

设置了"singleTask"启动模式的 Activity 的特点(划重点):
它在启动的时候,会先在系统(所有现存的tasks)中查找属性值 affinity 等于它的属性值 taskAffinity 的任务是否存在;

  • 如果存在这样的任务,那么继续在这个任务中查看是否已经存在相应的 Activity 实例。

    • 如果存在实例,则会复用这个任务跟实例,finish 掉它上面的所有的 activity ,使其处于栈顶,并执行其 onNewIntent() 方法。
    • 如果不存在实例,则在这个任务栈顶创建一个实例。
  • 如果不存这样的任务,那么就会以它的 taskAffinity 新创建一个任务,并在任务里创建一个实例。

因此,如果我们想要设置了 "singleTask" 启动模式的 Activity 在新的任务中启动,就要为它设置一个独立的 taskAffinity 属性值。

下面举几个例子,来体验一下singleTask 启动模式。测试 demo 下载地址:https://github.com/JulyDev/ActivityLaunchMode
ps: 这个 demo 改了很多次,想到什么情况就改一下,来验证自己的想法。因为随机组合条件太多了,不可能一一保存下来。所以最好自己写个 demo 亲自验证一下。

例子1. 有三个 Activity,MainActivity,SecondActivity, ThirdActivity, 其中SecondActivity设置为 singleTask ,其他都是standard启动模式,并且onClick事件里启动时也不加任何 Flag 或 Action。然后 MainActivity 启动SecondActivity,SecondActivity 再启动 ThirdActivity。

<activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:launchMode="singleTask"
            >
        </activity>
        <activity
            android:name=".ThirdActivity"
            >
        </activity>

启动 app,依次打开这三个 Activity,结果如下:


singleTask only.png

可以发现,设置为 singleTask 的 SecondActivity,是跟 MainActivity 在同一个 task 里。为什么呢?因为都没有设置 taskAffinity 啊,默认都是包名,也就是相同的,那么启动 SecondActivity 时,根据 taskAffinity ,找到了MainActivity 所在的task,这里面是没有 SecondActivity 实例的,于是创建一个实例入栈。

例子2. 现在,在例子1的基础上,给 SecondActivity 设置一个不同的 taskAffinity :

<activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:launchMode="singleTask"
            android:taskAffinity="july.com.tempdemo.second"
            >
        </activity>
        <activity
            android:name=".ThirdActivity"
            >
        </activity>

测试结果如下:


singleTask && taskAffinity.png

可以看到,此时 SecondActivity 新建了一个 task,不同于 MainActivity 所在的 task。
仔细看我们还发现,由 SecondActivity 启动的ThirdActivity 跟 SecondActivity 在同一个 task 里面。

singleInstance 启动模式

总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他 Activity 会自动运行于另一个任务中。当再次启动该 Activity 的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将 Intent 实例传递到该实例中。

设置了 singleInstance 的 Activity,整个系统只有一个实例,独占一个栈,且由它启动的 Activity 根据目标 Activity 的 taskAffinity 来选择进哪个 task,若不存在对应的 task,则新建一个 task 并新建一个目标 Activity 的实例入栈。
举例:
ThirdActivity 设置为 singleInstance 模式。其他都为 standard模式。MainActivity 启动 SecondActivity,SecondActivity 启动 ThirdActivity,ThirdActivity 又启动 SecondActivity。

<activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            >
        </activity>
        <activity
            android:name=".ThirdActivity"
            android:launchMode="singleInstance"
            >
        </activity>
        

测试结果如下:


singleInstance.png

可以看到,ThirdActivity 单独在一个 task 里,而由 ThirdActivity(singleInstance) 启动的 SecondActivity,根据 affinity 找到了 MainActivity 所在的 task,并且直接在栈顶创建一个实例(不管栈里是否已经存在 SecondActivity 的实例)。这里跟 singleTask 模式不一样。设置了 singleTask 模式的 Activity, 启动的普通 Activity ,直接就放在当前栈顶了(见singleTask 启动模式一节的例子2)。

参考资料:
1.http://m.blog.csdn.net/luoshengyang/article/details/6714543

2.http://blog.csdn.net/zhangjg_blog/article/details/10923643

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

推荐阅读更多精彩内容