Android四大组件之Activity的启动模式

Android四大组件之Activity的启动模式

简单的Launchmode

  • standard: 标准启动模式,每次都会启动一个新的activity实例
  • singleTop: 单独使用这种模式时,如果Activity实例位于当前任务栈顶,就重用栈顶实例,而不新建,并回调该实例onNewIntent()方法,否则走新建流程。

  • singleTask: 又称为栈内复用模式。这是一种单例模式,与singTop点类似,只不过singTop是检测栈顶元素是否有需要启动的Activity,而singTask则是检测整个栈中是否存在当前需要启动的Activity,如果存在就直接将该Activity置于栈顶,并将该Activity以上的Activity都从任务栈中移出销毁,同时也会回调onNewIntent方法

  • singleInstance: 该Activity在整个android系统内存中有且只有一个实例,而且该实例单独尊享一个Task。换句话说,A应用需要启动的MainActivity 是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A单独在这个新的任务栈中,如果此时B应用也要激活MainActivity,由于栈内复用的特性,则不会重新创建,而是两个应用共享一个Activity的实例。

上面只适用于Activity启动Activity,而且采用的都是默认Intent,没有额外添加任何Flag,尤其主要的是FLAG_ACTIVITY_NEW_TASK的使用。

Intent.FLAG_ACTIVITY_NEW_TASK分析

  1. 对于非Activity启动的Activity(比如Service或者通知中启动的Activity)需要显示地设置Intent.FLAG_ACTIVITY_NEW_TASK。
  2. singleTask及singleInstance两种模式,被AMS处理后,会隐形设置Intent.FLAG_ACTIVITY_NEW_TASK
  3. standard和singleTop的Activity不会被设置成Intent.FLAG_ACTIVITY_NEW_TASK,除非通过显示的intent.setFlag进行设置

Intent.FLAG_ACTIVITY_NEW_TASK的初衷是Activity目标taskAffinity的task中启动

Intent.FLAG_ACTIVITY_CLEAR_TASK分析

  1. CLEAR_TASK必须配合FLAG_ACTIVITY_NEW_TASK使用
  2. 如果目标task已经存在,将清空已存在的目标Task,否则,会新建一个Task栈
  3. 在新建或清空后,会新建一个Activity作为根Activity。Intent.FLAG_ACTIVITY_CLEAR_TASK的优先级最高。
  4. 可以无视所有的配置,包括启动模式及Intent Flag,哪怕是singleInstance也会被finish并重建。

CLEAR_TASK标识

Intent.FLAG_ACTIVITY_CLEAR_TOP分析

在TASK2中的D采用CLEAR_TOP唤起B,首先CD先出栈,B会不会重建,取决于B有没有设置SINGLE_TOP启动模式,如果设置则不会重新,回调onNewIntent方法,如果没有设置,则先出栈,然后创建B入栈。

如果没有会新建,但不会去另一个任务栈中寻找。

该标志位表示使用singleTask模式来启动一个Activity,与在清单文件指定android:launchMode=”singleTask”效果相同。

Intent.FLAG_ACTIVITY_SINGLE_TOP

和launchmode中的singleTop一致,有的话就创建,没有的话就新建。

为什么非Activity启动Activity要强制使用参数FLAG_ACTIVITY_NEW_TASK?

在ContextImpl中的startActivity做了检查,如果没添加,则会抛出AndroidRuntimeException。

Intent.FLAG_ACTIVITY_NO_HISTORY

使用该模式来启动Activity,当该Activity启动其他Activity后,该Activity就被销毁了,不会保留在任务栈中。如A-B,B中以这种模式启动C,C再启动D,则任务栈只有ABD。

Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

  使用该标识位启动的Activity不添加到最近应用列表,也即我们从最近应用里面查看不到我们启动的这个activity。与属性android:excludeFromRecents=”true”效果相同。

