Activity的启动模式

引言

当面试官说请你介绍一下activity启动模式,大多数人都能整两句,什么栈顶复用啊栈内复用啊,不过,你肯定你真的懂启动模式吗?android

若是你能回答出下面的问题,那么你能够直接退出当前界面。面试

假设有以下四个activity:浏览器

  1. A(standard)
  2. B(singleTop)
  3. C(singleTask)
  4. D(singleInstance)

它们的启动顺序依次是ABCDABCD,请描述activity栈内变化。bash

基于交互的分析

例: 1,用户在主屏幕中点击应用的图标启动应用后,弹出了第一Activity界面:A,并依次打开了以下界面 A -> B -> C -> D。 2,此时按下home键返回主屏幕,而后从新点击图标启动这个应用,咱们会发现弹出的界面仍是 D 而不是界面 A。 3,当咱们连续点击返回键时,应用中界面会按照启动顺序反向的依次展现,也就是D -> C -> B -> A -> 主屏幕微信

经过这个例子咱们能够知道Android系统会为应用暂时性的保存一组Activity启动链,记录启动顺序,这就引出了第一个概念:任务markdown

任务

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

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

任务能够同时存在多个吗测试

固然能够!spa

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

任务里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_TOPAndroidManifest.xml方式中的singleTop启动模式。

Intent.FLAG_ACTIVITY_NEW_TASKAndroidManifest.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

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

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

相关文章
相关标签/搜索