为了实现Activity的复用,Android系统提供了Activity的启动模式,即LaunchMode,根据情况选择创建还是复用实例。启动模式包含以下四种:Standard(默认)、SingleTop、SingleTask、SingleInstance。标准模式每一次都会去创建新的Activity,其他三种会根据情况选择创建还是复用。
由于被创建的Activity会运行与启动的Activity栈,其次启动Activity中参数Intent的Context类必须使用Activity的Context启动,如MainActivity.this,不能使用Application的Context。Application的Context不包含Activity栈,默认的Activity栈是包名。
栈的进出是先进后出、后进先出。
标准模式不用说,先来看看栈顶复用模式,即SingleTop模式,只有当Activity位于栈顶时,再次启动当前的Activity,复用栈顶的Activity实例,不会重新创建;如果位于栈内,与标准模式相同,仍然会去创建实例。
当一个处于栈顶,且启动模式是SingleTop的Activity重复启动自己,它的生命周期变化是onStart()—— onResume()变为 onNewIntent() —— onResume(),正常创建的声明周期中含onStart(),栈顶复用的声明周期中含有onNewIntent()。
上面TestBActivity就是栈顶复用模式下的Activity
那么总结一下,栈顶复用模式是当实例位于栈顶时重复创建,调用onNewIntent()复用实例;当实例位于栈中时,重复创建们不会复用实例,而是创建新的实例,与标准模式相同,示意图如图所示:
接着是栈内复用模式(SingleTask),只要栈中存在栈内复用模式的Activity,无论是在栈顶还是栈内,多次调用时都会复用实例,不会重新创建,即完全的单例模式。除此之外,站内复用模式还可以设置不同的任务栈,即taskAffinity属性。
栈内复用模式包含以下几种情况:
1、未设置taskAffinity属性,任务栈相同。当位于栈内时,会重置为栈顶,并清除其上的其他实例,有清除顶部的效果,位于栈顶时不会重复创建。
2、设置taskAffinity属性,任务栈不同,修改Activity的AndroidManifest配置,如下:
需要注意:taskAffinity属性适应于singleTask,配合使用,在SingleTop和Standard中无效!!!、
当taskAffinity指定的任务栈不存在,初次启动栈内复用模式的实例,会创建新的任务栈,并将实例放在其中。
这是一个比较复杂的模式,特别是设置了不同任务栈之后。当启动一个启动模式是单一任务栈模式且不同任务栈的Activity,会创建一个新的任务栈(首次),然后再次启动一个普通的Activity,这个Activity会在新的任务栈栈顶,此时应用中有两个任务栈,一个是默认的,一个是指定的。当任务栈不同时,启动不同栈内的实例,会导致任务栈之间的切换,后台任务栈会位于前台。如按手机的Home键,然后再次启动应用,则默认任务栈会启动,并不是原来的任务栈。然后按返回键,一直按,直到默认的任务栈中最后一个Activity都出栈,这时会返回至桌面,而另外的任务栈则留在应用中,避免重复创建,符合栈内复用模式。当再次启动应用,由MainActivity启动启动模式是SingleTask的Activity,如果还在内存中,则切换至原来的任务栈(如果栈顶有其他Actibity,会触发清除顶部机制)。
最后来个示意图:
来,最后就是单实例模式了(SingleInstance),这个模式简单来说就是系统为其创建一个单独的任务栈,以后每次使用时都会使用这个实例,直到被销毁,属于真正的单例模式。与栈内复用模式不同,栈内复用模式是指定taskAffinity属性,所创建的任务栈并不独享,仍可以添加其他的Activity;而单实例模式所创建的任务栈只含有这个实例,所以说是真正的单例模式。
现在来做一个实验:
MainActivity:应用默认Activity,启动模式Standard
TestAActivity:Standard
TestBActivity:SingleInstance
TestCActivity:Standard
操作顺序: MainActivity 启动 TestAActivity, TestAActivity 启动 TestBActivity, TestBActivity 是单实例模式, TestBActiv即启动 TestCActivity,则 TestCActivity 位于栈顶。 在应用内存中, 存在两个任务栈,一个是 MainActivity、 TestAActivity、 TestCActivity,即默认的任务栈,一个 是 TestBActivity,即单实例模式的任务栈。 两个任务栈的名称相同,都是包名,但是栈的序号 不同,为不同任务栈。也可以使用 taskAffinity 属性,重新为单实例模式的任务栈指定新的包名。 因为 TestBActivity 是 单实例模式,所创建的任务栈中无法添加 Activity,所以 TestCActivity 仍存储于默认的任务栈中。
还有两种后续操作:第一种是关闭当前页面 TestCActiv栈,第二种是 TestCActivity 再次启 动 TestBActivity。 首先介绍第一种操作,当关闭 TestCActivity 时,优先出栈当前任务栈的全部 实例,再出栈第二层任务栈的全部实例,依次类推。 虽然 TestCActivity 由 TestBActivity 启动, 但是任务栈不间,如关闭 TestCActivity,则位于栈顶的实例是 TestAActivity,而不是 TestBActivity。 接着,关闭 TestAActivity,位于栈顶的实例是 MainActivity,关闭 MainActivity,最终位于栈顶的实例才是不同任务栈中单实例模式的 TestBActivity。
其次介绍第二种操作,当 TestCActivity 再次启动 TestBActivity 时 ,单实例模式的 TestBActivity 位于栈顶,接着才是默认任务栈中的 MainActivity、 TestAActivity、 TestCActivity。
示意图:
除了在 AndroidManifest 中设置 launchMode ,修改 Activity 的启动模式,也可以在 startActivity()的 Intent 中设置启动标志位,即 Flag,以修改启动模式。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
常用的标志位如下:
© FLAG ACTIVITY SINGLE TOP : 同楼顶复用的启动模式。
© FLAG ACTIVITY NEW TASK:同校内复用的启动模式,单独使用不具备清除 顶部的效果。
© FLAG ACTIVITY CLEAR TOP :如果是校内复用的启动模式,会清除椅上其他 实例,复用实例,调用 onNew Intent()方法;如果是标准的启动模式,即默认模式, 则会清除自己和其他实例,再重新创建,调用 onCreate()。
在设置启动模式时, AndroidManifest 与 startActivi ty()的 Intent 还有一些区别:
AndroidManifest 无法设置清除顶部的效果,只有枝内复用的启动模式含有清除顶 部的效果;而 startActivity()的 Intent 支持设置 FLAG_ACTIVITY_ CLEAR_ TOP 属性,实现清除顶部的效果,并且在标准的启动模式中也可以应用。startActivity()的 Intent 无法设置单实例的启动模式。
两者任选其一使用即可,如果两种方式均设置, startActivity()的 Intent 的优先级大于 AndroidManifest 的优先级, startActivity()的 Int巳nt会覆盖 AndroidManifest 的效果。
同时, startActivityForResult()也是启动 Activity 的一种方式,不同于 startActivity(),需要设 置返回值。 startActivityForResult()使用启动( LaunchMode )模式启动 Activity 时,也会有一些 不同,其效果是可以正常传递返回值,但是无法复用实例,即无法触发单例效果,会重复创建 多份实例。
操作顺序为 MainActivity 启动 TestAActivity,TestAActivity 启动 TestBActivity, TestBActivity 是战内复用模式。 TestBActivity 使用 startActivityForResult()重复创建自己时,会生成一份新的 示例,而 TestBActivity 使用 startActivity()重复创建自己时,会复用默认实例,即产生单例效果。 以下是, startActivityForResult()重复创建的任务栈。
因为 startActivityForResult()需要返回值,任何时候都需要保留实例,接收返回值,这一特 性覆盖原有的单例效果。需要注意的是:在 Android 4.x 以下版本中,通过 startActivityForResult() 启动 singleTask,无法正常获取返回值;在 Android 5.x 以上版本中已修复此问题,但是考虑兼 容性的问题,不推荐 startActivityForResult()和单例(栈顶复用 、 栈内复用、单实例)的启动模式同时使用。
总结:
在 Android 开发中, Activity 是核心组件,开发者需要认真掌握的tivity 的方方面面, Activ町 的启动模式关系到 Activity 在任务战中的存在形式,影响 Activ町的展示顺序。 Android 系统并 未提供操作任务拢的接口,无法任意改变 Activity 的存储顺序,找中 Activity 排列都依赖于启动 模式。 当用户点击后退时, Activity 出钱;当用户创建页面时, Activity 人拢。 Android 系统的 任务技体系分为三层:
第一层是 Stack,每一个应用属于一个 Stack ,一一对应。 。
第二层是 TaskRecord ,它含有一组 Activity ,通过单例模式( SingleTask 和 Singlelnstance )支持创建多个 TaskRecord。
第三层是 ActivityRecord ,每一个 Activity 实例就是一个 ActivityRecord,相同的 Activity 支持存在多份实例。
启动模式的本质就是解决如何避免相同的 Activity 创建多个实例。 对于标准模式,在每次启动 Activity 时,都会创建一份实例,在某些情况下,这属性系统资源的浪费,即当重复创建 相同的 Activity 时,仅需一份实例即可。 因此, Android 系统通过启动模式提供三种解决方案:
1、栈顶复用模式:当 Activity 位于栈顶时,重复创建实例,直接复用;当 Activity 位于战中时,与标准模式相同 。-
2、栈内复用模式:当 Activity 位于栈顶时,同栈顶复用模式;当 Activity 位于栈中 时 ,清除其上的全部实例 ,直至位于栈顶。
3、单实例模式:直接创建一个任务栈,无论 Activity 在任何位置,都会复用这个实 例。
需要注意的是,栈内复用模式与单实例模式都支持创建新的任务栈,这会涉及前台任务栈 与后台任务栈的关系,即 Stack 中含有多个 TaskRecord。 如果使用系统的默认后退操作,即出 校操作,只有当前任务栈全部出栈完毕时,才会切换至其他的任务栈,或者通过启动单例模式 的 Activity,才会触发切换任务栈。这个地方很容易操作困惑,即入栈的顺序与出栈的顺序不同。
设置 Activity 的启动模式包含两种方式:一种是在 AndroidManifest 中的 Activity 声明 launchMode 属性, 一种是在startActivity()的 Intent 中添加 Flag 属性;两者各有局限,也可以同 时使用,当出现冲突时, startActivity()的 Intent 的设置覆盖 AndroidManifest。 最后,注意 startActivityForResult()需要处理返回值,这会导致单例模式的失效,无法复用实例。
在 Android 开发中,当需要重复使用某个页面时,可以选择不同的启动模式,避免重复创 建页面,消耗系统资源。 只有熟练掌握 Activity 启动模式的各种效果,才能在开发中游刃有余, 编写出优雅的代码,成为高级 Android 开发工程师。