activity启动模式

任务栈Task,是一种用来放置Activity实例的容器,他是以栈的形式进行盛放,也就是所谓的先进后出,主要有2个基本操作:压栈和出栈,其所存放的Activity是不支持重新排序的,只能根据压栈和出栈操作更改Activity的顺序。

启动一个Application的时候,系统会为它默认创建一个对应的Task,用来放置根Activity。默认启动Activity会放在同一个Task中,新启动的Activity会被压入启动它的那个Activity的栈中,并且显示它。当用户按下回退键时,这个Activity就会被弹出栈,按下Home键回到桌面,再启动另一个应用,这时候之前那个Task就被移到后台,成为后台任务栈,而刚启动的那个Task就被调到前台,成为前台任务栈,Android系统显示的就是前台任务栈中的Top实例Activity。

任务栈的作用

以往基于应用(application)的程序开发中,程序具有明确的边界,一个程序就是一个应用,一个应用为了实现功能可以采用开辟新线程甚至新进程来辅助,但是应用与应用之间不能复用资源和功能。

而Android引入了基于组件开发的软件架构,虽然我们开发android程序,仍然使用一个apk工程一个Application的开发形式,但是对于Aplication的开发就用到了Activity、service等四大组件,其中的每一个组件,都是可以被跨应用复用的,这就是android的神奇之处。

虽然组件可以跨应用被调用,但是一个组件所在的进程必须是在组件所在的Aplication进程中。由于android强化了组件概念,弱化了Aplication的概念,所以在android程序开发中,A应用的A组件想要使用拍照或录像的功能就可以不用去针对Camera类进行开发,直接调用系统自带的摄像头应用(称其B应用)中的组件(称其B组件)就可以了,

但是这就引发了一个新问题,A组件运行在A应用中,B组件运行在B应用中,自然都不在同一个进程中,那么从B组件中返回的时候,如何实现正确返回到A组件呢?Task就是来负责实现这个功能的,它是从用户角度来理解应用而建立的一个抽象概念。因为用户所能看到的组件就是Activity,所以Task可以理解为实现一个功能而负责管理所有用到的Activity实例的栈。

栈是一个先进后出的线性表,根据Activity在当前栈结构中的位置,来决定该Activity的状态。正常情况下,当一个Activity启动了另一个Activity的时候,新启动的Activity就会置于任务栈的顶端,并处于活动状态,而启动它的Activity虽然成功身退,但依然保留在任务栈中,处于停止状态,当用户按下返回键或者调用finish()方法时,系统会移除顶部Activity,让后面的Activity恢复活动状态。当然,世界不可能一直这么“和谐”,可以给Activity设置一些“特权”,来打破这种“和谐”的模式,这种特权,就是通过在AndroidManifest文件中的属性andorid:launchMode来设置或者通过Intent的flag来设置的,下面就先介绍下Activity的几种启动模式。

standard

默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。应用场景:绝大多数Activity。

如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解看起来也不是那么合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。

singleTop

栈顶复用模式,如果要开启的activity在任务栈的顶部已经存在,就不会创建新的实例,而是调用 onNewIntent() 方法。避免栈顶的activity被重复的创建。应用场景:在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用singleTop,否则每次点击都会新建一个Activity。当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 singleTop模式也是可以避免这个Bug的。

同standard模式,如果是外部程序启动singleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中。

singleTask

栈内复用模式, activity只会在任务栈里面存在一个实例。如果要激活的activity,在任务栈里面已经存在,就不会创建新的activity,而是复用这个已经存在的activity,调用 onNewIntent() 方法,并且清空这个activity任务栈上面所有的activity。应用场景:大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例,通过这种方式能够保证退出应用时所有的Activity都能报销毁。

在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。

1:假如目前有个任务栈T1中的情况是ABC,这个时候ActivityD以singleTask模式请求启动,其所需要的任务栈正是T1,则系统会直接创建D的实例并将其入栈到T1中。

2:假如DActivity启动所需要的任务栈为T2,由于T2和D的实例均不存在,那么系统会先创建任务栈T2,然后再创建D的实例并将其入栈到T2中。我们可以通过设置Activity的taskAffinity属性来模拟这一场景。

3:如果D所需的任务栈为T3,并且当前任务栈T3的情况为ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent()方法,同时由于singleTask默认具有ClearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终T3的情况为AD。

4:假如目前有两个任务栈,前台任务栈T4的情况为AB,后台任务栈t4里存有CD,假设CD的启动模式均为singleTask,现在由B去启动D,那么整个后台任务都会被切换到前台,这个时候整个栈就变成了ABCD。

5:假如上面的其他条件不变,B启动的是C而不是D,那么整个栈的情况就变成了ABC,因为D在C上面,会被清理出栈。

singleInstance

单一实例模式,整个手机操作系统里面只有一个实例存在。不同的应用去打开这个activity 共享公用的同一个activity。他会运行在自己单独,独立的任务栈里面,并且任务栈里面只有他一个实例存在。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。建议谨慎使用。

 