Android任务栈是什么?

  1. android任务栈又称为Task,它是一个栈结构,具有后进先出的特性,用于存放我们的Activity组件。
  2. 我们每次打开一个新的Activity或者退出当前Activity都会在一个称为任务栈的结构中添加或者减少一个Activity组件,因此一个任务栈包含了一个activity的集合, android系统可以通过Task有序地管理每个activity,并决定哪个Activity与用户进行交互:只有在任务栈栈顶的activity才可以跟用户进行交互。
  3. 在我们退出应用程序时,必须把所有的任务栈中所有的activity清除出栈时,任务栈才会被销毁。当然任务栈也可以移动到后台, 并且保留了每一个activity的状态. 可以有序的给用户列出它们的任务, 同时也不会丢失Activity的状态信息。
  4. 需要注意的是,一个App中可能不止一个任务栈,某些特殊情况下,单独一个Actvity可以独享一个任务栈。还有一点就是一个Task中的Actvity可以来自不同的App,同一个App的Activity也可能不在一个Task中。

TaskAffinity属性是什么?

  • TaskAffinity标识着Activity所需要的任务栈名称,默认情况下,一个应用中所有的Activity所需要的任务栈名称都为该应用的包名。
  • TaskAffinity属性一般跟singleTask模式或者allowTaskReparenting属性结合使用,其他情况下没有实际意义。
  • TaskAffinity属性的值不能与当前应用包名相同,否则其值跟作废没两样。

allowTaskReparenting属性是什么?

首先我们来聊聊allowTaskReparenting属性,它的主要作用是activity的迁移,即从一个task迁移到另一个task,这个迁移跟activity的taskAffinity有关。当allowTaskReparenting的值为“true”时,则表示Activity能从启动的Task移动到有着affinity的Task(当这个Task进入到前台时),当allowTaskReparenting的值为“false”,表示它必须呆在启动时呆在的那个Task里。如果这个特性没有被设定,元素(当然也可以作用在每次activity元素上)上的allowTaskReparenting属性的值会应用到Activity上。默认值为“false”。这样说可能还比较难理解,我们举个例子,比如现在有两个应用A和B,A启动了B的一个ActivityC,然后按Home键回到桌面,再单击B应用时,如果此时,allowTaskReparenting的值为“true”,那么这个时候并不会启动B的主Activity,而是直接显示已被应用A启动的ActivityC,我们也可以认为ActivityC从A的任务栈转移到了B的任务栈中。这就好比我们在路边收养了一只与主人走失了的猫,养着养着突然有一天,主人找上门来了,这只猫也就被带回去了。我们通过图解来更好地理解这种情景:

allowTaskReparenting

  对比发现,如果allowTaskReparenting值为false时,ActivityC并不会直接从A应用的任务栈迁移到B应用的任务栈,而是B应用直接重新创建了ActivityC的实例。到此我们对于allowTaskReparenting和taskAffinity属性的了解就已经相当深入了,不过有点需要说明的是allowTaskReparenting仅限于singleTop和standard模式,这是因为一个activity的affinity属性由它的taskAffinity属性定义(代表栈名),而一个task的affinity由它的root activity定义。所以,一个task的root activity总是拥有和它所在task相同的affinity。由于以singleTask和singleInstance启动的activity只能是一个task的root activity,因此allowTaskReparenting仅限于以standard 和singleTop启动的activity,大家可以自行测试一下,这里我们就不测试了哈,下面我们再来说说它们可能应用用场景。

怎么指定Activity所属的栈?

通过设置TaskAffinity属性值为android:taskAffinity=”xxx”

TaskAffinity和SingleTask结合的应用场景

假如现在有这么一个需求,我们的客户端app正处于后台运行,此时我们因为某些需要,让微信调用自己客户端app的某个页面,用户完成相关操作后,我们不做任何处理,按下回退或者当前Activity.finish(),页面都会停留在自己的客户端(此时我们的app回退栈不为空),这显然不符合逻辑的,用户体验也是相当出问题的。我们要求是,回退必须回到微信客户端,而且要保证不杀死自己的app.这时候我们的处理方案就是,设置当前被调起Activity的属性为:

1
LaunchMode=""SingleTask" taskAffinity="com.tencent.mm"

其中com.tencent.mm是借助于工具找到的微信包名,就是把自己的Activity放到微信默认的Task栈里面,这样回退时就会遵循“Task只要有Activity一定从本Task剩余Activity回退”的原则,不会回到自己的客户端;而且也不会影响自己客户端本来的Activity和Task逻辑。

TaskAffinity与allowTaskReparenting的应用场景

