> 文章列表 > Activity启动模式与栈的使用小结

Activity启动模式与栈的使用小结

Activity启动模式与栈的使用小结

Activity启动模式与栈的使用小结

前言

最近碰到了挺多和activity栈相关的内容,想来稍微总结下,前段时间写了篇taskAffinity相关的博客,但是感觉写的不咋样,这次就把脑子里相关的内容整理下吧。

四种启动模式

Activity的四种启动模式,这个估计是老生常谈的问题了,这里就提下注意点,主要就是配合FLAG的问题。

Standard

默认的启动方式,栈顶新建嘛,每次都会启动一个新的activity实例

需要注意的就是配合FLAG使用的时候:

  1. FLAG_ACTIVITY_NEW_TASK
    配合FLAG_ACTIVITY_NEW_TASK一起使用时,设置了taskAffinity会新开或者复用一个栈(对应taskAffinity),如果都是默认taskAffinity那就无效了。
  2. FLAG_ACTIVITY_CLEAR_TOP
    配合FLAG_ACTIVITY_CLEAR_TOP一起使用时,会将可复用的activity上面以及该activity都清除了,再创建个新的activity。
    如果再加上FLAG_ACTIVITY_SINGLE_TOP时,该可复用的activity就不会被清楚,而是复用,走onNewIntent方法。
  3. FLAG_ACTIVITY_SINGLE_TOP
    这里就是和singleTop一样的效果了。

SingleTop

栈顶复用,复用的时候会走onNewIntent,没复用就在当前栈新建,等同于FLAG_ACTIVITY_SINGLE_TOP。

SingleTask

这种模式启动的Activity只会存在相应的Activity的taskAffinity任务栈中,同一时刻系统中只会存在一个实例,已存在的实例被再次启动时,会重新唤起该实例,并清理当前Task任务栈该实例之上的所有Activity,同时回调onNewIntent()方法。

如果指定的task不存在,创建指定的taskAffinity的task,也就是说并不一定在当前栈的栈顶创建一个新的activity,下面看下几种FLAG的组合,就会理解深一点:

  1. FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP
    在当前栈中复用activity,并将该activity上面的activity清出栈,调用onNewIntent方法。如果没有复用,还是在当前栈新建一个activity。

  2. FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK
    FLAG_ACTIVITY_NEW_TASK一般就和FLAG_ACTIVITY_CLEAR_TASK、FLAG_ACTIVITY_CLEAR_TOP一起使用。
    这里如果有对应taskAffinity的task,就把栈清空,并创建一个activity作为唯一成员。
    如果没有对应taskAffinity的task,会新建一个对应taskAffinity的task,新建activity并放入。

  3. FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP
    如果有对应taskAffinity的task,且有可复用的activity,就把它以及它以上的activity清空,创建个新的activity放在栈顶,没可复用就栈顶新建。
    如果没有对应taskAffinity的task,会新建一个对应taskAffinity的task,新建activity并放入。

看完上面这几个例子能理解SingleTask对应什么FLAG了吗?在我看来,上面四个都不对,更应该是这三个的合成:

FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP

可是这里会有问题,FLAG_ACTIVITY_NEW_TASK和FLAG_ACTIVITY_SINGLE_TOP放一起的时候,会先验证FLAG_ACTIVITY_SINGLE_TOP,如果当前栈正好是可复用的activity,就不会验证task问题,直接复用了。

如果栈顶没有的可复用的activity的话,会在对应taskAffinity的task栈中找,z找到了再清空该activity上层,注意这里会复用!

但是如果真看一般使用的话,FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP应该更符合我们的需求。

ps. 这里我后面又写了个DEMO试了下,可以看下这篇文章:

对 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP 的实践

SingleInstance

这种模式启动的Activity独自占用一个Task任务栈,同一时刻系统中只会存在一个实例,已存在的实例被再次启动时,只会唤起原实例,并回调onNewIntent()方法。

这里不用想对应的FLAG了,因为它没有对应的FLAG。

Intent的FLAG分析

