前言

今天,小编来聊一聊我们的“老朋友”Activity,我相信作为一个Android开发爱好者,Activity是大家最熟悉的和打交道最多的。今天,我们就来细解Activity的生命周期与启动模式,最后再来介绍一下Activity的最佳实践方式。

Activity的生命周期

返回栈

Android由任务(Task)来管理Activity,一个任务就是一组存放在栈里的Activity的集合,这个栈也被称为返回栈(back stack)。栈是一种后进先出的数据结构,在默认情况下,每当我们启动了一个新的Activity,他就会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finish()方法去销毁一个Activity时,处于栈顶的Activity就会出栈,前一个入栈的Activity就会重新处于栈顶的位置。系统总是会显示处于栈顶的Activity给用户。

Activity的状态

每一个Activity在其生命周期中最多会有4种状态。

  1. 运行状态

    当一个Activity位于返回栈的顶部时,Activity就处于运行状态。系统最不愿意回收的就是处于运行状态的Activity,因为这回带来非常差的用户体验。

  2. 暂停状态

    当一个Activity不处于栈顶位置,但仍然可见时,Activity就处于暂停状态。你可能会觉得,既然Activity已经不在栈顶了,怎么会可见呢?这是因为并不是每一个Activity都会占满整个屏幕,比如对话框形式的Activity只会占用屏幕中间的部分区域,处于暂停状态的Activity仍然是完全存活着的,系统不愿意回收这种Activity(因为它还是可见的,回收可见的东西都会在用户体验方面又不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种Activity。

  3. 停止状态

    当一个Activity不再处于栈顶位置,并且完全不可见的时候,就进入停止状态。系统仍然会为这种Activity保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于暂停状态的Activity有可能会被系统回收。

  4. 销毁状态

    一个Activity从返回栈中移除后就变成了销毁状态。系统最倾向于回收处于这种状态的Activity,以保证手机的内存充足。

Activity的生存期

Activity的7个回调方法:

  • onCreate():它会在Activity第一次被创建的时候调用。你应该在这个方法中完成Activity的初始化操作,比如加载布局、绑定事件等。
  • onStart():这个方法在Activity由不可见变为可见的时候调用。onStart()执行完成之后就可以与用户交互。
  • onResume():这个方法在Activity准备好和用户进行交互的时候调用。此时的Activity一定位于返回栈的栈顶,并且处于运行状态。
  • onPause():这个方法在系统准备去启动或者恢复另一个Activity的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶的Activity的使用。
  • onStop():这个方法在Activity完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新Activity是一个对话框式的Activity,那么onPause()会得到执行,而onStop()方法并不会执行。
  • onDestroy():这个方法在Activity被销毁之前调用,之后Activity的状态将变为销毁状态。
  • onRestart():这个方法在Activity由停止状态变为运行状态之前调用,也就是Activity被重新启动了。

以上7中方法,除了onRestart()方法,其他都是两两相对的,从而又可以将Activity分为以下3种生存期。

  • 完整生存期。Activity在onCreate()方法和onDestroy()方法之间所经历的就是完整生存期。一般情况下,一个Activity会在onCreate()方法中完成各种初始化操作,而在onDestroy()方法中完成释放内存的操作。
  • 可见生存期。Activity在onStart()方法和onStop()方法之间所经历的就是可见生存期。在可见生存期内,Activity对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法合理地管理那些对用户可见的资源。比如在onStart()方法中对资源进行加载,而在onStop()方法中对资源进行释放,从而保证处于停止状态的Activity不会占用过多内存。
  • 前台生存期。Activity在onResume()方法和onPause()方法之间所经历的就是前台生存期。在前台生存期内,Activity总是处于运行状态,此时的Activity是可以和用户进行交互的,我们平时看到和接触最多的就是这个状态下的Activity。

官方提供的Activity生命周期图:

image-20221001112220780

Activity启动模式

启动模式一共有4种,分别是standard、singleTop、singleTask和singleInstance,可以在AndroidManifest.xml中通过给<activity>标签指定android:launchMode属性来选择启动模式。

这里给大家推荐一个视频,真的很完美的诠释了启动模式,是由非常有名的GDE专家朱凯 凯哥的食谱……不,视频:

https://www.bilibili.com/video/BV1CA41177Se/?spm_id_from=333.999.0.0

因为,小编觉得视频已经讲得非常好了,所以,这里只对启动模式做一个简单的介绍与总结。

standard

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

singleTop

可能在很多情况下,你会觉得standard模式不太合理。Activity明明已经在栈顶了,为什么再次启动的时候还要创建一个新的Activity实例呢?为了解决这种情况,singleTop启动模式就出现了。当Activity的启动模式指定为singleTop,在启动Activity时如果发现返回栈的栈顶已经是该Activity,则认为可以直接使用它,不会再创建新的Activity实例。可以认为singleTop是对standard一种优化。

singleTask

使用singleTask模式可以很好地解决重复创建栈顶Activity的问题,但是如果该Activity没有处于栈顶的位置,还是会创建新的Activity实例。为了解决这一问题,singleTask出现了。当Activity的启动模式指定为singleTask,每次启动该Activity时,系统首次会返回栈中检查是否存在该Activity的实例,如果发现已经存在则直接使用该实例,并把这个Activity之上的所有其他Activity统统出栈,如果没有发现就会创建一个新的Activity实例。

当然,对于不同应用之间的跳转与启动模式的灵活应用,凯哥的视屏有更好的介绍。

singleInstance

singleInstance模式应该算是4种启动模式中最特殊也最复杂的一个了。不同于以上3种启动模式,指定为singleInstance模式的Activity会启动一个新的Activity来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈,所以由多少个返回栈和taskAffinity的数量有关)。那么这么做有什么意义呢?想象以下场景,假设我们的程序中有一个Activity是允许其他程序调用的,如果想实现其他程序和我们的程序可以共享这个Activity的实例,应该如何实现呢?使用前面3中启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个Activity在不同的返回栈中入栈时必然创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这个情况下,会有一个单独的返回栈来管理这个Activity,不管是哪个应用程序来访问这个Activity,都共用同一个返回栈,也就解决了共享Activity实例的问题。

