深入理解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官方文档

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

  • FLAG_ACTIVITY_SINGLE_TOP
    等同于SingleTop

  • FLAG_ACTIVITY_NEW_TASK
    等同于SingleTask

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

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

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


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

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

  • FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
    在API 21中废弃,现使用FLAG_ACTIVITY_NEW_DOCUMENT

  • FLAG_ACTIVITY_FORWARD_RESULT
    官方文档中是这么解释的:如果设置,并且这个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()

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

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

  • FLAG_ACTIVITY_MATCH_EXTERNAL

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

  • FLAG_ACTIVITY_MULTIPLE_TASK
    这个标识用来创建一个新的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栈回到前台的功能。

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

  • FLAG_ACTIVITY_NO_ANIMATION
    禁用activity间的切换动画

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

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

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

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

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

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

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

  • FLAG_EXCLUDE_STOPPED_PACKAGES

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

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

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

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

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

  • FLAG_INCLUDE_STOPPED_PACKAGES

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

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

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

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

  • FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
    设置之后,广播将对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开发笔记

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值