Activity作为Android的四大组件之一,也是Android最基础的东西,是非常重要的部分。搞清楚Activity的生命周期和启动模式,能够使我们设计出更流畅的程序。
本文主要记录我对Activity生命周期和启动模式的探索,从实践出发来掌握理论。
一、基础知识
1.返回栈
Android是通过返回栈来管理Activity的。每启动一个Activity,就会把该Activity放入栈中,并且处于栈顶;当去销毁一个Activity时,就会把该Activity从栈顶去除。
所以,处于界面前台并且能与用户交互的Activity一定处于返回栈的栈顶。而当按返回或finish()结束一个Activity的时候,就会显示上一个Activity。
2.生命周期
Activity一共有四种状态:
-
运行状态:
处于返回栈栈顶的Activity处于运行状态。
-
暂停状态:
当Activity不处于栈顶,且依然可见时就处于暂停状态。
-
停止状态
当Activity不处于栈顶且不可见时处于停止状态。这时候系统可能还保留着该Activity的一些变量在内存里。
-
销毁状态
当Activity从返回栈中移除之后,就变成了销毁状态。
值得注意的是暂停状态和停止状态的区别。为什么不处于栈顶的Activity还能可见?这个到后面会详细说明。
Activity一共有七个回调方法来管理生命周期:
-
onCreate():
在Activity被创建的时候调用。此时会完成一些初始化操作,比如加载布局绑定事件。
-
onStart():
在Activity由不可见变为可见的时候调用。
-
onResume()
当Activity准备好和用户交互的时候调用,此时的Activity一定处于栈顶。
-
onPause()
当系统要去启动或恢复另一个Activity时调用。此时系统会释放掉一些资源,只保留一些关键数据。
-
onStop()
在该Activity变为不可见时调用。
-
onDestroy()
当一个Activity移除返回栈时调用。
-
onRestart()
当Activity由停止状态变为运行状态时调用。
3.启动模式
Activity一共有四种启动模式
-
standard
这是Activity默认的启动方式。在这个模式下,每次启动一个Activity都会创建一个新的Activity放在栈顶,无论之前返回栈中是否存在该Activity。
-
singleTop
在启动一个Activity的时候,系统会去判断返回栈的栈顶是不是要启动的Activity。如果是,那么就不再创建一个新的了。如果不是,那就创建一个新的Activity放到栈顶。
-
singleTask
这个模式下,系统会去返回栈中寻找是否有该Activity的实例。如果在站内找到了实例,就把实例前面的所有Activity都弹出栈,来使该实例处于栈顶的位置。
-
singleInstance
因为一个应用程序对应一个返回栈。如果一个Activity可以供多个应用程序调用,那么前面的几种方式无法做到共用。而singleInstance模式下有一个独立的栈,用来管理singleInstance模式的Activity。
前面的基础知识都是一些生涩的文字描述。想要理解比较困难,所以以下就开始了通过实践去理解这些知识。
二、实践部分
为了能够清晰地看清楚什么时候调用了生命周期回调函数,我们可以先写一个自己的BaseActivity,在生命周期回调函数里添加日志打印。后面的Activity去继承自己写的这个Activity即可。
open class BaseActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(this.toString(), "onCreate()")
}
override fun onStart() {
super.onStart()
Log.d(this.toString(), "onStart()")
}
override fun onResume() {
super.onResume()
Log.d(this.toString(), "onResume()")
}
override fun onPause() {
super.onPause()
Log.d(this.toString(), "onPause()")
}
override fun onStop() {
super.onStop()
Log.d(this.toString(), "onStop()")
}
override fun onDestroy() {
super.onDestroy()
Log.d(this.toString(), "onDestroy()")
}
override fun onRestart() {
super.onRestart()
Log.d(this.toString(), "onRestart()")
}
}
1.生命周期
我准备了三个Activity:
-
LifeActivity
-
NormalActivity
-
DialogActivity
然后在LifeActivity中添加按钮,让他能够跳转到另外两个Activity去。
btn_startNormalActivity.setOnClickListener {
val intent = Intent(this, NormalActivity::class.java)
startActivity(intent)
}
btn_startDialogActivity.setOnClickListener {
val intent = Intent(this, DialogActivity::class.java)
startActivity(intent)
}
其中DialogActivity是一个对话框式的Activity。在AndroidManifest.xml去指定
<activity
android:name=".activityStudy.DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog"
android:label="dialogActivity"/>
其中android:theme="@style/Theme.AppCompat.Dialog"就制定了该Activity是一个对话框式的Activity。
1.1 启动和销毁LifeActivity
先启动程序看一下,发现了前三个回调函数,此为Activity的创建三部曲。
当按下返回键后,就出现了一次完整的生命周期。
1.2 启动一个普通的Activity
我们从LifeActivity中启动NormalActivity。
这里就发现,首先是LifeActivity调用了onPause()方法释放了一些资源。但此时LifeActivity依然是可见状态。当NormalActivity完成了创建三部曲之后,LifeActivity才调用了onDestroy()方法。
那我们再从NormalActivity返回试一下。
能看到,依然是当前处于栈顶的NormalActivity先调用了onPause()方法,然后等LifeActivity处于栈顶之后再调用了onStop()和onDestroy()。
这里能发现不同的是,因为LifeActivity之前并没有被销毁,而是处于停止状态,所以此时他恢复栈顶的调用顺序是onRestart() -> onStart() -> onResume()
1.3 启动一个对话框式的Activity
我们继续试一下启动对话框。启动后是这个样子
这个DialogActivity并没有沾满整个屏幕,并且LifeActivity依然处于可见状态。不过此时的LifeActivity并不能与用户交互,也就是它不在栈顶。这时的LifeActivity就处于暂停状态。
查看回调函数,就会发现此时的LifeActivity并没有调用onStop()。
这也就是暂停状态和停止状态的区别。
返回后的函数调用
当LifeActivity需要回到栈顶时,此时就只需要调用onResume()函数了。
1.4 小结
Activity的创建三部曲是onCreate() onStart() onResume()
当从一个Activity A去启动Activity B的时候,会先调用A的onPause(),然后等待B创建完成。
如果B会沾满整个屏幕,使A完全不可见,那么会调用A的onStop(),使A处于停止状态。此时要恢复A时,会走onRestart() -> onStart() -> onResume()的步骤。
如果B只是一个对话框式的Activity,那么A就会处于暂停状态。要恢复A的时候,只会调用onResume()
2.启动模式
这里我们沿用LifeActivity,添加一个按钮来再启动一个LifeActivity。
val intent = Intent(this, LifeActivity::class.java)
startActivity(intent)
2.1 standard模式
此时LifeActivity处于默认的standard模式。我们启动试一下。
我们可以看到,这里的两个LifeActivity并不是同一个实例,而是新建了一个LifeActivity。
虽然很少有自己启动自己的操作,但是在这样的模式下会消耗很多的资源,系统里会有多个实例。
2.2 singleTop模式
那我们把LifeActivity的启动模式修改一下,改成singleTop模式。
在AndroidManifest.xml里修改。
<activity android:name=".activityStudy.LifeActivity"
android:label="lifeActivity"
android:launchMode="singleTop"/>
再启动试一试。
会发现无论点多少次,都只会调用onPause()和onResume()两个方法。标识也是一样的,说明是同一个实例。
那这时候我们又得搞点新操作了。
我们新添加一个MiddleActivity,可以从LifeActivity跳转到MiddleActivity。MiddleActivity也可以跳转到LifeActivity和它自己。
然后我们来进行这样的操作:LifeActivity -> MiddleActivity -> LifeActivity
C:\Users\Joker\AppData\Roaming\Typora\typora-user-images\image-20211227225906078.png
根据日志来看,确实是创建了一个新的LifeActivity,而不是同一个实例。
此时返回栈的情况应该是这样:
此时处于栈顶的LifeActivity并不是最初的那个Activity了。这也就是singleTop模式的局限性。
此时如果我们按下返回键,会返回到MiddleActivity,再次按下返回键才会返回到最初的LifeActivity。可根据上面的返回栈图片来理解。
2.3 singleTask模式
那么我们再给LifeActivity换一个启动模式。换成singleTask试一下。
这里我们可以看到LifeActivity就是同一个实例了。并且中间的MiddelActivity已经被销毁了。
此时的返回栈情况应该是这样:
所以此时我们按返回键的话,并不会返回到MiddleActivity,而是直接销毁掉这个LifeActivity了。
无论中间有多少个其他的Activity,最终都会被弹出栈。
这里我用MiddleActivity启动了6个MiddleActivity。会发现他们都被销毁了。
这样的模式虽然能够保证整个栈里只有一个实例,但是它会将上方所有Activity都弹出栈,也存在一定的风险。
2.4 singleInstance模式
这是最特殊的启动模式。一个应用程序对应一个返回栈,而singleInstance模式下的Activity会被一个额外的返回栈统一管理。
当一个Activity会被多个应用程序。
这里我用到三个新的Activity来实践:
-
FirstActivity
-
SecondActivity 启动模式为singleInstance
-
ThirdActivity
然后按顺序启动他们,并在日志里打印出他们的taskId
Log.d(this.toString(), "task id is $taskId")
通过日志能够看到,FirstActivity和ThirdActivity属于同一个返回栈,而SecondActivity则属于另一个返回栈。
并且在从FirstActivity跳转到SecondActivity和从SecondActivity跳转到ThirdActivity的时候,有一个明显的类似切换应用的动画,而不是像之前的在同一应用内跳转。
并且当我从ThirdActivity点击返回后,是先返回的FirstActivity,然后再返回到了SecondActivity。并不是意料之中的返回顺序。此时是返回栈情况应该是这样:
所以当我们从ThirdActivity返回时,是按照task54来返回的。当task54空了之后,才会到task55的栈顶。
所以在这样的模式下,如果FirstActivity下面还有很多Activity,要后退到这些Activity全部销毁后才会返回到SecondActivity。所以这样的模式下跟前三个模式有很大的不同。
三、提出问题
1.为什么从停止状态恢复的Activity会调用一个onRestart()?
前面的实践可以得出结论,当Activity从暂停状态恢复到运行状态时,只需要调用onResume()。而从停止状态恢复的时候,不光要调用onStart()和onRestart(),还会先调用onRestart()。这是为什么?这个函数又做了什么事?
2.singleInstance模式下独立的栈是用singleTop模式还是singleTask模式?
既然singleInstance模式的Activity由一个统一的栈管理,这么这个栈的管理方式是singleTop还是singleTask或者是其他?
这里猜测应该是singleTask模式。毕竟要保证全局只有一个实例,如果是singleTop的话还是可能会出现多个实例。
四、探索问题
1.onRestart()调用
这部分涉及到源码分析,我会另外用一篇笔记来探究。
2.singleInstance里栈的管理模式
这里我在前面的基础上又引入了一个ForthActivity,并把启动模式设置为了singleInstance.
FirstActivity的启动模式设置为singleTop.
现在能从FirstActivity跳转到SecondActivity和ForthActivity,并且SecondActivity和ForthActivity也有跳转到FirstActivity的按钮。
于是就开始了一系列的操作。从FirstActivity跳转到SecondActivity,再跳转回FirstActivity;然后又从FirstActivity跳转到ForthActivity。本来这顿操作我以为会形成的返回栈情况是:
结果一看日志,才发现他们在各自的栈里互不打扰。
原来singleInstance的管理方式就是给这个Activity分配一个独立的栈。当有多个Activity是这个启动模式的时候,就会有多个各自的栈。这也就保证了全局唯一,并且也不用管是singleTop还是singleTask,反正栈里也只有这一个Activity。