在上面的内容中,我们看到了,四种启动模式看起来简单,但是加上FLAG之后就变得特别麻烦了,关于FLAG我也没什么好说的了,之前转载了一篇博客,还有一些资料可以提供给读者看下,写的非常好:

Android Intent的FLAG标志详解

Android面试官装逼失败之:Activity的启动模式

官方文档

taskAffinity及allowTaskReparenting

taskAffinity,可以翻译为任务相关性。这个参数标识了一个 Activity 所需要的任务栈的名字,默认情况下,所有 Activity 所需的任务栈的名字为应用的包名,当 Activity 设置了 taskAffinity 属性,那么这个 Activity 在被创建时就会运行在和 taskAffinity 名字相同的任务栈中,如果没有,则新建 taskAffinity 指定的任务栈,并将 Activity 放入该栈中。另外,taskAffinity 属性主要和 singleTask 或者 allowTaskReparenting 属性配对使用,在其他情况下没有意义(不生效)。

allowTaskReparenting,翻译过来的意思是允许重新找父母(允许迁移任务栈),该属性用于配置是否允许该activity更换从属的task。
如果一个Activity设置了这个属性,其他应用启动这个activity的时候分两种情况处理:

  • 这个activity对应的进程已经启动了:则这个activity直接附属到自己所对应的进程的应用栈上
  • 这个activity对应的进程没有启动:则这个activity先直接附属到启动它的应用的应用栈上,当activity对应的进程启动后,则会主动迁移到activity对应的进程。

官方解释:

当下一次将启动 Activity 的任务转至前台时,Activity 是否能从该任务转移至与其有相似性的任务 -“true”表示可以转移,“false”表示仍须留在启动它的任务处。

有趣说法:

你捡到一条狗,在家里喂养几天觉得不错,当自己家的了;但是突然有一天他的主人找上门来了,小狗还是乖乖和主人走了。这条狗就是带有allowTaskReparenting属性的activity。

其他问题

在Service内启动Activity

这个是面试的时候被问到的,在Service内启动Activity,会报错,需要添加FLAG_ACTIVITY_NEW_TASK,当时我觉得报错闪退是因为两个不在同一个栈内,现在看来还是太天真了。

这里其实是安卓设置成这样的,可以看下下面这篇文章,我觉得写道挺好的:

Android 在Service中启动Activity的大坑

最近任务中多个页面

如果有多个任务栈,在手机应用卡片里面可能会有多个页面,这里和我们的taskAffinity有关。

发生原因

如果SingleTask的activity(或者FLAG_ACTIVITY_NEW_TASK启动),其taskAffinity不一样,那就会产生多个任务卡片。

前面解决StrandHogg漏洞的时候,我把application的taskAffinity设置为空字符串也会触发这种情况,默认的taskAffinity是应用的包名。

解决办法

这里有好几种办法:

  1. 通过设置manifest的android:excludeFromRecents="true"解决
  2. 通过FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS解决
  3. 通过对ActivityManager.AppTask设置setExcludeFromRecents解决,复杂些。
  4. 通过对activity设置非SingleTask模式,并通过FLAG启动来解决。

交换Activity的顺序

在我遇到的情况就是,希望两个页面互切不finish,大致就是这样一个情况:A -> B, B -> A, A - > B,希望B或A仅仅交换。

这里一开始我觉得会很麻烦,结果就是加一个FLAG_ACTIVITY_REORDER_TO_FRONT就行了,实际就是对Activity的交换,而且这里会触发onNewIntent方法,很实用。

获取栈信息

有时候想要获取Activity的信息,打log就有点麻烦了,可以用adb命令:

adb shell
dumpsys activity top | grep ACTIVITY

在代码中就可以用下面代码:

// 只能获取当前栈顶和设置的baseActivity(stack最开始activity)
ActivityManager am = (ActivityManager) RunningTaskInfo context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTaskInfoList = am.getRunningTasks(1);
ActivityManager.RunningTaskInfo info = runningTaskInfoList.get(0);
info.baseActivity;
info.topActivity;

这里的topActivity就是顶层activity,baseActivity是stack最开始activity,相应的还有个rootActivity的概念,也是这样,可以通过Activity的isTaskRoot()判断。