设置Intent的Flag

系统提供了两种方式来设置一个Activity的启动模式,除了在AndroidManifest文件中设置以外,还可以通过Intent的Flag来设置一个Activity的启动模式,下面我们在简单介绍下一些Flag。

FLAG_ACTIVITY_NEW_TASK

使用一个新的Task来启动一个Activity,但启动的每个Activity都讲在一个新的Task中。该Flag通常使用在从Service中启动Activity的场景,由于Service中并不存在Activity栈,所以使用该Flag来创建一个新的Activity栈,并创建新的Activity实例。

FLAG_ACTIVITY_SINGLE_TOP

使用singletop模式启动一个Activity,与指定android:launchMode=“singleTop”效果相同。

FLAG_ACTIVITY_CLEAR_TOP

使用SingleTask模式来启动一个Activity,与指定android:launchMode=“singleTask”效果相同。

FLAG_ACTIVITY_NO_HISTORY

Activity使用这种模式启动Activity,当该Activity启动其他Activity后,该Activity就消失了,不会保留在Activity栈中。

LaunchMode与StartActivityForResult

我们在开发过程中经常会用到StartActivityForResult方法启动一个Activity,然后在onActivityResult()方法中可以接收到上个页面的回传值,但你有可能遇到过拿不到返回值的情况,那有可能是因为Activity的LaunchMode设置为了singleTask。5.0之后,android的LaunchMode与StartActivityForResult的关系发生了一些改变。两个Activity,A和B,现在由A页面跳转到B页面,看一下LaunchMode与StartActivityForResult之间的关系:

这是为什么呢?

这是因为ActivityStackSupervisor类中的startActivityUncheckedLocked方法在5.0中进行了修改。在5.0之前,当启动一个Activity时,系统将首先检查Activity的launchMode,如果为A页面设置为SingleInstance或者B页面设置为singleTask或者singleInstance,则会在LaunchFlags中加入FLAG_ACTIVITY_NEW_TASK标志,而如果含有FLAG_ACTIVITY_NEW_TASK标志的话,onActivityResult将会立即接收到一个cancle的信息,而5.0之后这个方法做了修改,修改之后即便启动的页面设置launchMode为singleTask或singleInstance,onActivityResult依旧可以正常工作,也就是说无论设置哪种启动方式,StartActivityForResult和onActivityResult()这一组合都是有效的。所以如果你目前正好基于5.0做相关开发,不要忘了向下兼容,这里有个坑请注意避让。

任务

先说下任务的定义,Android官方把上述这种为了完成某些工作而链式启动的一系列Activity合集称之为 任务

我们都知道每个Activity都是互相独立的界面,正是有了任务这样的概念,多个Activity才能够关联起来组成一个完整的应用。

任务可以同时存在多个吗

当然可以!

例:平时我们使用手机经常会在刷微博和聊微信来回切换,每次切换系统都会为我们保存上一次离开的状态。

任务里Activity必须是来自同一个应用吗

当然不是!

例:当我们在社交软件设置用户头像时一般会有拍照和相册两个选项,选择拍照会跳转到摄像机软件,选择相册会跳到系统相册软件。通过这几个软件之间的共同合作完成了一次任务。

任务中的根Activity

通常情况下,我们都是通过设备主屏幕点击应用图标启动应用的,同理设备主屏幕也是大多数任务的起点,而应用中的入口Activity就是这个任务的根Activity,根Activity的声明方式你一定特别熟悉:

<activity
       android:name=".HelloActivity"">
       <intent-filter>
              <action android:name="android.intent.action.MAIN" />
              <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
</activity>

当用户点击主屏幕应用图标打开应用时,如果该应用最近未曾被使用过,则会创建一个任务,并将该应用中的入口Activity作为任务中的根Activity打开。反之直接把该应用所在的任务调出来置于前台即可。

了解完任务之后,我们就大概知道了上述几个例子中Android系统如何保存Activity使用状态的规则。

返回栈

任务呢是一个特别虚的概念,是为了方便开发者理解才有的它,而系统中真正存储Activity的是一个遵循先进后出原则的数据结构:栈。一般叫它返回栈(任务栈,堆栈,其实叫什么的都有)。

返回栈是任务的实际载体,每个任务中所有的Activity都会按照各自的打开顺序保存在对应的返回栈中。所以Android系统显示界面的顺序是先找到要显示界面所在的任务,然后在对应的返回栈中找到显示的Activity。

值得一提的是由于返回栈存储结构的特殊性,外部只能访问到栈顶的Activity,也就是最后入栈的那个。所以一个Activity想要能显示在屏幕上那么它必须存在于栈顶位置。

进栈与出栈

当前 Activity 启动另一个 Activity 时,新的 Activity 会被推送到堆栈顶部,成为焦点显示在屏幕上。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。

