深入理解Activity的启动模式

总结一下启动模式,以便日后回顾,整理自官方文档

前言

Activity的启动模式很重要,与回退栈以及重复实例息息相关,此文将就启动模式介绍以下内容:

  • LaunchMode
  • Intent flag
  • TaskAffinity

前置知识

  • Activity采用栈式管理(任务与回退栈/Tasks and Back Stack)
  • 那么任务和回退栈分别是什么?
    • 任务是用户在执行某项任务时与之交互的一系列活动,可以理解为活动集合。
    • 而活动按照被打开的顺序放在堆栈中,我们称之为回退栈
    • 回退栈采用 先进后出 的栈式结构
    • 每按下back时,栈顶Activity弹出。
    • 栈中的活动不会被重新排列,只有在活动启动和销毁时才从堆栈中推送和弹出。
示意图

LaunchMode

LaunchMode的类别:

如看不下去= =,可直接跳至下方四种模式的异同

1. standard 标准模式

默认的启动方式,特点是每次启动时,都会创建活动的新实例,所有该活动的实例位于同一个task栈,遵循first in last out。

2. singleTop 栈顶复用

和standard很类似的启动方式,也可以创建很多实例,特点是如果启动目标Activity时,前台已经有一个目标Activity的实例(即目标Activity处在task栈顶),则会重用该目标Activity实例,而非创建新实例。同时这个重用的实例,会接收到一个Activity.onNewIntent()的调用,以此获取新的Intent
这个模式使用场景其实很少,通常只会避免相同程序的重复启动,而不同程序间的跳转情况与stardard完全一致,给定以下情景():

  • 消息推送:目前我们页面停留在Activity A中,此时通知栏弹出Notification,点击Notification再次启动Activity A,那么为了避免Activity的重复打开,以及按下back时,回退到"重复页面",则需要将该Activity设置为singleTop,并且重写onNewIntent()来处理新请求。

3. singletask 栈内复用

该模式下的Activity在系统中只会有一个实例,如果启动时,task栈中存在一个该活动的实例,则会复用该活动实例,并将task栈中该实例之上的activity全部出栈(销毁过程中会调用Activity的生命周期回调方法)。同样的,通过onNewIntent()方法接收新的Intent

4. singleInstance 单例模式

和singleTask类似,系统中只会存在一个活动实例。区别在于singleInstance模式下的Activity所处的task栈中仅存在该Activity一个实例,如果启动其他任何Activity,那么都会在另一个task栈中启动对应的Activity;而对于重复启动自身时,则会复用原Activity,同样通过onNewIntent()方法接收新的intent

四种模式的异同:

四种模式的异同

该图摘自大佬Carson_Ho《Android基础:最易懂的Activity启动模式详解》一文,若我表述不清,可移步原文

LaunchMode的设置方法

在Manifest的Activity配置中进行设置

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="yourPackage">

    <application
        ...>
        <activity android:name="..."
            android:launchMode="standard/singleTop/singleTask/singleInstance">

           
        </activity>
    </application>

</manifest>

至此关于LaunchMode的部分就介绍完了。


Intent Flag

通过Intent.addFlags()设置标志位是另一种设置启动模式的方式,和LuanchMode方式有相交的部分。

注意:LaunchMode和Intent flags的优先级问题:
Intent设置方式的优先级 > Manifest设置方式

更多详见Google官方文档

首先我们先介绍以下最常见的几个标记位属性

等同于SingleTop

等同于SingleTask

清除位于其上层的所有Activity,与singleTask及其类似。区别在于:
当此标记不搭配FLAG_ACTIVITY_SINGLE_TOP标记时,会将已有的实例销毁重建。
而搭配FLAG_ACTIVITY_SINGLE_TOP标记时则会复用已有的实例,并通过onNewIntent()方法得到新的intent

设置后,新Activity不会出现在recent apps里,即无法通过历史列表回到该Activity。等同于在XML中指定Activity的属性android:excludeFromRecents="true"

类似singleTask,区别在于当重复启动目标活动时,不会将位于原活动之上的其他活动出栈,只是将原活动“置顶”。


以下部分好像没那么"常用",至少很少看到有博客总结以下的标记位属性。

启动目标Activity时传递这个标记,则会导致所处的task栈被清空,然后在清空在之后的task栈中启动目标Activity,也就是说,目标Activity成为这个空task栈的root Activity该标志必须配合FLAG_ACTIVITY_NEW_TASK一起使用。

在API 21中废弃,现使用FLAG_ACTIVITY_NEW_DOCUMENT

官方文档中是这么解释的:如果设置,并且这个Intent用于从一个存在的Activity启动一个新的Activity,那么,这个作为答复目标的Activity将会传到这个新的Activity中。这种方式下,新的Activity可以调用setResult(int),并且这个结果值将发送给那个作为答复目标的Activity。
为了理解下面举个例子:Activity A 通过startActivityForResult()启动Activity B,之后Activity B 同样通过startActivityForResult(),但附加FLAG_ACTIVITY_FORWARD_RESULT的flag来启动Activity C。此时将会由C向AsetResult()

一般由系统调用,比如长按home键从历史记录中启动。

此标志仅用于分屏多窗口模式。new Activity显示在启动它的活动(old Activity)的旁边。只能与FLAG_ACTIVITY_NEW_TASK一起使用。另外,如果需要创建现有活动的新实例,则需要设置FLAG_ACTIVITY_MULTIPLE_TASK。

Android P Developer Priview中加入
设置后,如果设备上没有能够处理该intent的app,那么将会启动一个instant app来进行处理。

