深入理解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
启动的activity
的task
栈中打开一个document
,和documentLaunchMode效果相等,有着不同的documents
的activity
的多个实例,将会出现在最近的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
的权限。当使用于Intent
的ClipData
时,所有的URIs
和data
的所有递归遍历或者其他Intent
的ClipData
数据都会被授权。
同上,只是相应的赋予的是写权限
当发送广播时,允许其接受者拥有前台的优先级,更短的超时间隔。
如果这是一个有序广播,不允许接受者终止这个广播,它仍然能够传递给下面的接受者。
如果设置了这个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
移到另一个App
的task
中。
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开发相关的技术干货,期待与你的交流,共勉。