用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行。如果用户继续按“返回”,堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。 当所有 Activity 均从堆栈中移除后,任务即不复存在。栈也就会被回收掉。

特殊的任务

通过前面的了解,我们知道如果要打开新的界面需要把Activity实例放到当前任务对应的返回栈的栈顶。该操作是不管该Activity之前有没有实例化过或者栈中是否已经存在了的。

但是,有些特殊情况下,我们会发现一些“例外”。

例1:当来自多个不同任务中的应用选择使用系统浏览器访问网页的时候,浏览器应用并不会在每个任务的返回栈中都创建Activity,而是将所有网页以选项卡的形式展示在同一个界面中。

本例中浏览器应用的Activity如果已经实例化过了就不会重新创建。

例2:小明在微信中向你分享了一条微博内容,你打开后跳转到了微博APP中的该条微博详情页,当你看完内容后按返回键退出该界面发现并不是回到了微信聊天界面,而是来到了微博主页(或上一次在微博中停留的界面)。

本例中微博详情页的Activity虽然是由微信应用所在的任务启动,但是没有加入到微信应用的任务中,而是加入到了微博的任务栈中。

管理任务

很显然上述两个例子在实际使用中并不少见,对于这种特殊的情况我们需要针对性的管理任务,而众所周知的启动模式仅仅是其中的一种。

定义启动模式

定义Activity的启动模式其实就是定义一个Activity的新实例如何(是否)与当前任务做关联。以什么样的方式进入到当前(或其他)任务中。

如果你只说Activity的启动模式有四种,其实是不准确的,因为我们可以通过两种方法定义不同的启动模式:

  • 使用AndroidManifest.xml中定义


    在AndroidManifest.xml中<activity>标签下使用lauchMode属性来指定当前这个activity的启动模式。
  • 使用Intent标志定义


    在调用startActivity(Intent intent)前,通过调用intent.addFlags()或者intent.setFlags()方法为Intent添加一个标志,用于为将要启动的Activity声明启动模式。

那两者有什么区别呢?

上述两种方法均可以为activity声明启动模式,只是使用情景不同。

  1. 如果我们希望某个activity在任何情况下都会执行一种特殊的启动模式,我们就可以采用AndroidManifest.xml的方法声明。

  2. 如果我们希望某个activity大多数情况下正常启动,而少数情况下执行特殊的启动模式,我们就可以在需要执行特殊启动模式时在Intent中添加标志声明。

  3. 如果一个activity两种方式都声明了的话,使用Intent标志的方式要比AndroidManifest.xml的优先级高。

  4. 两种方式中定义的启动模式有些是不一样的,Intent标志中定义的某些启动模式AndroidManifest.xml中没有,反之一样。

  5. 我们常说的四种启动模式其实说的是AndroidManifest.xml中定义的。

使用AndroidManifest.xml声明启动模式

