启动模式
在讲解intent之前讲一下activity的四种启动模式
1.standard
默认的启动模式,就是拉起一个activity,每次启动一个activity都会重新创建一个实例。但是如果用applicationContext启动standard模式的activity会报错,因为standard模式的activity会默认进入启动它的activity栈中,但是applicationContext都不是activity所以也没有栈,所以就报错了。解决办法就是添加FLAG_ACTIVITY_NEW_TASK方式启动,启动一个新的栈
同时还存在一个弊端,假设这是一个button,点击拉起activityA,如果用户快速点击两次button,就会连续拉起两次Activity
2.singleTop
栈顶复用模式,在这种模式下,如果新的activity已经位于任务栈的栈顶,那么此activtiy不会被重复创建,同时会回调它的onNewIntent和onResume,注意此activtiy的onStart和onCreate也不会被调用。这种启动方式可以规避快速双击启动两次activity的异常。
但是还是存在一个弊端,假设A启动了B,B再启动A,如果A是singleTop模式,就会出现两个实例A,原因很简单,因为B这时候是栈顶,A不在栈顶所以singleTop就无法生效了
3.singleTask
栈内复用模式,单例模式,在这种模式下
(1)只要activityA在一个栈中,无论怎么启动activityA都只会存在一个,若activityA不在栈顶,那么出栈所有activity(clearTop的效果),直到A到栈顶,然后调用onNewIntent;若activityA在栈顶,就和singletop一样的逻辑,调用onNewIntent。
(2)如果activityA不在任何一个栈,那么系统会寻找A想要的任务栈,如果不存在任务栈,就创建一个新的任务栈并添加进入,如果存在此任务栈,就不会创建新的任务栈,直接push进去 ,所以singleTask模式不一定开启新栈哦
4.singleInstance
全局单例模式,上述讲到的singleTask仅仅是针对一个栈中的单例,假设程序运行有多个activity栈,那么singleTask就不能保证这个activity是全局单例的,这时候singleInstance就出来了,这是一个加强的singleTask,且此模式下的activity会单独存在一个栈中,这个栈也仅仅保存这个全局单例的activity。singleInstance官方建议也是少用,因为栈的复杂性会导致很多不可控因素
TaskAffinity和allowReparenting
taskAffinity:亲和栈属性,和上述说的singleTask和后面会介绍的标记位FLAG_ACTIVITY_NEW_TASK搭配使用,因为启动singleTask的activtiy的时候如果此activity不存在,就会判断需求栈,怎么判断需求栈呢?就是通过判断启动的activity的taskAffinity和被启动的activity的taskAffinity,如果不一样就开启新栈(一般情况下如果不写taskAffinity就默认是包名,一个任务的affinity决定于这个任务的根activity的taskAffinity)
<activity
android:name=".intentTest.SingleTaskActivity"
android:launchMode="singleTask"
android:exported="false"
android:taskAffinity="com.example.othertask"/>
上述的写法拉起singleTaskActivity就会开启一个新栈
allowReparenting:设置了true,比如两个应用A和B,A拉起来B应用里的activityC,然后按home键回到了桌面,再点击B应用会重新显示了被A启动的activityC,或者说,activtiyC从A的任务栈转移到了B的任务栈中。可以这么理解,由于A启动了ActivityC,这个activityC存在于A的任务栈中,但是activityC属于B应用,正常情况下,它的taskAffinity肯定不可能和A相同(如果启动C没有加FLAG_ACTIVITY_NEW_TASK,就是相同的栈)。所以当B被启动后,B会创建自己的任务栈,这时候系统发现activityC原本所想要的任务栈已经被创建了,所以就把C从A的任务栈挪过来了。具体可以看后续介绍的案例分析。
设置false,那么A拉起B的页面activityC的时候按home退出,再启动B,B页面不会默认展示之前的页面,且如果再点击A拉起,页面还会保留之前的activityC。
总结如下:
activityC allowReparenting=false——
(1)A拉起B的activityC,不添加new task,此时C页面在A的task中。按home后重新点击A,C页面还在,重新点击B,拉起B的launch页面。
(2)A拉起B的activityC,添加new task,此时C页面不在在A的task中。按home后重新点击A,C页面不在,重新点击B,拉起C页面,且是单独一个栈
activityC allowReparenting=true——
(1)A拉起B的activityC,不添加new task,此时C页面在A的task中。按home后重新点击A,C页面不在,重新点击B,拉起C页面。
(2)A拉起B的activityC,添加new task,此时C页面不在A的task中。按home后重新点击A,C页面不在,重新点击B,拉起C页面,且是单独一个栈
Intent flag标记位
这边讲几个常用的标记位
Intent.FLAG_ACTIVITY_NEW_TASK:
开启一个新栈,并将此栈拉到前台。
1.如果在相同进程内,activityA启动activityC且加了此标记位,且ActivityC的taskAffinity和A不相同,那么就会开启一个新栈,如果ActivityC的taskAffinity和相同,则不会开启新栈
案例a:
案例b:
案例c:
案例d:
案例e:
案例f:
总结:添加new_task的标记位,和taskAffinity强相关,如果拉起的activity已经存在,且是此task的跟activity,且不是原taskAffinify,就会单纯把当前的栈拉到前面
2.如果在不同进程内,比如应用A启动应用B的activityC,加了此标记位,无论如何都会开启一个新栈;如果不加,那么拉起的activityC会默认加在应用A的栈中。其实我们看动画效果就能知道有没有开新栈了:应用间切换动效(有缩放和前后台切换的样式)就是开了新栈,普通左右进入切换就是没有开新栈
加了FLAG_ACTIVITY_NEW_TASK的动效:
没有加的动效:
3.如果通过service、applicationContext启动activtiy,一定要加此标记位,不然会报错,因为service这些是没有栈的,因为它们并不是activity
Intent.FLAG_ACTIVITY_SINGLE_TOP:
相同于singleTop启动模式
Intent.FLAG_ACTIVITY_NO_HISTORY:
activity不加入栈,比如A启动了B,加了FLAG_ACTIVITY_NO_HISTORY,B再启动C,这时候C返回,会直接回到A。等效于xml中配置android:noHistory=“true”,实际上很像不保留活动,B启动C的时候,B就销毁了,回调了onDestroy()
Intent.FLAG_ACTIVITY_CLEAR_TOP:
具有此标记位的Activity,当它启动时,在同一个任务栈中所有位于它上面的activity都要出栈(和singleTask差不多,但是singleTask会回调onNewIntent,clearTop会重新创建实例哦),一般情况下配合FLAG_ACTIVITY_NEW_TASK标记位一起使用,就能达到和singleTask一模一样的效果,但是并不是完全一样,
val intent = Intent(this,NormalActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
比如normalActivity启动方式是默认的,即使加了上述标记位,但是normalActivity还是会在栈顶重新创建实例,感觉是不是和singleTask之间的关系搞得越来越复杂了?我们可以看如下的案例就可以明了了
案例a:ActivityA是singleTask
案例b:ActivityA是singleTask,B启动A的时候又加了FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK,结果和案例a一样
案例c:ActivityA是默认,B启动A的时候又加了FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK
案例d:ActivityA是默认,B启动A的时候又加了FLAG_ACTIVITY_CLEAR_TOP
案例e:B拉起A的时候,B启动A的时候又加了FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_NEW_TASK,且A的taskAffinity和B不一样,这种情况下开启了一个新的task,且栈中只有一个新实例A且在栈顶,和B不在一个栈,所以B不用出栈
案例f:B拉起B,但是设置了single_top和clear_top,B不会重走onCreate,继续回调onNewIntent
Intent.FLAG_ACTIVITY_CLEAR_TASK:
只能和FLAG_ACTIVITY_NEW_TASK一起使用,此activity将变成一个空栈中新的最底端的activity,所有的旧activity都会被finish掉,慎用,因为栈管理没做好,很容易把你所有activtiy全finish掉
Intent.FLAG_ACTIVITY_MULTIPLE_TASK:
- 用于创建一个新任务,并启动一个活动放进去;
总是跟FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK一起使用;
单独用FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK时,会在已存在的任务中寻找匹配的Intent,找不到才会创建一个新任务;
使用了本flag不会寻找匹配的Intent,无条件创建一个新任务。 - 用了FLAG_ACTIVITY_NEW_TASK就不要用本flag,除非你启动的是应用的launcher。 跟FLAG_ACTIVITY_NEW_TASK联合使用能防止把已存在的任务移到前面,会为新活动创建一个新任务,无论已存在的任务中有没有新活动。
- 因为默认安卓系统中没有提供可视化的任务管理,所以你不应该使用本flag,除非给用户提供可以回到其他任务的方法。
- 单独用本flag而不用FLAG_ACTIVITY_NEW_DOCUMENT或者FLAG_ACTIVITY_NEW_TASK是无效的。
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT:
此标志通常不是由应用程序代码设置的,而是由系统为您设置的,如launchMode文档中对于singleTask模式的描述。
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT:
这个Flag的意思,比如我现在有一个A,然后在A中启动B。然后在B中启动C,并设置FLAG_ACTIVITY_BROUGHT_TO_FRONT启动,此时栈就是A,B,C。如果这个时候在C中启动B,那么栈的情况会是A,C,B。设置了FLAG_ACTIVITY_CLEAR_TOP会导致此flag失效
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET:(这个测试的时候,发现使用这种flag的启动肯定会开启一个新栈)
等效于Intent.FLAG_ACTIVITY_NEW_DOCUMENT。官方释义:如果设置了此标识,这在栈中的Activity标记了一个当栈被重置的时候应该被清除的还原点。就是说,当下次栈携带有FLAG_ACTIVITY_RESET_IF_NEEDED标识回到了前台(这是用户从主界面重启动的典型结果),那个被设置了还原点的Activity和在这个Activity上的Activity将被销毁,并且用户无法返回到他们,但能够返回到这个Activity之前
设置此标签相当于在任务栈中标记一个位置,当任务栈被重置时,从该标记位置到栈顶的 Activity 全部被清除。所谓的任务栈重置,是指当程序从后台进入前台时(通常是用户从主屏重新点击启动图标,或者从 recent apps 重新进入程序),如果栈顶的 Activity 设置了 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 标记,那么从设置 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标记到栈顶的 Activity 会全部被清除掉。
举例:该标签可用于这种情况,用户在浏览邮件时,通过另外一个 Activity 来打开图片附件,这时用户可能会按 Home 键返回主屏,进行其它操作,一段时间后重新点击邮件的启动图标进入程序,这时应该显示邮件的内容而不是图片,因为用户可能是很长时间后返回邮件 App 的,也许已经忘记之前的操作,如果用户打开邮件后看到的是一张图片,可能会觉得很困惑。而设置 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 标记后,用户再次打开程序时,任务栈就会把图片 Activity 清除掉,显示邮件内容。
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED:
只有当新启动一个任务栈或任务栈被切换到前台时才有用,也就意味着该标记只会加在任务栈的根 Activity 上,用这个标记来表明需要重置任务栈的状态。应用中按home键,然后在桌面重新启动,会有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED此逻辑。一般与FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET一起使用
经过测试发现,对于一个处于后台的应用,如果在主选单点击应用,这个动作中含有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,长按Home键,然后点击最近记录,这个动作不含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记,所以前者会清除,后者不会。
Intent.FLAG_ACTIVITY_NO_ANIMATION:
禁用掉系统默认的Activity切换动画。可以禁掉进入activity的动画(包括singleInstance的栈切换动画),返回动画禁用不了。此flag优先级比overridePendingTransition高,比设置Activity Theme动画低
案例分析
此文档介绍的比较好,强烈推荐:https://blog.csdn.net/vivo_tech/article/details/130199165