• Android开发基础——Activity启动模式


    Activity的启动模式

    Activity的启动模式有四种:

    • standard
    • singleTop
    • singleTask
    • singleInstance

    启动模式可通过给activity标签指定android:launchMode属性来选择启动模式。

    standard

    standard是Activity默认的启动模式,在不进行显式指定的情况下,所有Activity都会自动使用这种启动模式。在standard模式下,每当启动一个新的Activity,其就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的Activity,系统不会在乎该Activity是否已经在返回栈中存在,每次启动都会创建一个该Activity的新实例。

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. Log.d("FirstActivity", this.toString())
    4. setContentView(R.layout.first_layout)
    5. button1.setOnClickListener {
    6. val intent = Intent(this, FirstActivity::class.java)
    7. startActivity(intent)
    8. }
    9. }

    上边代码重写了onCreate方法,并在FirstActivity的基础上启动FirstActivity。

    运行程序后,连续点击按钮会出现下列打印信息:

    1. 2022-09-12 14:28:46.602 12248-12248/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
    2. 2022-09-12 14:29:37.635 12392-12392/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
    3. 2022-09-12 14:29:41.626 12392-12392/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@b73636d
    4. 2022-09-12 14:29:43.513 12392-12392/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@3ad27ac
    5. 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

    singleTop是在启动Activity时,如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用,而不会创建新的Activity实例。

    这里在AndroidManifest.xml文件中修改FirstActivity的启动模式:

    1. <activity
    2. android:name=".FirstActivity"
    3. android:launchMode="singleTop"
    4. android:exported="true"
    5. android:label="This is FirstActivity">
    6. <intent-filter>
    7. <action android:name="android.intent.action.MAIN" />
    8. <category android:name="android.intent.category.LAUNCHER" />
    9. intent-filter>
    10. 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方法:

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. Log.d("FirstActivity", this.toString())
    4. setContentView(R.layout.first_layout)
    5. button1.setOnClickListener {
    6. val intent = Intent(this, SecondActivity::class.java)
    7. startActivity(intent)
    8. }
    9. }
    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. Log.d("SecondActivity", this.toString())
    4. setContentView(R.layout.second_layout)
    5. button2.setOnClickListener {
    6. val intent = Intent(this, FirstActivity::class.java)
    7. startActivity(intent)
    8. }
    9. }

    运行程序,点击按钮后的打印信息:

    1. 2022-09-12 14:43:35.097 13049-13049/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
    2. 2022-09-12 14:43:45.900 13049-13049/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@7db5197
    3. 2022-09-12 14:43:47.905 13049-13049/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@dd2bdb9
    4. 2022-09-12 14:43:49.374 13049-13049/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@de0371a
    5. 2022-09-12 14:43:50.622 13049-13049/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@8e59af6

    此时FirstActivity和SecondActivity互相层叠,使FirstActivity不在栈顶,以致多次创建。

    singleTask

    singleTask是每次启动Activity时,系统首先会在返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把在这个Activity之上的所有其它Activity统统出栈,如果没有发现则会创建一个新的Activity实例。

    这里在AndroidManifest.xml文件中修改FirstActivity的启动模式:

    1. android:name=".FirstActivity"
    2. android:launchMode="singleTask"
    3. android:exported="true"
    4. android:label="This is FirstActivity">
    5. "android.intent.action.MAIN" />
    6. "android.intent.category.LAUNCHER" />

    然后在FirstActivity中添加onRestart方法:

    1. override fun onRestart() {
    2. super.onRestart()
    3. Log.d("FirstActivity", "onRestart")
    4. }

    然后在SecondActivity中添加onDestroy方法:

    1. override fun onDestroy() {
    2. super.onDestroy()
    3. Log.d("SecondActivity", "onDestroy")
    4. }

     重新运行程序,多次点击按钮的打印信息为:

    1. 2022-09-12 14:56:24.371 13442-13442/com.example.activitytest D/FirstActivity: com.example.activitytest.FirstActivity@bd88491
    2. 2022-09-12 14:56:35.771 13442-13442/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@7db5197
    3. 2022-09-12 14:56:38.287 13442-13442/com.example.activitytest D/FirstActivity: onRestart
    4. 2022-09-12 14:56:38.916 13442-13442/com.example.activitytest D/SecondActivity: onDestroy
    5. 2022-09-12 14:56:46.632 13442-13442/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@f3407f3
    6. 2022-09-12 14:56:48.017 13442-13442/com.example.activitytest D/FirstActivity: onRestart
    7. 2022-09-12 14:56:48.656 13442-13442/com.example.activitytest D/SecondActivity: onDestroy
    8. 2022-09-12 14:56:49.069 13442-13442/com.example.activitytest D/SecondActivity: com.example.activitytest.SecondActivity@a6704e9
    9. 2022-09-12 14:56:50.507 13442-13442/com.example.activitytest D/FirstActivity: onRestart
    10. 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的启动模式:

    1. <activity
    2. android:name=".SecondActivity"
    3. android:launchMode="singleInstance"
    4. android:exported="true">
    5. <intent-filter>
    6. <action android:name="com.example.activityTest.ACTION_START" />
    7. <category android:name="android.intent.category.DEFAULT" />
    8. <category android:name="com.example.activityTest.MY_CATEGORY" />
    9. intent-filter>
    10. activity>

    然后修改FirstActivity的onCreate方法:

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. Log.d("FirstActivity", "Task id is $taskId")
    4. setContentView(R.layout.first_layout)
    5. button1.setOnClickListener {
    6. val intent = Intent(this, SecondActivity::class.java)
    7. startActivity(intent)
    8. }
    9. }

    上面代码打印了当前返回栈的id。然后修改SecondActivity的onCreate方法:

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. Log.d("SecondActivity", "Task id is $taskId")
    4. setContentView(R.layout.second_layout)
    5. button2.setOnClickListener {
    6. val intent = Intent(this, ThirdActivity::class.java)
    7. startActivity(intent)
    8. }
    9. }

    上面代码同样打印了当前返回栈的id。然后修改ThirdActivity的onCreate方法:

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. Log.d("ThirdActivity", "Task id is $taskId")
    4. setContentView(R.layout.third_layout)
    5. }

    上面代码仍然是打印当前返回栈的id。

    重新运行程序,分别点击几个按钮后的打印信息为:

    1. 2022-09-12 15:25:09.637 13879-13879/com.example.activitytest D/FirstActivity: Task id is 62
    2. 2022-09-12 15:25:15.536 13879-13879/com.example.activitytest D/SecondActivity: Task id is 63
    3. 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方法:

    1. package com.example.activitytest
    2. import android.os.Bundle
    3. import android.os.PersistableBundle
    4. import android.util.Log
    5. import androidx.appcompat.app.AppCompatActivity
    6. open class BaseActivity:AppCompatActivity() {
    7. override fun onCreate(savedInstanceState: Bundle?) {
    8. super.onCreate(savedInstanceState)
    9. Log.d("BaseActivity", javaClass.simpleName)
    10. }
    11. }

    上面代码用于打印当前实例的类名。Kotlin中的javaClass表示获取当前实例的Class对象,而BaseActivity::class.java表示获取BaseActivity类的Class对象。在上述代码中,先获取了当前实例的Class对象,然后调用simpleName获取当前实例的类名。

    之后让BaseActivity称为ActivityTest项目中所有Activity的父类,而open关键字说明该类是可以继承的,然后修改FirstActivity/SecondActivity/ThirdActivity的继承结构。运行程序后的打印信息为:

    1. 2022-09-12 15:45:53.977 14216-14216/com.example.activitytest D/BaseActivity: FirstActivity
    2. 2022-09-12 15:46:07.857 14216-14216/com.example.activitytest D/BaseActivity: SecondActivity
    3. 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的集合:

    1. package com.example.activitytest
    2. import android.app.Activity
    3. object ActivityCollector {
    4. private val activities = ArrayList()
    5. fun addActivity(activity: Activity) {
    6. activities.add(activity)
    7. }
    8. fun removeActivity(activity: Activity) {
    9. activities.remove(activity)
    10. }
    11. fun finishAll() {
    12. for (activity in activities) {
    13. if (!activity.isFinishing) {
    14. activity.finish()
    15. }
    16. }
    17. activities.clear()
    18. }
    19. }

    这里使用单例类,是因为全局只需要一个Activity集合。在集合中,通过ArrayList来暂存Activity,然后提供了几个方法对该集合进行修改,最后提供finishAll方法用于将该集合中存储的所有Activiy全部销毁。只是在销毁之前,需要先调用activity.isFinishing来判断Activity是否正在销毁(back键),如果没有正在被销毁,则调用finish方法销毁。

    然后修改BaseActivity中的代码:

    1. package com.example.activitytest
    2. import android.os.Bundle
    3. import android.os.PersistableBundle
    4. import android.util.Log
    5. import androidx.appcompat.app.AppCompatActivity
    6. open class BaseActivity:AppCompatActivity() {
    7. override fun onCreate(savedInstanceState: Bundle?) {
    8. super.onCreate(savedInstanceState)
    9. Log.d("BaseActivity", javaClass.simpleName)
    10. ActivityCollector.addActivity(this)
    11. }
    12. override fun onDestroy() {
    13. super.onDestroy()
    14. ActivityCollector.removeActivity(this)
    15. }
    16. }

    在BaseActivity的onCreate方法中调用了ActivityCollector的addActivity方法将当前正在创建的Activity添加到集合中,然后重写onDestroy方法,并调用ActivityCollector的removeActivity方法从列表中移除要销毁的Activity。

    而如果要调用ActivityCollector的finishAll方法,比如通过ThirdActivity的按钮调用,就可以修改代码:

    1. override fun onCreate(savedInstanceState: Bundle?) {
    2. super.onCreate(savedInstanceState)
    3. Log.d("ThirdActivity", "Task id is $taskId")
    4. setContentView(R.layout.third_layout)
    5. button3.setOnClickListener {
    6. ActivityCollector.finishAll()
    7. android.os.Process.killProcess(android.os.Process.myPid())
    8. }
    9. }

    此时就可以点击ThirdActivity中的按钮退出程序,同时在之后杀死了当前进程。

    启动Activity的最佳写法

    之前提到在Activity之间可以传递数据,但是却要事先知道数据的类型和变量名,这就需要阅读代码。

    而下面的代码可以解决这种麻烦(SecondActivity):

    1. companion object {
    2. fun actionStart(context: Context, data1: String, data2: String) {
    3. val intent = Intent(context, SecondActivity::class.java)
    4. intent.putExtra("param1", data1)
    5. intent.putExtra("param2", data2)
    6. context.startActivity(intent)
    7. }
    8. }

    上面使用companion object语法结构,并在该结构中定义了actionStart方法,Kotlin规定,在该语法结构中定义的方法都可以使用类似于Java静态方法的形式调用。

    而actionStart方法中完成了Intent对象的构建,并且所有的SecondActivity中需要的数据都是从actionStart方法的参数传递过来的,然后将之存储在了Intent中,最后调用startActivity方法启动SecondActivity。

    这样写的好处在于通过actionStart方法将SecondActivity启动需要的参数数据完全暴露了出来,而不用像之前一样需要查看代码。

    因此,在其它Activity中,只需要一行代码便可以传递参数到SecondActivity,并启动SecondActivity:

    1. button1.setOnClickListener {
    2. SecondActivity.actionStart(this, "data1", "data2")
    3. }

    这样就显得省事多了。

  • 相关阅读:
    线程的状态
    2024年经典【自动化面试题】附答案
    【漏洞复现】时空智友企业流程化管控系统 session泄露
    JVM优化案例实战-手动模拟Young GC
    关于 JVM 的内存模型
    二、T100固定资产之固定资产数据建立篇
    RLChina2022-强化学习暑期课程-博弈搜索算法
    在mac(m1)上启动mybatis-generator-gui报错及修复
    Chrome DOM断点之实现源码追溯
    使用 zk-SNARKs 的可编程零知识证明:第 3 部分
  • 原文地址:https://blog.csdn.net/SAKURASANN/article/details/126817603