在清单文件中声明 Activity 时,您可以使用<activity>元素的 ][launchMode属性指定 Activity 应该如何与任务关联。

您可以分配给 launchMode 属性的启动模式共有四种:

  • standard
  • singleTop
  • singleTask
  • singleInstance

先不用管他们具体的操作是什么,我们首先要知道这四种启动模式可以分为两大类:

  • standardsingleTop
    该类启动模式的activity可以被多次的实例化,它们的实例可以放到任何任务中,并且可以位于返回栈的任何位置。
  • singleTasksingleInstance
    带有此类启动模式的activity,它们只能有一个实例存在,且实例只能存在于单独的任务中。

standard:标准模式

默认启动模式,启动activity时直接创建新的实例并压入启动它的任务栈顶。

singleTop:栈顶复用模式

该模式唯一与standard不同的就是,如果启动singleTop模式的activity时发现当前任务的栈顶已经存在着这个activity的实例,那么就不会创建新的实例,而是调用该实例的onNewIntent()方法。其他的跟标准模式一样。

singleTask:栈内复用模式

这个模式有些特殊一点,我们先按使用情景介绍它,当我们将要启动该模式的activity时,系统会判断当前是否有它想要的任务栈:

  1. 没有它要的任务栈
    系统会新创建一个任务,并将该activity实例化作为该任务的根activity。

  2. 有它要的任务栈


    这时候系统会找到该任务栈,如果任务栈里只有它自己则直接调用该activity实例的onNewIntent()方法。如果任务栈中它的上方还存在别的activity,那么这些activity会被全部弹出栈。

至于什么是“它想要的任务栈”,我们会在下面单独分析。

singleInstance:单例模式
基本上跟singleTask相同,会为activity单独创建一个任务并能够复用。但是该模式的activity不允许其他activity跟自己存在于同一个任务中,由此 activity 启动的任何 activity 均会被在其他的任务中打开。

使用Intent标志声明启动模式

此方式可以通过调用intent.addFlags(int flags)或者intent.setFlags(int flags)方法为Intent添加一个标志,用于为将要启动的Activity声明启动模式。

在开始介绍前,先进行几点扫盲科普:

  1. 一个Intent可以设置多个标志,这就是为啥有addflags()setFlags()两个方法的原因了。
  2. 为Intent设置标志的参数都是Intent类的静态常量。
  3. 设置Intent标志不光只有设置activity启动模式这一个功能,设置不同的参数还有其他功能。
  4. Intent标志中可以对activity启动模式进行操作的标志可多了,我们只介绍特别典型的三种。

Intent.FLAG_ACTIVITY_SINGLE_TOP
AndroidManifest.xml方式中的singleTop启动模式。

Intent.FLAG_ACTIVITY_NEW_TASK
AndroidManifest.xml方式中的singleTask启动模式。

Intent.FLAG_ACTIVITY_CLEAR_TOP
如果即将启动的 activity 已经存在于当前任务栈中,则会弹出销毁它上方的所有 activity,并调用该activity实例的onNewIntent()方法,而不是启动该 Activity 的新实例。

singleTask有点像但不一样,在AndroidManifest.xml方式中没有与此对应的值。

singleTask默认就包含了FLAG_ACTIVITY_CLEAR_TOP的功能。

关联任务

在分析singleTask时有提到过该模式下启动activity前会去找“它想要的任务栈”,那么如何去找呢?这就引出了AndroidManifest.xml<activity>标签下的taskAffinity属性。

taskAffinity 属性

taskAffinity 属性学名任务相关性,说白了其实就是这个参数可以指定当前activity所属任务栈的名字,该属性的值为字符串:

例:android:taskAffinity="com.test.demo.task1"

如果你在<activity>标签没指定这个属性,那么它就用<application>标签的taskAffinity属性,如果<application>标签下也没指定,它就应用包名当做默认值。

taskAffinity 与 singleTask

了解到taskAffinity属性后我们在重新梳理一下singleTask启动模式。

  • 如果我们指定了taskAffinity属性的值,那么就跟之前分析的一样,创建新任务等等...
  • 如果我们未指定taskAffinity属性的值,新activity就与当前任务的taskAffinity属性值一样,所以新activity的实例会被放置到当前的任务栈中。

除了singleTask呢?
现在我们知道了taskAffinity属性可以强行指定activity所属的任务栈,那么这个属性在其他启动模式情况下是什么样的呢?网上好多人都说没有效果,我不信就亲自测试了一下得出以下结论:

  1. 刚介绍 SingleInstance的时候说它跟singleTask一样都会新建一个任务,既然singleTask是根据taskAffinity属性来决定是否需要新建任务的,那么singleInstance是不是也需要指定这个属性呢?
    答案是否!只要启动模式为singleInstance它一定会单独开一个任务。

  2. SingleTop模式下指定了taskAffinity属性的值后,他就会神奇的切换到指定的那个任务栈中,除此之外跟原来一样。

  3. 最神奇的就是Standard,它也同样受到了taskAffinity属性的影响,也会切换到指定的那个任务栈中,但当我们多次启动这个activity时它不会再多次的创建实例,而是拉起了之前启动过的实例,更特殊的是,其他三种启动模式在复用之前实例时都会调用onNewIntent()方法,他却不会调用该方法。

taskAffinity的其他作用

taskAffinity还有一个功能就是可以重新定向所属任务,意思就是这个activity原来是属于任务A的,当有一个跟该activity的taskAffinity属性值相同的任务B被创建后,这个activity就会从任务A中转移到任务B中。

想要实现这个功能我们还需要allowTaskReparenting属性的配合:

  1. 我们在清单文件中给taskAffinity="A"的activity标签下添加属性android:allowTaskReparenting=true
  2. taskAffinity="B"的任务下启动这个activity,此时这个activity存在于任务B中。
  3. taskAffinity="A"的任务被创建或者被置于前台,该activity将被转移到其任务栈中,位于栈顶位置。

清理任务

如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。

您可以使用下列几个 Activity 属性修改此行为:

alwaysRetainTaskState
如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。

clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。

finishOnTaskLaunch
类似于clearTaskOnLaunch,但是更狠一些,当用户离开任务再回来的时候,整个任务的activity都会清除,连根activity也是,相当于第一次启动这个任务。

启动模式的实际应用

个人觉得常见的四种启动模式中要属singleTop不是很好理解,其他的还好。

singleTop

singleTop模式的activity特点就是除了外部可以启动它显示信息外,它也可以用同样的方式启动自己更新显示信息,这样就减少了冗余代码,降低了维护成本。

例:如果让你设计一个带有搜索应用的APP,主页有一个搜索框,输入信息点击搜索按钮进入结果页显示结果,为方便用户使用,结果页也有一个搜索框,跟主页的搜索框功能一样,你会怎么设计?

singleTask

这个启动模式可以分为两种情况:

  1. 未指定taskAffinity
    此时该activity可以当应用中的某一模块的入口处

有如下启动流程,微信主页 >> 聊天页 >> 聊天设置页 >> 用户资料页 >> 聊天页,此时我们按下返回键直接回到了微信主页。

  1. 指定了taskAffinity
    如果利用该启动模式新开了任务,在用户的视角里就相当开了两个应用(在任务管理器中会看到两个最近应用),所以谨慎使用,我能想到的使用情况就是一个Word应用打开了两份文档。

singleInstance

这种情况下不管有多少个任务启动它,它都会作为一个单独任务存在着,这种模式极其特殊,谨慎使用。

例:拨号界面,闹钟界面。

 

  众所周知当我们多次启动同一个Activity时,系统会创建多个实例,并把它们按照先进后出的原则一一放入任务栈中,当我们按back键时,就会有一个activity从任务栈顶移除,重复下去,直到任务栈为空,系统就会回收这个任务栈。但是这样以来,系统多次启动同一个Activity时就会重复创建多个实例,这种做法显然不合理,为了能够优化这个问题,Android提供四种启动模式来修改系统这一默认行为。 
       进入正题,Activity的四种启动模式如下: 
       standard、singleTop、singleTask、singleInstance 
       接下来,我们一边讲理论一边结合案例来全面学习这四种启动模式。 
       为了打印方便,定义一个基础Activity,在其onCreate方法和onNewIntent方法中打印出当前Activity的日志信息,主要包括所属的task,当前类的hashcode,以及taskAffinity的值。之后我们进行测试的Activity都直接继承该Activity

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
 * Created by huangshuai on 2016/5/23.
 * Email:huangshuai@wooyun.org
 * 方便打印的基础Activity
 */
public class BaseActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        Log.i("WooYun", "*****onCreate()方法******");
        Log.i("WooYun", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
        dumpTaskAffinity();
    }

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
        Log.i("WooYun", "*****onNewIntent()方法*****");
        Log.i("WooYun", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
        dumpTaskAffinity();
    }

protected void dumpTaskAffinity(){
try {
            ActivityInfo info = this.getPackageManager()
                    .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
            Log.i("WooYun", "taskAffinity:"+info.taskAffinity);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

standard-默认模式

       这个模式是默认的启动模式,即标准模式,在不指定启动模式的前提下,系统默认使用该模式启动Activity,每次启动一个Activity都会重写创建一个新的实例,不管这个实例存不存在,这种模式下,谁启动了该模式的Activity,该Activity就属于启动它的Activity的任务栈中。这个Activity它的onCreate(),onStart(),onResume()方法都会被调用。 
配置形式:

<activity android:name=".standard.StandardActivity" android:launchMode="standard" > 
  •  

使用案例: 
       对于standard模式,android:launchMode可以不进行声明,因为默认就是standard。 
       StandardActivity 的代码如下,入口Activity中有一个按钮进入该Activity,这个Activity中又有一个按钮启动StandardActivity。

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by huangshuai on 2016/5/23.
 * Email:huangshuai@wooyun.org
 * Standard模式
*/
public class ActivityStandard extends BaseActivity  {
private Buttonjump;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_standard);

jump= (Button) findViewById(R.id.btn_standard);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                Intent intent = new Intent(ActivityStandard.this, ActivityStandard.class);
                startActivity(intent);
            }
        });
    }
}
  •  

       我们首先进入StandardActivity,进入后再点击进入Standard的按钮,再按四次返回键不断返回。

 