一个e-mail应用消息包含一个网页链接,点击这个链接将出发一个activity来显示这个页面,虽然这个activity是浏览器应用定义的,但是activity由于e-mail应用程序加载的,所以在这个时候该activity也属于e-mail这个task。如果e-mail应用切换到后台,浏览器在下次打开时由于allowTaskReparenting值为true,此时浏览器就会显示该activity而不显示浏览器主界面,同时actvity也将从e-mail的任务栈迁移到浏览器的任务栈,下次打开e-mail时并不会再显示该activity

在AndroidMainifest的标签设置该activity的

1
allowTaskReparenting:true

如何清空任务栈?

我们只需要在标签指明相应的属性值

android:clearTaskOnLaunch

  这个属性用来标记是否从task清除除根Activity之外的所有的Activity,“true”表示清除,“false”表示不清除,默认为“false”。这里有点我们必须要注意的,这个属性只对任务栈内的root Activity起作用,任务栈内其他的Activity都会被忽略。如果android:clearTaskOnLaunch属性为“true”,每次我们重新进入这个应用时,我们只会看到根Activity,任务栈中的其他Activity都会被清除出栈。
  比如一个应用的Activity A,B,C,其中clearTaskOnLaunch设置为true,C为默认值,我们依次启动A,B,C,点击HOME,再在桌面点击图标。启动的是A,而B,C将都被移除当前任务栈。也就是说,当Activity的属性clearTaskOnLaunch为true时将被优先启动,其余的Activity(B、C)都被移除任务栈并销毁,除非前面A已经finish销毁,后面的已注册clearTaskOnLaunch为true的activity(B)才会生效。
  特别地,如果我们的应用中引用到了其他应用的Activity,这些Activity设置了android:allowTaskReparenting属性为“true”,则它们会被重新宿主到有共同affinity的task中。      

android:finishOnTaskLaunch

如果将finishOnTaskLauch属性值设置为true,离开这个Activity所依赖的任务栈后,重新返回时,该Activity会被finish掉,其他的Activity不受影响。

android:alwaysRetainTaskState

当该属性设置为true时,该Activity不受到任何清理命令的影响,一直保持当前任务栈状态。

各启动模式下的生命周期

standard

这个最好理解,A -> B,当A跳转到B时,先是B的onCreate->onStart->onResume,再是前一个Activity的onStop。

singletop

栈顶复用模式

当A是standard启动模式,B是singleTop启动模式时,分几种情况:

  1. A启动B时,当栈中无B实例、B不在栈顶时,和启动一个standard Activity没有区别,流程如上。
  1. B启动B时,当B在栈顶时,会经过B的onNewIntent -> onResume

singleTask

栈内复用模式

当A是standard启动模式,B是singleTask启动模式时,分几种情况:

  1. A启动B时,当栈内无B实例时,和启动一个standard Activity没有区别。

  2. B启动B时,B在栈顶时,和启动一个在栈顶的singleTop Activity没有区别。

  3. A启动B时,B不在栈顶时,在之前的singleTask Activity之上的所有Activity按顺序出栈(这个顺序是添加到栈的顺序),如B是singleTask,A、C都是Standard,栈内从底到顶的顺序依次为BACAC,这时C再跳转B时,那么会相继调用A,C,A的onDestroy,再调用B的onNewIntent,onStart,onResume,最后再是C的onStop,onDestroy。

singleInstance

全局单例模式

当A是standard启动模式,B是singleInstance启动模式

  1. A启动B时,当全局无该实例时,与启动一个standard Activity在生命周期上没区别,但是在所属任务栈上有区别,任务栈id是不同的。

  2. B启动B时,和singleTop/singleTask在顶部时没有区别

  3. B启动A时,和启动一个standard Activity在生命周期上没有区别,任务栈id也是不同的,同1的情况。且id等同于A启动B时,A的任务栈id。

  4. singleInstance实例会独占一个任务栈,该任务栈仅存在该一个实例。

  5. 当A启动B时,在B中回退,会先把该任务栈清空,也就是B的实例清空,再切换到A的任务栈中。当B启动A时,如果A有多个实例,则回退时会一直清空到A的任务栈无任何实例时,最后才会转到B的任务栈清空该singleInstance实例。