- 本章知识点:
- Activity 启动模式
- 特殊场景: startActivityForResult
Activity 启动模式
standard
singleTop
singleTask
singleInstance
xml 中使用 launchMode 属性:
<activity
...
android:launchMode="launchMode">
用于测试的代码:打印 Activity 相关信息
open class BaseAct : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LogUtils.e(this.toString() + " : onCreate : ")
}
override fun onResume() {
super.onResume()
printTaskInfo()
}
fun printTaskInfo(){
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val runningTaskInfos = manager.getRunningTasks(1)
val currentTask = runningTaskInfos[0]
val num = currentTask.numActivities
//当前 Activity 所在的 Task 的 id
LogUtils.e(this.toString() + " : this.taskId : " + this.taskId.toString())
//当前 Activity 所在的 Task 中存在的 Activity 实例的数量
LogUtils.e(this.toString() + " : Activity num : " + num.toString())
}
override fun onDestroy() {
super.onDestroy()
LogUtils.e(this.toString() + " : onDestroy : ")
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
LogUtils.e(this.toString() + " : onNewIntent : ")
}
}
standard
standard 是 Activity 默认的启动模式 : 如果在xml中不设置launchMode的属性,默认就是这个启动模式。代码示例: AFirstAct 启动模式为默认 standard :
在这种启动模式下,Activity 在栈内没有位置与数量的限制 : 该 Activity 可以出现在栈的任何位置,可以同时存在多个实例。
举个栗子:如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A –>A。
<activity android:name=".AFirstAct">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
AFirstAct :
class AFirstAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_afirst)
jump.setOnClickListener {
startActivity(Intent(this, AFirstAct::class.java))
}
}
}
场景:连续点击三次按钮,再点击返回三次,最后返回到 launcher 界面。
- 从打印信息可以看出:
- AFirstAct 响应了每一个 Intent ,最终创建了 3 次
- taskId 没有变化过,3 个 AFirstAct 的实例始终在同一栈内
singleTop
singleTop和standard模式非常相似,这两种模式下的Activity都可以为发送过来的Intent创建新的实例。
- 不过差别在于:
- standard 会为每次接收到的 Intent 创建实例。
- 而 singleTop 要求,该模式下的 Activity 接收到 Intent 的时候:
- 如果栈中已经存在该 Activity 的实例了,并且处于栈顶的位置,那么就不再创建新的实例,而是直接复用;
- 但如果栈顶的 Activity 不是想要创建的 Activity,亦或者栈中就不存在该 Activity 的实例,那么就会为此 Intent 再次创建一个新的该 Activity 实例。
- 举个栗子:
- 情景①:如果Activity A的启动模式为 standard ,B 的启动模式为 singleTop,并且A已经启动,在A中启动 Activity B,即调用 startActivity(new Intent(this,B.class)),此时系统会启动一个新的 Activity B 的实例,即当前的桟中的状态为 A -> B,然后在 B 中再次启动 B 本身,此时的由于 B 处于栈顶,那么系统不会重新创建 B,此时的栈的状态依然是 A -> B。
- 情景②:如果Activity A的启动模式为 singleTop ,B 的启动模式为 standard,并且A已经启动;在A中首次启动 Activity B,即调用startActivity(new Intent(this,A.class)),然后在 B 中再次启动A,此时虽然 A 的启动模式是 singleTop,并且栈中并已经存在 A 的实例,系统依然会为 A 创建一个新的实例,此时的栈 A -> B -> A。
代码示例①:
AFirstAct 启动模式为默认 singleTop,在 AFirstAct 中不断跳转 AFirstAct :
<activity
android:name=".AFirstAct"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
AFirstAct :
class AFirstAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_afirst)
jump.setOnClickListener {
startActivity(Intent(this, AFirstAct::class.java))
}
}
}
场景:点击一次跳转按钮,再点击返回,返回到 launcher 界面。
- 从打印信息可以看出 情景①的结论:
- taskId 没有变化过,栈中 Activity 的数量 AFirstAct 的实例始终只有一个
- AFirstAct 响应了启动时 Intent ,创建了 1 个实例,但是从下图可以看出第二次的 Intent,虽然也被响应了,但是并没有创建新的实例,从打印信息就可以看出 Activity 一直是同一个,只是 onNewIntent 被调用了一次,这个方法文章结尾会提一下。
代码示例②:
AFirstAct 启动模式为 singleTop ,ASecondAct 启动模式为 standard:
<activity
android:name=".AFirstAct"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ASecondAct" />
AFirstAct :
class AFirstAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_afirst)
jump.setOnClickListener {
startActivity(Intent(this, ASecondAct::class.java))
}
}
}
ASecondAct :
class ASecondAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.act_second)
jump.setOnClickListener {
startActivity(Intent(this, AFirstAct::class.java))
}
}
}
场景:App 启动后,点击一次跳转按钮从 AFirstAct 跳转到 ASecondAct, 再点击跳转按钮,从 ASecondAct 跳转到 AFirstAct。
- 从打印信息可以看出 情景②的结论:
- AFirstAct 响应了启动时 Intent ,创建了 1 个实例,之后的跳转先是创建了一个 ASecondAct 的实例,再之后,又创建了一次 AFirstAct;从此可以看书,singleTop 的 Activity 只有在栈顶的时候才会存在复用的情形。
singleTask
本文会以较简单的层面介绍 singleTask,亦即默认该模式下的 activity 的 taskAffinity 属性不设置,使用默认值。
- 在上面的大前提下,singleTask 有以下特征:
- 栈内复用:如果当前栈中已经存在该 Activity ,那么系统就不会再次创建新的实例,而是直接复用已经存在的实例,调用 onNewIntent 方法,栈中始终最多只会存在一个该 Activity 的实例。
- 该 Activity 的复用是建立在销毁所有栈中在其上的 Activity。
代码示例:
AFirstAct 启动模式为 singleTask ,ASecondAct 启动模式为 standard:
<activity android:name=".AFirstAct" android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ASecondAct"/>
AFirstAct
class AFirstAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_afirst)
jump.setOnClickListener {
startActivity(Intent(this, ASecondAct::class.java))
}
}
}
ASecondAct
class ASecondAct: BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_asecond)
jump.setOnClickListener {
toast()
startActivity(Intent(this, AFirstAct::class.java))
}
}
}
场景:App 启动后,点击一次跳转按钮从 AFirstAct 跳转到 ASecondAct, 再点击跳转按钮,从 ASecondAct 跳转到 AFirstAct。
- 从打印信息可以看出:
- AFirstAct 响应了启动时 Intent ,创建了 1 个实例,之后的跳转先是创建了一个 ASecondAct 的实例,而从 ASecondAct 跳转至 AFirstAct,这个操作并没有再次创建新的 AFirstAct,仅仅是调用了 onNewIntent。
- 在 AFirstAct 再次响应 Intent 而调用 onNewIntent 时,ASecondAct 先被系统销毁,最终栈中只剩下了一个 Activity 的实例,也就是 AFirstAct。
singleInstance
- 当一个 Activity 被设置为 singleinstance 时,此时启动该 Activity
- 如果还未创建过该Activity的实例,系统会先为该 Activity 创建一个新的任务栈后,再在该栈中创建该 Activity 的实例。该Activity所在的栈为新栈。
- 如果已有存在该 Activity 实例的栈,则不会创建新的任务栈和实例,Activity 会调用 onNewIntent 响应 intent。
- 拥有该 Activity 实例的栈,有且仅会有一个实例,那就是该 Activity 的实例,如果跳转至别的 Activity,其实就是切换栈的行为。
代码示例:
AFirstAct、AThirdAct 启动模式为 standard ,ASecondAct 启动模式为 singleInstance:
<activity android:name=".AFirstAct" android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ASecondAct" android:launchMode="singleInstance"/>
<activity android:name=".AThirdAct" android:launchMode="standard"/>
AFirstAct:
class AFirstAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Logger.addLogAdapter(AndroidLogAdapter())
setContentView(R.layout.activity_afirst)
jump.setOnClickListener {
startActivity(Intent(this, ASecondAct::class.java))
}
}
}
ASecondAct:
class ASecondAct: BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_asecond)
jump.setOnClickListener {
startActivity(Intent(this, AThirdAct::class.java))
}
}
}
AThirdAct:
class AThirdAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_athird)
jump.setOnClickListener {
startActivity(Intent(this, ASecondAct::class.java))
}
}
}
场景:App 启动后,点击一次跳转按钮从 AFirstAct 跳转到 ASecondAct, 再点击跳转按钮,从 ASecondAct 跳转到 AThirdAct,AThirdAct 再次跳转到 ASecondAct。
- 从打印信息可以看出:
- 当第一次点击跳转时,还没有创建过 ASecondAct,此时系统为之创建了一个栈和实例,从打印信息可以看到不同的 taskId 以及调用了 ASecondAct 的 onCreate 函数。
- 第二次点击从 ASecondAct 跳转到 AThirdAct,在打印信息中可以看到 AThirdAct 所在的栈就是 AFirstAct 所在的栈,并且栈中 Activity 的数量已经变成了 2,这其实就是切换栈的行为。
- 第三次点击从 AThirdAct 跳转到 ASecondAct,这是 ASecondAct 中被调用的方法是 onNewIntent,AThirdAct 所在的栈被退到后台,而 ASecondAct 所在的栈被推到前台。
特殊场景: startActivityForResult
对于 startActivityForResult 的用法,本文不再赘述,请自行google。
- 与 startActivityForResult 相对应的方法 onActivityResult,本意是对于 Activity 之间的数据交互而产生的方法。但在不同版本下,startActivityForResult 有着不同的表现。
- 在4.4及以下的版本中,如果要打开的用于数据交互的 Acvivity 的 launchmode 是 singleTask、singleInstance 这两种模式中的任一一个,都会导致在该 Activity 还未启动前,onActivityResult 方法就被调用了,从而导致数据交互的失败。
- 在5.0及以上的版本中,startActivityForResult 这个方法所要跳转的 Activity,无论其是什么启动模式,最终的结果都是是按照 standard 模式的方式运:
- singleTop: 即便当前栈顶的 Activity 与要跳转的 Activity 相同,不会遵循 singleTop 的规则,调用已有栈顶 Activity 的 onNewIntent 方法,而是会会在当前栈上创建一个新的实例入栈,
- singleTask: 在这种模式下,系统不会遵循之前所说的 栈内复用 的规则,也会在当前栈上创建一个新的实例入栈。
- signleInstance:这种模式亦然。
- 。
- 代码示例:
- 以 singleInstance 为例,singleTask 的运行效果是一样的
- AFirstAc 启动模式为 standard ,ASecondAct 启动模式为 singleTask:
<activity
android:name=".AFirstAct"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ASecondAct"
android:launchMode="singleInstance" />
AFirstAct:
class AFirstAct : BaseAct() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Logger.addLogAdapter(AndroidLogAdapter())
setContentView(R.layout.activity_afirst)
jump.setOnClickListener {
startActivityForResult(Intent(this, ASecondAct::class.java), 111)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
LogUtils.e("onActivityResult :::: $requestCode")
}
}
- 4.4及以下版本打印信息。从打印信息可以看出,正如以上描述:
- onActivityResult 被提前调用了。
5.0及以上版本打印信息。从打印信息可以看出,正如以上描述:
- 遵循了 standard 启动模式。
有趣的是,singleTop 这个模式,在低版本下与 startActivityForResult 的协作行为,与高本版是一致的,看来 google 在当时已经意识到这个问题了。
事实上,在同时使用startActivityForResult和启动模式时,需要我们通过实际的现象来确认是否是我们自己想要的效果,尽管在上面已经提到很多关于startActivityForResult的注意点,但是在实际使用的时候,很多效果依然与我们预期的不同。举两个个栗子:
- A的启动模式为standard | B的启动模式为singleInstance
- 情景:ActivityA -> startActivityForResult(ActivityB) -> ActivityB -> startActivity(B)
- 结论:最后从B跳转B,在我的手机上(7.1.1),发现B根本没有启动,而是调用了onNewIntent;包括我之后又改变了跳转:ActivityA -> startActivityForResult(ActivityB) -> ActivityB -> startActivity(A)->ActivityA-> startActivity(B),依然是一样的结果。这种情况下就好似只要B已经存在过,哪怕是拥有两个Activity的栈,也认为他就是B唯一的栈。
- A的启动模式为standard | B的启动模式为singleTask,并且给B设置了与Application不同的taskAffinity属性(关于taskAffinity可以看我后面的文章Android 组件系列 – Activity 栈、taskAffinity、intent/flag)
- 情景:ActivityA -> startActivityForResult(ActivityB) -> ActivityB -> startActivity(B)
- 结论:所得到的结果与 singleInstance 几乎是一样的,就好像在startActivityForResult的作用下,taskAffinity失效了一般。
其实上面两个例子,并不是要得出什么结论,只是告诫:警惕startActivityForResult与launchmode的组合使用。
以上,就是对于 Activity 启动模式的总结,以及 startActivityForResult 对于启动模式的影响。