输出的日志如下:

这里写图片描述

       可以看到日志输出了四次StandardActivity的和一次MainActivity的,从MainActivity进入StandardActivity一次,后来我们又按了三次按钮,总共四次StandardActivity的日志,并且所属的任务栈的id都是2087,这也验证了谁启动了该模式的Activity,该Activity就属于启动它的Activity的任务栈中这句话,因为启动StandardActivity的是MainActivity,而MainActivity的taskId是2087,因此启动的StandardActivity也应该属于id为2087的这个task,后续的3个StandardActivity是被StandardActivity这个对象启动的,因此也应该还是2087,所以taskId都是2087。并且每一个Activity的hashcode都是不一样的,说明他们是不同的实例,即“每次启动一个Activity都会重写创建一个新的实例”

singleTop-栈顶复用模式

       这个模式下,如果新的activity已经位于栈顶,那么这个Activity不会被重写创建,同时它的onNewIntent方法会被调用,通过此方法的参数我们可以去除当前请求的信息。如果栈顶不存在该Activity的实例,则情况与standard模式相同。需要注意的是这个Activity它的onCreate(),onStart()方法不会被调用,因为它并没有发生改变。 
配置形式:

<activity android:name=".singletop.SingleTopActivity" android:launchMode="singleTop">
  • 1
  • 1

使用案例: 
ActivitySingleTop.java

