Activity的启动模式有四种:
启动模式可通过给activity标签指定android:launchMode属性来选择启动模式。
standard是Activity默认的启动模式,在不进行显式指定的情况下,所有Activity都会自动使用这种启动模式。在standard模式下,每当启动一个新的Activity,其就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎该Activity是否已经在返回栈中存在,每次启动都会创建一个该Activity的新实例。
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("FirstActivity", this.toString())
- setContentView(R.layout.first_layout)
- button1.setOnClickListener {
- val intent = Intent(this, FirstActivity::class.java)
- startActivity(intent)
- }
- }
上边代码重写了onCreate方法,并在FirstActivity的基础上启动FirstActivity。
运行程序后,连续点击按钮会出现下列打印信息:
- 2022-09-12 14:28:46.602 12248-12248/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
- 2022-09-12 14:29:37.635 12392-12392/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
- 2022-09-12 14:29:41.626 12392-12392/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@b73636d
- 2022-09-12 14:29:43.513 12392-12392/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@3ad27ac
- 2022-09-12 14:29:45.070 12392-12392/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@6b43541
可以看出,每点击一次按钮,就会创建出一个新的FirstActivity实例,此时返回栈中存在5个FirstActivity实例,因此需要点击5次back键才能够退出程序。
singleTop是在启动Activity时,如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用,而不会创建新的Activity实例。
这里在AndroidManifest.xml文件中修改FirstActivity的启动模式:
- <activity
- android:name=".FirstActivity"
- android:launchMode="singleTop"
- android:exported="true"
- android:label="This is FirstActivity">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- intent-filter>
- activity>
此时重新运行程序,多次点击按钮的打印信息为:
2022-09-12 14:37:36.398 12741-12741/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
即只有一个FirstActivity实例,而FirstActivity处于返回栈的栈顶,每当想要再启动一个FirstActivity时,都会直接使用栈顶的FirstActivity,因此一次back就可以退出程序。
不过当FirstActivity未处于栈顶位置时,再启动FirstActivity还是会创建新的实例的。
比如修改FirstActivity和SecondActivity的onCreate方法:
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("FirstActivity", this.toString())
- setContentView(R.layout.first_layout)
- button1.setOnClickListener {
- val intent = Intent(this, SecondActivity::class.java)
- startActivity(intent)
- }
- }
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("SecondActivity", this.toString())
- setContentView(R.layout.second_layout)
- button2.setOnClickListener {
- val intent = Intent(this, FirstActivity::class.java)
- startActivity(intent)
- }
- }
运行程序,点击按钮后的打印信息:
- 2022-09-12 14:43:35.097 13049-13049/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
- 2022-09-12 14:43:45.900 13049-13049/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@7db5197
- 2022-09-12 14:43:47.905 13049-13049/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@dd2bdb9
- 2022-09-12 14:43:49.374 13049-13049/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@de0371a
- 2022-09-12 14:43:50.622 13049-13049/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@8e59af6
此时FirstActivity和SecondActivity互相层叠,使FirstActivity不在栈顶,以致多次创建。
singleTask是每次启动Activity时,系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有其它Activity统统出栈,如果没有发现则会创建一个新的Activity实例。
这里在AndroidManifest.xml文件中修改FirstActivity的启动模式:
-
- android:name=".FirstActivity"
- android:launchMode="singleTask"
- android:exported="true"
- android:label="This is FirstActivity">
-
-
"android.intent.action.MAIN" /> -
-
"android.intent.category.LAUNCHER" /> -
-
然后在FirstActivity中添加onRestart方法:
- override fun onRestart() {
- super.onRestart()
- Log.d("FirstActivity", "onRestart")
- }
然后在SecondActivity中添加onDestroy方法:
- override fun onDestroy() {
- super.onDestroy()
- Log.d("SecondActivity", "onDestroy")
- }
重新运行程序,多次点击按钮的打印信息为:
- 2022-09-12 14:56:24.371 13442-13442/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
- 2022-09-12 14:56:35.771 13442-13442/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@7db5197
- 2022-09-12 14:56:38.287 13442-13442/com.example.activitytest D/FirstActivity: onRestart
- 2022-09-12 14:56:38.916 13442-13442/com.example.activitytest D/SecondActivity: onDestroy
- 2022-09-12 14:56:46.632 13442-13442/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@f3407f3
- 2022-09-12 14:56:48.017 13442-13442/com.example.activitytest D/FirstActivity: onRestart
- 2022-09-12 14:56:48.656 13442-13442/com.example.activitytest D/SecondActivity: onDestroy
- 2022-09-12 14:56:49.069 13442-13442/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@a6704e9
- 2022-09-12 14:56:50.507 13442-13442/com.example.activitytest D/FirstActivity: onRestart
- 2022-09-12 14:56:51.198 13442-13442/com.example.activitytest D/SecondActivity: onDestroy
可以看出,在SecondActivity中启动FirstActivity时,会发现返回栈中存在FirstActivity的实例,并且是在SecondActivity的下面,于是SecondActivity会从返回栈中出栈,而FirstActivity重新成为了栈顶Activity,因此FirstActivity的onRestart方法和SecondActivity的onDestroy方法会得到执行,此时返回栈中只存在一个FirstActivity的实例,一下back键即可退出程序。
singleInstance
singleInstance会启动一个新的返回栈管理该Activity。考虑如下场景:假定程序中有一个Activity是允许其它程序调用的,如果想要实现其它程序和该程序共享该Activity的实例的话,使用这种启动模式,就会有一个单独的返回栈管理该Acitvity,不管是哪个应用程序访问该Acitivity,都共用同一个返回栈,也就解决了共享Activity的问题。而如果采用其它启动模式,则会因为每个应用程序都有自己的返回栈,同一个Activity在不同的返回栈中入栈会创建新的实例。
这里在AndroidManifest.xml文件中修改SecondActivity的启动模式:
- <activity
- android:name=".SecondActivity"
- android:launchMode="singleInstance"
- android:exported="true">
- <intent-filter>
- <action android:name="com.example.activityTest.ACTION_START" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="com.example.activityTest.MY_CATEGORY" />
- intent-filter>
- activity>
然后修改FirstActivity的onCreate方法:
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("FirstActivity", "Task id is $taskId")
- setContentView(R.layout.first_layout)
- button1.setOnClickListener {
- val intent = Intent(this, SecondActivity::class.java)
- startActivity(intent)
- }
- }
上面代码打印了当前返回栈的id。然后修改SecondActivity的onCreate方法:
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("SecondActivity", "Task id is $taskId")
- setContentView(R.layout.second_layout)
- button2.setOnClickListener {
- val intent = Intent(this, ThirdActivity::class.java)
- startActivity(intent)
- }
- }
上面代码同样打印了当前返回栈的id。然后修改ThirdActivity的onCreate方法:
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("ThirdActivity", "Task id is $taskId")
- setContentView(R.layout.third_layout)
- }
上面代码仍然是打印当前返回栈的id。
重新运行程序,分别点击几个按钮后的打印信息为:
- 2022-09-12 15:25:09.637 13879-13879/com.example.activitytest D/FirstActivity: Task id is 62
- 2022-09-12 15:25:15.536 13879-13879/com.example.activitytest D/SecondActivity: Task id is 63
- 2022-09-12 15:25:18.957 13879-13879/com.example.activitytest D/ThirdActivity: Task id is 62
可以看出,SecondActivity的taskId不同于其它两者,这说明SecondActivity的确是存放在一个单独的返回栈中的,而且这个栈中只有SecondActivity这一个Activity。
然后back返回,会发现ThirdActivity直接回到了FirstActivity,再back返回,则会从FirstActivity回到SecondActivity,再back才会退出程序。这是因为FirstActivity和ThirdActivity在同一返回栈,因此才会从ThirdActivity直接回到了FirstActivity,而FirstActivity出栈后,该返回栈为空,会显示另一个返回栈的栈顶Activity,即SecondActivity,SecondActivity出栈后,所有返回栈为空,此时才退出程序。
Activity实践
判断当前是在哪一个Activity
项目开发中,如果需要在某个界面上修改一些非常简单的东西时,找到该界面对应的Activity是必须的。
这里还在ActivityTest项目上修改,新建一个BaseActivity类,创建一个Kotlin File/Class文件。因为不需要BaseActivity在AndroidManifest.xml上注册,所有创建的只是一个Kotlin类。
然后让BaseActivity继承AppCompatActivity,并重写onCreate方法:
- package com.example.activitytest
-
- import android.os.Bundle
- import android.os.PersistableBundle
- import android.util.Log
- import androidx.appcompat.app.AppCompatActivity
-
- open class BaseActivity:AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("BaseActivity", javaClass.simpleName)
- }
- }
上面代码用于打印当前实例的类名。Kotlin中的javaClass表示获取当前实例的Class对象,而BaseActivity::class.java表示获取BaseActivity类的Class对象。在上述代码中,先获取了当前实例的Class对象,然后调用simpleName获取当前实例的类名。
之后让BaseActivity称为ActivityTest项目中所有Activity的父类,而open关键字说明该类是可以继承的,然后修改FirstActivity/SecondActivity/ThirdActivity的继承结构。运行程序后的打印信息为:
- 2022-09-12 15:45:53.977 14216-14216/com.example.activitytest D/BaseActivity: FirstActivity
- 2022-09-12 15:46:07.857 14216-14216/com.example.activitytest D/BaseActivity: SecondActivity
- 2022-09-12 15:46:09.590 14216-14216/com.example.activitytest D/BaseActivity: ThirdActivity
即每当进入某个Activity界面,就会打印该Activity的类名,也就获知了当前界面对应的Activity。
随时退出程序
在上面的例子中,如果手机界面处于ThirdActivity,此时需要连续3次back才会退出程序。而HOME键也只是将程序挂起,并没有退出程序。而如果需要随时都能够退出程序的话,就需要使用专门的集合对所有Activity进行管理。
新建一个单例类ActivityCollector作为Activity的集合:
- package com.example.activitytest
-
- import android.app.Activity
-
- object ActivityCollector {
-
- private val activities = ArrayList
() -
- fun addActivity(activity: Activity) {
- activities.add(activity)
- }
-
- fun removeActivity(activity: Activity) {
- activities.remove(activity)
- }
-
- fun finishAll() {
- for (activity in activities) {
- if (!activity.isFinishing) {
- activity.finish()
- }
- }
- activities.clear()
- }
- }
这里使用单例类,是因为全局只需要一个Activity集合。在集合中,通过ArrayList来暂存Activity,然后提供了几个方法对该集合进行修改,最后提供finishAll方法用于将该集合中存储的所有Activiy全部销毁。只是在销毁之前,需要先调用activity.isFinishing来判断Activity是否正在销毁(back键),如果没有正在被销毁,则调用finish方法销毁。
然后修改BaseActivity中的代码:
- package com.example.activitytest
-
- import android.os.Bundle
- import android.os.PersistableBundle
- import android.util.Log
- import androidx.appcompat.app.AppCompatActivity
-
- open class BaseActivity:AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("BaseActivity", javaClass.simpleName)
- ActivityCollector.addActivity(this)
- }
-
- override fun onDestroy() {
- super.onDestroy()
- ActivityCollector.removeActivity(this)
- }
- }
在BaseActivity的onCreate方法中调用了ActivityCollector的addActivity方法将当前正在创建的Activity添加到集合中,然后重写onDestroy方法,并调用ActivityCollector的removeActivity方法从列表中移除要销毁的Activity。
而如果要调用ActivityCollector的finishAll方法,比如通过ThirdActivity的按钮调用,就可以修改代码:
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("ThirdActivity", "Task id is $taskId")
- setContentView(R.layout.third_layout)
- button3.setOnClickListener {
- ActivityCollector.finishAll()
- android.os.Process.killProcess(android.os.Process.myPid())
- }
- }
此时就可以点击ThirdActivity中的按钮退出程序,同时在之后杀死了当前进程。
启动Activity的最佳写法
之前提到在Activity之间可以传递数据,但是却要事先知道数据的类型和变量名,这就需要阅读代码。
而下面的代码可以解决这种麻烦(SecondActivity):
- companion object {
- fun actionStart(context: Context, data1: String, data2: String) {
- val intent = Intent(context, SecondActivity::class.java)
- intent.putExtra("param1", data1)
- intent.putExtra("param2", data2)
- context.startActivity(intent)
- }
- }
上面使用companion object语法结构,并在该结构中定义了actionStart方法,Kotlin规定,在该语法结构中定义的方法都可以使用类似于Java静态方法的形式调用。
而actionStart方法中完成了Intent对象的构建,并且所有的SecondActivity中需要的数据都是从actionStart方法的参数传递过来的,然后将之存储在了Intent中,最后调用startActivity方法启动SecondActivity。
这样写的好处在于通过actionStart方法将SecondActivity启动需要的参数数据完全暴露了出来,而不用像之前一样需要查看代码。
因此,在其它Activity中,只需要一行代码便可以传递参数到SecondActivity,并启动SecondActivity:
- button1.setOnClickListener {
- SecondActivity.actionStart(this, "data1", "data2")
- }
这样就显得省事多了。