这个标识用来创建一个新的task栈,并且在里面启动新的activity(所有情况,不管系统中存在不存在该activity实例),经常和FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK一起使用。这上面两种使用场景下,如果没有带上FLAG_ACTIVITY_MULTIPLE_TASK标识,他们都会使系统搜索存在的task栈,去寻找匹配intent的一个activity,如果没有找到就会去新建一个task栈;但是当和FLAG_ACTIVITY_MULTIPLE_TASK一起使用的时候,这两种场景都会跳过搜索这步操作无条件的创建一个新的task。和FLAG_ACTIVITY_NEW_TASK一起使用需要注意,尽量不要使用该组合除非你完成了自己的顶部应用启动器,他们的组合使用会禁用已经存在的task栈回到前台的功能。

api 21之后加入的一个标识,用来在intent启动的activitytask栈中打开一个document,和documentLaunchMode效果相等,有着不同的documentsactivity的多个实例,将会出现在最近的task列表中。单独使用效果和documentLaunchMode="intoExisting"一样,如果和FLAG_ACTIVITY_MULTIPLE_TASK一起使用效果就等同于documentLaunchMode="always"

禁用activity间的切换动画

Activity不在回退栈中保留,一旦退出就销毁,等同于设置noHistory属性。
该方法会导致onActivityResult()失效,毕竟没有返回的结果了嘛。

禁止activity调用onUserLeaveHint()onUserLeaveHint()作为activity周期的一部分,它在activity因为用户要跳转到别的activity而退到background时使用。比如,在用户按下Home键(用户的操作),它将被调用。比如有电话进来(不属于用户的操作),它就不会被调用。注意:通过调用finish()时该activity销毁时不会调用该函数。

设置之后,再次重新启动一个存在的Activity时,新的Activity会立即finish掉,原本的Activity则会作为栈顶Activity使用。

这个标记在以下情况下会生效:1.启动Activity时创建新的task来放置Activity实例;2.已存在的task被放置于前台。系统会根据affinity对指定的task进行重置操作,task会压入某些Activity实例或移除某些Activity实例。我们结合上面的FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET可以加深理解。

默认情况下通过FLAG_ACTIVITY_NEW_DOCUMENT启动的activity在关闭之后,task中的记录会相对应的删除。如果为了能够重新启动这个activity你想保留它,就可以使用这个flag,最近的记录将会保留在接口中以便用户去重新启动。接受该flag的activity可以使用autoRemoveFromRecents去复写这个request或者调用Activity.finishAndRemoveTask()方法。

把当前新启动的任务置于Home任务之上,也就是按back键从这个任务返回的时候会回到home,即使这个不是他们最后看见的activity,注意这个标记必须和FLAG_ACTIVITY_NEW_TASK一起使用。

如果设置了这个flag,那么在处理这个intent的时候,将会打印相关创建日志。

用来标识该intent的操作是一个后端的操作而不是一个直接的用户交互。

当和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用时,uri权限在设置重启之后依然存在直到用户调用了revokeUriPermission(Uri, int)方法,这个标识仅为可能的存在状态提供许可,接受的应用必须要调用takePersistableUriPermission(Uri, int)方法去实际的变为存在状态。

当和FLAG_GRANT_READ_URI_PERMISSION 和/或FLAG_GRANT_WRITE_URI_PERMISSION一起使用时,uri的许可只用匹配前缀即可(默认为全部匹配)。

如果设置FLAG_GRANT_READ_URI_PERMISSION这个标记,Intent的接受者将会被赋予读取Intent中URI数据的权限和ClipData中的URIs的权限。当使用于IntentClipData时,所有的URIsdata的所有递归遍历或者其他IntentClipData数据都会被授权。

同上,只是相应的赋予的是写权限

当发送广播时,允许其接受者拥有前台的优先级,更短的超时间隔。

如果这是一个有序广播,不允许接受者终止这个广播,它仍然能够传递给下面的接受者。

如果设置了这个flag,当发送广播的时,动态注册的接受者才会被调用,在AndroidManifest.xml里定义的Receiver 是接收不到这样的Intent的。

如果设置了的话,ActivityManagerService就会在当前的系统中查看有没有相同的intent还未被处理,如果有的话,就由当前这个新的intent来替换旧的intent,所以就会出现在发送一系列的这样的Intent 之后,中间有些Intent 有可能在你还没有来得及处理的时候, 就被替代掉了的情况

设置之后,广播将对instant app中的广播接收器可见。默认不可见。


taskAffinity

每个Activity都有一个taskAffinity属性,用于指定活动具有"亲和力"的task名称,简单来说就是指出该活动希望进入的task。该属性默认为包名,除非Application或者Activity设置该属性。
taskAffinity属性必须要与singleTask启动模式或者allowTaskReparenting属性配对使用,否则没有意义。

allowTaskReparenting属性表明是否允许该Activity更换从属task

  • taskAffinity + singleTask
    启动Activity时,首先检查是否存在与自己的taskAffinity相同的task,如果存在,那么将会把该Activity放入该task中;如果不存在,则新建taskAffinity指定的task。
  • taskAffinity + allowTaskReparenting
    用于实现把一个App里的Activity移到另一个Apptask中。

taskAffinity设置方法

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourpackagename">

    <application
        ...>
        <activity 
            ...
            android:taskAffinity="com.example.yourtaskname"
            android:allowTaskReparenting="true/false"
            >
            ...
        </activity>
    </application>

</manifest>

引用


总结

  • 本文尽可能详细的对Activity的启动模式做出介绍,包括LaunchMode 四种模式的对比,Intent Flags各种标志的用法,以及对于taskAffinity的介绍。
  • 笔者水平有限,如有错漏,欢迎指正。
  • 接下来我也会将所学的知识分享出来,有兴趣可以继续关注whd_Alive的Android开发笔记

欢迎关注whd_Alive的简书

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

推荐阅读更多精彩内容