/**
 * Created by huangshuai on 2016/5/23.
 * Email:huangshuai@wooyun.org
 * SingleTop模式
*/
public class ActivitySingleTop extends BaseActivity {
private Button jump,jump2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singletop);

jump = (Button) findViewById(R.id.btn_singletop);
jump2 = (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                Intent intent = new Intent(ActivitySingleTop.this, ActivitySingleTop.class);
                startActivity(intent);
            }
        });
jump2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                Intent intent = new Intent(ActivitySingleTop.this, OtherTopActivity.class);
                startActivity(intent);
            }
        });
    }
  •  

OtherTopActivity.java

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by huangshuai on 2016/5/23.
 * Email:huangshuai@wooyun.org
 */
public class OtherTopActivity extends BaseActivity {
private Button jump;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

jump= (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                Intent intent = new Intent(OtherTopActivity.this, ActivitySingleTop.class);
                startActivity(intent);
            }
        });
    }
}
  •  

操作和standard模式类似,直接贴输出日志

这里写图片描述

       我们看到,除了第一次进入SingleTopActivity这个Activity时,输出的是onCreate方法中的日志,后续的都是调用了onNewIntent方法,并没有调用onCreate方法,并且四个日志的hashcode都是一样的,说明栈中只有一个实例。这是因为第一次进入的时候,栈中没有该实例,则创建,后续的三次发现栈顶有这个实例,则直接复用,并且调用onNewIntent方法。那么假设栈中有该实例,但是该实例不在栈顶情况又如何呢? 
       我们先从MainActivity中进入到SingleTopActivity,然后再跳转到OtherActivity中,再从OtherActivity中跳回SingleTopActivity,再从SingleTopActivity跳到SingleTopActivity中,看看整个过程的日志。

 

 

这里写图片描述

       我们看到从MainActivity进入到SingleTopActivity时,新建了一个SingleTopActivity对象,并且task id与MainActivity是一样的,然后从SingleTopActivity跳到OtherActivity时,新建了一个OtherActivity,此时task中存在三个Activity,从栈底到栈顶依次是MainActivity,SingleTopActivity,OtherActivity,此时如果再跳到SingleTopActivity,即使栈中已经有SingleTopActivity实例了,但是依然会创建一个新的SingleTopActivity实例,这一点从上面的日志的hashCode可以看出,此时栈顶是SingleTopActivity,如果再跳到SingleTopActivity,就会复用栈顶的SingleTopActivity,即会调用SingleTopActivity的onNewIntent方法。这就是上述日志的全过程。 
对以上内容进行总结 
       standard启动模式是默认的启动模式,每次启动一个Activity都会新建一个实例不管栈中是否已有该Activity的实例。 
singleTop模式分3种情况

  1. 当前栈中已有该Activity的实例并且该实例位于栈顶时,不会新建实例,而是复用栈顶的实例,并且会将Intent对象传入,回调onNewIntent方法
  2. 当前栈中已有该Activity的实例但是该实例不在栈顶时,其行为和standard启动模式一样,依然会创建一个新的实例
  3. 当前栈中不存在该Activity的实例时,其行为同standard启动模式

       standard和singleTop启动模式都是在原任务栈中新建Activity实例,不会启动新的Task,即使你指定了taskAffinity属性。 
那么什么是taskAffinity属性呢,可以简单的理解为任务相关性。

  • 这个参数标识了一个Activity所需任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用的包名
  • 我们可以单独指定每一个Activity的taskAffinity属性覆盖默认值
  • 一个任务的affinity决定于这个任务的根activity(root activity)的taskAffinity
  • 在概念上,具有相同的affinity的activity(即设置了相同taskAffinity属性的activity)属于同一个任务
  • 为一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task

       很重要的一点taskAffinity属性不对standard和singleTop模式有任何影响,即时你指定了该属性为其他不同的值,这两种启动模式下不会创建新的task(如果不指定即默认值,即包名) 
指定方式如下:

<activity android:name=".ActivitySingleTop" android:launchMode="singleTop" android:taskAffinity="com.castiel.demo.singletop"/>
  •  
<activity android:name=".ActivityStandard" android:launchMode="standard" android:taskAffinity="com.castiel.demo.standard"/>
  •  

singleTask-栈内复用模式

       这个模式十分复杂,有各式各样的组合。在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。 
配置形式:

<activity android:name=".singleTask.SingleTaskActivity" android:launchMode="singleTask" >
  •  

使用案例: 
ActivitySingleTask.java

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by huangshuai on 2016/5/23.
 * Email:huangshuai@wooyun.org
 * SingleTask模式
*/
public class ActivitySingleTask extends BaseActivity {
private Button jump,jump2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_task);

jump = (Button) findViewById(R.id.btn_task);
jump2 = (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                Intent intent = new Intent(ActivitySingleTask.this, ActivitySingleTask.class);
startActivity(intent);
            }
        });