Activity的最佳实践

这里,小编介绍一个在项目中实用的Activity使用技巧。

这个技巧会教会你如何根据程序当前的界面就能判断出这是哪一个Activity。可能你会觉得挺纳闷的,我自己些的代码怎么会不知道这是哪个Activity呢?然而现实情况是,在你进入一个公司之后,更有可能的是接受一份别人的代码,因为你刚进公司就正好有一个新项目七佛那个的概率不高(很不幸,我就成了概率不高那部分)。阅读别人的代码时有一个头疼的问题,就是当你需要在某个界面上修改一些非常简单的东西时,却半天找不到这个界面对应的Activity是哪一个。

  • 创建一个基类Activity——BaseActivity,作为项目中所有Activity的父类,值得注意的是,这是一个普通类,不需要注册.

    open class BaseActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    ActivityCollector.addActivity(this)
    }

    override fun onDestroy() {
    super.onDestroy()
    ActivityCollector.removeActivity(this)
    }
    }
  • 如果你的项目中又多个Activity,跳转之后再推出会非常不方便,按Home键只是把程序挂起,并没有退出程序。那么有没有一种方法可以实现注销或推出的功能呢?就像我们的QQ在同类设备上登录被强制退出那样。(当然,我们今天没有完全实现那种功能,如果要实现还需要用到广播,下期小编就来回味广播)

    其实解决思路很简单,只需要用一个专门的集合对所有的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,然后提供了一个addActivity()方法,用于向ArrayList中添加Activity;提供了一个removeActivity()方法,用于从ArrayList中移除Activity;最后提供了一个finishAll()方法,用于将ArrayList中存储的Activity全部销毁。注意在销毁Activity之前,我们需要先调用activity.isFinishing来判断Activity是否正在销毁中,因为Activity还可能通过按下Back键等方式销毁,如果该Activity没有正在销毁中,我们再去调用它的finish()方法来销毁它。

启动Activity的最佳写法

有这样一种情况,比如某个Activity(暂且叫做SecondActivity)不是你开发的,但是你负责开发的部分需要启动SecondActivity,而你不清楚启动SecondActivity需要传递那些数据。这时无非就有两种方法:一个是你自己去阅读SecondActivity中的代码,另一个是去访问负责编写SecondActivtiy的同事。你会不会觉得很麻烦呢?其实只需要换一种写法,就可以轻松解决问题。

class SecondActivity:BaseActivity() {
...
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)
}
}
}

在使用时:

SecondActivity.actionStart(this,"data1","data2")

这样,即使不用阅读SecondActivity的代码,不去询问负责编写SecondActivity的同事,你也可以非常清晰地直到启动SecondActivity需要传递哪些数据。另外,这样写还简化了启动Activity的代码,只需要一行代码就可以启动SecondActivity。

总结

本章主要是对Activity的生命周期和启动模式进行回顾,毕竟有些细节很久不接触就容易忘记。

最后还介绍了Activity实践中的技巧,养成良好的编程习惯,会使我们的效率事半功倍。

参考:《第一行代码》