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

Activity的启动模式

Activity的启动模式有四种:

  • standard
  • singleTop
  • singleTask
  • singleInstance

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

standard

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

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

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

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

        <activity
            android:name=".FirstActivity"
            android:launchMode="singleTask"
            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>

然后在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<Activity>()
    
    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")
        }

这样就显得省事多了。

猜你喜欢

转载自blog.csdn.net/SAKURASANN/article/details/126817603