jump2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                Intent intent = new Intent(ActivitySingleTask.this, OtherTaskActivity.class);
startActivity(intent);
            }
        });
    }
}
  •  

OtherTaskActivity.java

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

/**
 * Created by huangshuai on 2016/5/23.
 * Email:huangshuai@wooyun.org
 */
public class OtherTaskActivity extends BaseActivity {
private Button jump;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other_task);

jump= (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                Intent intent = new Intent(OtherTaskActivity.this, ActivitySingleTask.class);
                startActivity(intent);
            }
        });
    }
}
  •  

       现在我们先不指定任何taskAffinity属性,对它做类似singleTop的操作,即从入口MainActivity进入SingleTaskActivity,然后跳到OtherActivity,再跳回到SingleTaskActivity。看看整个过程的日志。

 

 

这里写图片描述

       当我们从MainActiviyty进入到SingleTaskActivity,再进入到OtherActivity后,此时栈中有3个Activity实例,并且SingleTaskActivity不在栈顶,而在OtherActivity跳到SingleTaskActivity时,并没有创建一个新的SingleTaskActivity,而是复用了该实例,并且回调了onNewIntent方法。并且原来的OtherActivity出栈了,具体见下面的信息,使用命令adb shell dumpsys activity activities可进行查看

这里写图片描述

       可以看到当前栈中只有两个Activity,即原来栈中位于SingleTaskActivity 之上的Activity都出栈了。 
       我们看到使用singleTask启动模式启动一个Activity,它还是在原来的task中启动。其实是这样的,我们并没有指定taskAffinity属性,这说明和默认值一样,也就是包名,当MainActivity启动时创建的Task的名字就是包名,因为MainActivity也没有指定taskAffinity,而当我们启动SingleTaskActivity ,首先会寻找需要的任务栈是否存在,也就是taskAffinity指定的值,这里就是包名,发现存在,就不再创建新的task,而是直接使用。当该task中存在该Activity实例时就会复用该实例,这就是栈内复用模式。 
       这时候,如果我们指定SingleTaskActivity 的taskAffinity值。

<activity android:name=".ActivitySingleTask" android:launchMode="singleTask" android:taskAffinity="com.castiel.demo.singletask"/>
  •  

还是之前的操作。但是日志就会变得不一样。

这里写图片描述

       我们看到SingleTaskActivity所属的任务栈的TaskId发生了变换,也就是说开启了一个新的Task,并且之后的OtherActivity也运行在了该Task上 
打印出信息也证明了存在两个不同的Task

这里写图片描述

如果我们指定MainActivity的taskAffinity属性和SingleTaskActivity一样,又会出现什么情况呢。

这里写图片描述

没错,就是和他们什么都不指定是一样的。 
这时候,就有了下面的结论 
singleTask启动模式启动Activity时,首先会根据taskAffinity去寻找当前是否存在一个对应名字的任务栈

  • 如果不存在,则会创建一个新的Task,并创建新的Activity实例入栈到新创建的Task中去
  • 如果存在,则得到该任务栈,查找该任务栈中是否存在该Activity实例 
                  如果存在实例,则将它上面的Activity实例都出栈,然后回调启动的Activity实例的onNewIntent方法 
                  如果不存在该实例,则新建Activity,并入栈 
    此外,我们可以将两个不同App中的Activity设置为相同的taskAffinity,这样虽然在不同的应用中,但是Activity会被分配到同一个Task中去。 
    我们再创建另外一个应用,指定它的taskAffinity和之前的一样,都是com.xingyu.demo.singletask
<activity android:name=".MainActivity" android:launchMode="singleTask" android:taskAffinity="com.castiel.demo.singletask"/>
  • 1
  • 1

然后启动一个应用,让他跳转到该Activity后,再按home键后台,启动另一个应用再进入该Activity,看日志

这里写图片描述

       我们看到,指定了相同的taskAffinity的SingleTaskActivity和OtherActivity被启动到了同一个task中,taskId都为2169。

singleInstance-全局唯一模式

       该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。 
配置形式:

<activity android:name=".singleinstance.SingleInstanceActivity" android:launchMode="singleInstance" >
  • 1
  • 1

使用案例: 
增加一个Activity如下: 
ActivitySingleInstance.java

import android.os.Bundle;

/**
 * Created by huangshuai on 2016/5/24.
 * Email:huangshuai@wooyun.org
 * SingleInstance模式
 */
public class ActivitySingleInstance extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_singleinstance);
    }
}
配置属性如下:
<activity
    android:name=".ActivitySingleInstance"
    android:launchMode="singleInstance">

    <intent-filter>
        <action android:name="com.castiel.demo.singleinstance" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
  •  

使用下面的方式分别在两个应用中启动它

Intent intent = new Intent();
intent.setAction("com.castiel.demo.singleinstance");
startActivity(intent);
  •  

做的操作和上一次是一样的,查看日志

