Android基础之Activity 运行模式与回退栈
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android基础之Activity 运行模式与回退栈相关的知识,希望对你有一定的参考价值。
参考技术ALaunchMode 定义的是activity实例与task之间的关系,可以通过下面的两种方式来定义:
在Activity A中启动B,可以利用Activity B在清单中的launchmode定义,也可以在A中调用startActivity()的时候通过intent的flag传入,当两种方式都有定义,intent的flag参数会覆盖掉B原有的定义。
利用Activity 元素的launchMode属性
launchMode属性指定Activity如何被运行到一个task中。launchMode的值有四种:
默认, 每次启动Activity系统都会产生一个新的实例,并且把intent发送给新产生的实例,这个Activity可以被实例化多次,每个实例可以属于不同的task,每个task也可以保有多个此Activity的实例。
如果当前task 的回退栈栈顶已经存在一个此Activity的实例,系统通过调用这个实例的onNewIntent()方法把intent发送给这个Activity实例,而不是创建一个新的此Activity的实例。这个Activity也可以被实例化多次,每个实例可以属于不同的task,每个task可一个保有多个实例(仅限于此Activity已存在的实例不在栈顶)
注意:
应用场合如下:不想出现2个同样的activity在顶部。比如用户正在一个activity阅读信息,这时来了notification,用户点击后应该更新这些信息,而不是新建一个activity,这样在点击back时,就不会出现回到旧信息activity的情况了。这种情况正是下面这段英语提到的。
Note: When a new instance of an activity is created, the user can press the Back button to return to the previous activity. But when an existing instance of an activity handles a new intent, the user cannot press the Back button to return to the state of the activity before the new intent arrived in onNewIntent()
.
例如,当前回退栈中有A,B,C,D四个Activity,全部是Standard,在D中调用startActivity()去启动B,intent的flag设置成FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_NEW_TASK,系统发现栈中有B,会先销毁这个B,再原位置重建B,清空CD,而不是把这个新建的B的实例压入栈顶,这里之所以会销毁B再新建B,因为B的launchmode是Standard,无论什么情况下启动,都需要new一个B的实例,但如果此时B是SingleTop的,系统会把这个intent通过onNewIntent传给已经在栈中的B的实例,不需要销毁再创建,仍需要清空CD。
系统会创建一个新的task并且把这个实例放在栈底( 此处有疑问,测试发现并不一定是栈底 ),但是,如果在一个单独的task中已经存在一个此Activity的实例,系统会把intent通过onNewIntent()发送给这个实例( 测试发现如果在回退栈中,该Activity的上面还有其他Activity,启动此Activity会清空栈中此Activity上面的其他Activity ),而不是创建一个新的实例。同一时间在只有一个此Activity的实例存在于系统中。
与SingleTask一样,不同的是SingleTask的Activity所在的task中可以有其他的Activity,而SingleInstance的Activity独占一个task,并且在整个系统中只有唯一的一个实例。由这个Activity启动的其他Activity都会在新的task中打开。
另一个例子,系统自带浏览器APP把浏览器Activity声明为SingleTask,通过在Activity标签里的launchMode进行指定,这意味着如果你发送一个intent启动浏览器,不管是为浏览器新开启一个task还是从浏览器已经在后台保有的task中启动浏览器,浏览器Activity与你的APP不在同一个task。
不管一个Activity是不是在一个新的task中启动,点击返回都会返回前一个Activity。不过,如果启动一个LaunchMode为singleTask的Activity,如果该Activity此时在一个处于后台的task中,整个task会变成前台task,此时,回退栈会包含由这个后台task携带过来所有Activity,放在回退栈的栈顶,下图说明这种情况。
在一个新的task里启动Activity. 如果已经有Activity实例运行在某一task中,启动这个Activity会把该实例所在的task带到前台,由该实例的onNewIntent()来接收新的intent。
如果被启动的Activity就是当前的Activity,这个已经存在的实例通过onNewIntent()接收intent,不会产生新的实例。
被启动的Activity如果已经存运行于当前task,回退栈中所有在此Activity上面的Activity都将被销毁,此Activity通过onNewIntent()接收新的intent。
例如,一个task中有A,B,C,D,四个Activity,如果D 调用startActivtiy()启动Activity B,C和D会被销毁,B接收这个intent,回退栈中有A,B。
上例中的Activity B的实例,或者通过onNewIntent()接收新的intent,或者销毁新建来处理新的intent。如果B的launchmode是standard,并且没有设置FLAG_ACTIVITY_SINGLE_TOP,那么B会被销毁重启,如果是其他launchmode或者设置了FLAG_ACTIVITY_SINGLE_TOP,则会通过onNewIntent()接收。
FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_NEW_TASK结合使用会有个不错的效果。
如果启动的Activity位于task的底部,它会把所在task带到前台,并且清理状态至root状态,当从通知栏里打开一个Activity的会非常有用。
Affinity指的是一个Activity偏向于从属于哪个task,默认情况下,一个APP内的所有Activity互相之间共享一个affinity的值,所以,所有同一APP下的所有Activity都偏向于从属于同一个task。但是,这个值是可以更改的,不同APP内的Activity可以共享一个affinity,同一个APP内的Activity也可以被分配不同的affinity的值。
affinity的值可以通过修改Activity标签的taskAffinity属性来修改。
这个属性接收一个String的值,必须在manifest标签范围内是唯一的值,因为系统是通过名称来标识APP的affinity的值的。
Affinity作用于以下两种情况:
通过startActivity()启动一个新的Activity时,默认情况下,新的Activity会被压入与启动者相同的回退栈中。但是,如果在启动Activity的时候,使用了FLAG_ACTIVITY_NEW_TASK 这个标志,系统会为新的Activity寻找一个新的task。通常情况下,是一个新的task。但是也并不是必须的。如果系统中有一个task的affinity值与新的Activity的值相同,新的Activity会被分配到这个task中。如果没有这样的task,就启动一个新的task。如果这个标志产生了一个新的task,当用户点击home键离开的时候,必须要有某种方式能够使用户返回到这个task来。有些实体(例如通知管理器)总是从一个外部task中启动Activity,所以在通过startActivity()启动新的Activity时总是需要传递FLAG_ACTIVITY_NEW_TASK 这个标志。如果你有一个Activity可以被外部实体可能这个标志启动,注意用户可以有一种独立的方式回到启动它的task,例如点击启动图标。
这种情况下,一个Activity可以动启动它的那个task移动到它的affinity值对应的task中,当那个task回到前台。例如,假设,一个报告指定城市天气情况的Activity作为一个旅行APP的一部分,它跟其他处在同一APP的Activity一样有一个相同的affinity值,并且允许通过这个属性来调整目标task。当你的一个Activity启动了这个天气预报Activity,它默认跟你的Activity在一个task里,但是,当旅行APP进入到前台,这个天气预报Activity又会被重新分配给旅行APP并且在旅行APP内展示。
提示:
如果一个APK文件从用户的角度看是多款APP,可能需要这个属性来设置不同的affinity来关联不同的APP。
如果用户离开一个task太长时间,系统会清除task中的所有Activity仅仅保留这下根Activity。当用户返回到这个task的时候,只有这个根Activity会被恢复。系统通过这种方式来处理,是因为经过相当长的一段时间之后,用户已经抛弃他们曾经正在做的事情,计划再回来的时候做点新的事情。
你可以通过如下属性来更改这种行为:
参考文献:
Tasks and Back Stack
<activity>
android 阅读Tasks and Back Stack文章后的重点摘抄
Android基础 - Activity的启动模式
前言:前一篇文章介绍了Android的activity的生命周期方法,activity的启动模式也是一个难点,是因为各种启动模式和标示位太容易混淆。为了满足不同的使用场景,我们必须分清这些启动模式。这一篇文章,我们来了解一下Activity的启动模式,也算是一个深入吧,写的不好,请各位多担待。
Activity的LanuchMode
在我们新建activity时,如果不选择启动模式,则默认使用的是standard模式,这种模式在使用过程中,如果我们多次启动该activity,则会在栈里重复创建多个activity实例,单击back键时,则会一一回退,出栈。任务栈是一种“后进先出”的栈结构,意思就是新入栈的任务会放在栈顶,出栈的时候,由栈顶依次出栈。所以我们在使用activity时不可避免的会出现上述问题,导致栈内有多个重复的activity,这是不合理的,所以我们需要分清楚activity的启动模式,在适合的场景下,使用合适的启动模式,这就是我们的目的。首先我们先来看一下activity都有哪些启动模式,然后我们通过实例来分别认识一下它们。启动模式分别有以下四种模式:
- standard模式: 标准模式,也就是默认模式,每次启动一个activity的时候,都会创建一个新的实例,不管这个实例是否存在。被创建的实例,生命周期都符合典型情况下的Activity生命周期。一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种模式下,谁启动了这个activity,那么这个activity就会放在谁的任务栈中。当我们用AoolicationContext 去启动standard模式的activity的时候,就会出现:
Caused by: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
这样的异常。其实很好理解,就是因为,非Activity类型的Context(如ApplicationContext) 并没有所谓的任务栈,所以就会启动失败。解决方法就是为启动的activity指定FLAG_ACTIVITY_NEW_TASK标记位。这样启动这个activity的时候就会创建一个新的任务栈,并且将activity放进任务栈。此时activity其实是以singleTask模式启动的。
具体实例如下:
//启动自己
btn_start_myself.setOnClickListener
var sIntent = Intent()
sIntent.setClass(MainActivity@this,MainActivity::class.java)
startActivity(sIntent)
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
点击3次启动自己,然后打开dos命令行,输入adb shell dumpsys activity,找到如下位置,可以看到,Running activitys 中有4个activity,分别就是刚才启动的3个,再加上初始一个。所以使用该模式时,启动请求都会创建新的activity实例,并放入栈内。
-
singleTop模式: 顾名思义,栈顶复用模式。在这种模式下,如果新activity已经位于栈顶,则新的activity不会被创建,同时会调用它的onNewIntent方法,通过此方法我们可以取出当前请求信息。此模式启动的activity不会调用onCreate、onStart方法,因为该activity没有重建或者重新显示。
示例如下:将上述的MainActivity的启动模式改为singleTop,然后多次点击启动自己,我们先看看生命周期回调日志:
2019-02-16 22:59:02.165 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-16 22:59:02.166 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onPause(MainActivity.kt:43)
2019-02-16 22:59:02.166 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-16 22:59:02.166 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onPause
2019-02-16 22:59:02.166 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-16 22:59:02.166 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-16 22:59:02.167 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onNewIntent(MainActivity.kt:79)
2019-02-16 22:59:02.167 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-16 22:59:02.167 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onNewIntent
2019-02-16 22:59:02.167 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-16 22:59:02.167 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-16 22:59:02.168 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onResume(MainActivity.kt:61)
2019-02-16 22:59:02.168 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-16 22:59:02.168 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onResume
2019-02-16 22:59:02.168 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-16 22:59:02.373 13586-13604/com.xyd.activitylaunchmodedemo I/zygote64: Compiler allocated 6MB to compile void android.view.ViewRootImpl.performTraversals()
2019-02-16 22:59:20.825 13586-13604/com.xyd.activitylaunchmodedemo I/zygote64: Do full code cache collection, code=124KB, data=97KB
2019-02-16 22:59:20.827 13586-13604/com.xyd.activitylaunchmodedemo I/zygote64: After code cache collection, code=104KB, data=68KB
2019-02-16 23:00:03.094 13586-13604/com.xyd.activitylaunchmodedemo I/zygote64: Do partial code cache collection, code=121KB, data=91KB
2019-02-16 23:00:03.095 13586-13604/com.xyd.activitylaunchmodedemo I/zygote64: After code cache collection, code=121KB, data=91KB
2019-02-16 23:00:03.095 13586-13604/com.xyd.activitylaunchmodedemo I/zygote64: Increasing code cache capacity to 512KB
2019-02-16 23:00:33.628 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-16 23:00:33.628 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onPause(MainActivity.kt:43)
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onPause
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onNewIntent(MainActivity.kt:79)
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onNewIntent
2019-02-16 23:00:33.629 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-16 23:00:33.630 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-16 23:00:33.630 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onResume(MainActivity.kt:61)
2019-02-16 23:00:33.630 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-16 23:00:33.630 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onResume
2019-02-16 23:00:33.630 13586-13586/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
然后我们在看看栈内activity的数量:
所以就算是多次点击,栈内也还是只有一个activity。
如果我们用另一个activity启动singleTop模式的activity,看一下是否能复用。
SecondActivity部分代码:
//启动第一个activity
btn_start_1.setOnClickListener
var sIntent = Intent()
sIntent.setClass(SecondActivity@this,MainActivity::class.java)
startActivity(sIntent)
将MainActivity的启动模式改为singleTop:
<activity android:name=".SecondActivity">
</activity>
<!--默认就是standard模式-->
<activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
运行之后,在MainActivity中启动SecondActivity,然后在SecondActivity中启动MainActivity,此时我们再看栈内activity的情况:
可以看到此时栈内有3个activity,分别是两个MainActivity和一个SecondActivity,由此说明singleTop只是在该activity在栈顶时才能复用。
-
singleTask模式:栈内复用模式。这是一种单实例模式,这种模式下,只要是栈内存在该activity实例,多次启动后不会创建新的实例,而是会复用该activity,并且调用onNewIntent方法。例如:Activity A 是singleTask模式启动的,再次启动时,系统会先在栈内找是否存在A的实例的任务栈,如果不存在,则会创建新的任务栈,并将A的实例放在栈中,如果存在A所需的任务栈,则需要看该栈中是否存在A的实例,如果存在则直接调用onNewIntent方法,如果不存在,则创建A的实例。
还是上述两个activity,但是我们修改一下MainActivity的启动模式,并且操作步骤还是上述步骤,我们看一下栈内activity的情况:
此时我们可以看到栈内只有一个activity,这就是singleTask的另一个特性,就是说如果singleTask模式的activity在栈内存在,并且在它之上还有别的activity,则再次启动这个activity的时候,在它之上的所有activity全部出栈,然后直到该activity位于栈顶截止。
-
singleInstance模式:但是萝莉模式。这是一种加强的singleTeask模式。此模式的activity只会单独存在以一个任务栈中。例如:activity A 是以singleInstance模式,当A 启动后,系统会为它创建一个单独的任务栈,并将A放入栈中,后续的请求不会再创建新的A,除非这个任务栈被销毁。
我们将上述MainActivity的启动模式改为singleInstance,然后由MainActivity启动SecondActivity,然后由SecondActivity启动MainActivity,此时查看栈内情况如下:
我们会发现一个问题,栈内现在只有两个activity实例,而SecondActivity启动模式为standard,照理说,应该存在多个activity的,但是为什么只有两个呢?我们来看一下生命周期日志:
2019-02-17 00:23:34.490 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.491 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onPause(MainActivity.kt:42)
2019-02-17 00:23:34.491 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-17 00:23:34.491 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onPause
2019-02-17 00:23:34.491 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.539 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.540 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ SecondActivity.onRestart(SecondActivity.kt:57)
2019-02-17 00:23:34.540 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-17 00:23:34.540 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onRestart
2019-02-17 00:23:34.540 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.540 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.541 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ SecondActivity.onStart(SecondActivity.kt:39)
2019-02-17 00:23:34.541 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-17 00:23:34.541 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onStart
2019-02-17 00:23:34.541 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.541 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.542 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ SecondActivity.onResume(SecondActivity.kt:45)
2019-02-17 00:23:34.542 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-17 00:23:34.542 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onResume
2019-02-17 00:23:34.542 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.586 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.587 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onSaveInstanceState(MainActivity.kt:84)
2019-02-17 00:23:34.587 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-17 00:23:34.587 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onSaveInstanceState
2019-02-17 00:23:34.587 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.588 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➀: ╔══════════════════════════════════════════════════════════════
2019-02-17 00:23:34.588 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➁: ║ MainActivity.onStop(MainActivity.kt:66)
2019-02-17 00:23:34.588 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➂: ╟──────────────────────────────────────────────────────────────
2019-02-17 00:23:34.588 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➃: ║ onStop
2019-02-17 00:23:34.588 18292-18292/com.xyd.activitylaunchmodedemo D/LogUtils➄: ╚══════════════════════════════════════════════════════════════
由MainActivity再次启动SecondActivity时,没有重新创建activity,只是重新显示出来,调用了onRestart方法。这个问题后续研究。
未完待续。。。。。
以上是关于Android基础之Activity 运行模式与回退栈的主要内容,如果未能解决你的问题,请参考以下文章
Android开发基础之Activity,图解Activity状态与生命周期