这里写图片描述

       我们看到,第一个应用启动SingleInstanceActivity时,由于系统中不存在该实例,所以新建了一个Task,按home键后,使用另一个App进入该Activity,由于系统中已经存在了一个实例,不会再创建新的Task,直接复用该实例,并且回调onNewIntent方法。可以从他们的hashcode中可以看出这是同一个实例。因此我们可以理解为:SingleInstance模式启动的Activity在系统中具有全局唯一性。

 

singleinstance的Activity启动其他Activity会怎样?

场景一:MainActivity是standard模式,启动了是singleinstance模式的SecondActivity,然后在SecondActivity中再次启动MainActivity,此时会怎样?点击返回返回到哪儿呢?

show code:

 

//MainActivity.java
public class MainActivity extends AppCompatActivity {
  
  private Button button;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    button = findViewById(R.id.button1);
    button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this,SecondActivity.class);
        startActivity(intent);
      }
    });
  }
}

//SecondActivity.java
public class SecondActivity extends Activity {
  private final static String TAG = "spq";
  private Button botton2;
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    botton2 = findViewById(R.id.button2);
    botton2.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        Intent intent = new Intent(SecondActivity.this,MainActivity.class);
        startActivity(intent);
      }
    });
  }
}
//AndroidManifest.xml
?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.bjshipeiqing.testproject">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity"
            android:launchMode="singleInstance"/>
    </application>

</manifest>

代码很简单,这里只是给大家看下,下边的场景不会再贴代码,会把Activity的栈信息dump出来。

 

adb shell dumpsys activity activities
  • 当我点击了MainActivity以后,启动了SecondActivity,此时的栈信息想必大家都已经知道了,两个TaskRecord,里边分别放置了MainActivity和SecondActivity:

     

    微信截图_20190119131143.png

  • 当我点击SecondActivity中的Button再次启动一个MainActivity的时候,会发生什么呢?我们来看栈信息:

微信截图_20190119131351.png

 

看出来了吗?此时在MainActivity的TaskRecord中又启动了一个MainActivity,而不是重新创建一个新的TaskRecord。

  • 此时,我们点击返回,大家猜测现在应该是哪个页面呢?

10.png

 

应用界面的图我就不贴了,从这里可以看出,由于刚才在MainActivity所在任务栈启动了两个MainActivity,此时返回,并不是返回到SecondActivity,而是返回到最初启动的那个MainActivity,此时再点击返回,我们肯定能猜到,此时是要返回到SecondActivity了:

 

11.png

结论:当一个Standard模式的Activity A启动SingleInstance模式的Activity B后,会为B重新创建一个任务栈,此时,如果在B中再次启动A,则会在A的任务栈中再启动一个A,因为此时切回了A任务栈,所以再次点击返回,返回的是第一次启动的A,再返回,才会回到B。

场景二:如果MainActivity是standard模式,SecondActivity是singleinstance模式,ThirdActivity是标准模式,那么ThirdActivity,那么它会在哪个任务栈里边呢?

12.png

可以看到,此时也会在MainActivity里创建ThirdActivity,并不会创建新的TaskRecord。想必此时点返回,大家也猜到应该会到哪儿了吧?没错,跟场景一是一样的,返回了MainActivity,因为此时是在TaskRecord1的栈上:

image.png


此时,再次点击返回,回到ThridActivity。
结论:singleinstance的Activity,启动新的standard Activity时,并不会创建新的TaskRecord。

 

场景三:如果此时ThirdActivity是singTask模式,情况会怎么样呢?

通过上述两种场景,我们猜测,启动的这个ThridActivity,也依然会被放到MainActivity的任务栈里,到底是不是这样呢?我们来看实际情况:

image.png


可以看到,此时情况跟上述两种情景是一样,singleinstance的Activity,并不会为其创建新的taskRecord,而是在启动该页面的TaskRecord里继续进行操作,后续的操作跟上述情景也是一样的:

15.png


结论:singleinstance的Activity,启动新的singleTask Activity时,并不会创建新的TaskRecord。

 

场景四:如果此时启动的Activity为singleTop的呢?

想必验证过了上述三种情景,结论大家一定很清楚了吧,直接来看图:

16.png


结论:singleinstance的Activity,启动新的singleTop Activity时,并不会创建新的TaskRecord。

 

总结

由于singleinstance启动模式的Activity要独占一个TaskRecord的特殊性, 不进行验证不清楚它启动下个Activity到底是怎么样的,通过上述几个小demo的验证,我们可以知道:

  • singleinstance启动的Activity,再次启动Activity,无论要启动的Activity是何种启动模式,都不会为其重新创建TaskRecord,而是在启动它的Activity里按照以往的规律操作;
  • Activity的回退操作是按整个TaskRecord来操作的,在上述情况下切了当前显示的任务栈,那么回退将会按照该栈的压入顺利来进行,并不会严格按照我们操作的先后顺序来执行回退;

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值