Android开发基础——Activity生命周期

Activity的生命周期

返回栈

Android中的Acivity是可以层叠的,每启动一个新的Activity,就会覆盖在原Activity之上,然后点击back键就会销毁最上面的Activity,下面的一个Activity就会重新显示出来。

Android是使用任务(Task)来管理Activity的,一个任务就是一组存放在栈中的Activity的集合,该栈也称为返回栈(back stack)。栈是一种后进先出的数据结构,默认情况下,每当启动一个新的Activity,其就会在返回栈中入栈,并处于栈顶的位置,而每当按下back键或调用finish方法销毁一个Activity时,处于栈顶的Activity就会出栈,前一个入栈的Activity就会重新处于栈顶的位置。

系统总是会显示处于栈顶的Activity给用户。

Activity状态

每个Activity在其生命周期中最多可能会有四种状态。

  • 运行状态:当一个Activity位于返回栈的栈顶时,Activity就处于运行状态。系统最不愿意回收的就是处于运行状态的Activity,因为会导致较差的用户体验。
  • 暂停状态:当一个Activity不再处于栈顶位置,但仍然可见时,Activity就进入了暂停状态。比如对话框形式的Activity处于运行状态时,其不会占用全部的屏幕空间,而处于暂停状态的Activity仍然是完全存活的,系统也不愿回收此类Activity,因为会导致较差的用户体验,只有在内存极低的情况下,系统才会考虑回收该类Activity。
  • 停止状态:当一个Activity不再处于栈顶位置,并且完全不可见时,就进入停止状态。系统仍然会为此类Activity保存相应的状态和成员变量,但是这并不是完全可靠的,当其它地方需要内存时,处于停止状态的Activity有可能会被系统回收。
  • 销毁状态:一个Activity从返回栈中移除后就变成了销毁状态。系统最倾向于回收处于此状态的Activity,以保证手机的内存充足。

Activity的生存期

Activity类定义了7个回调方法,覆盖了Activity生命周期的每一个环节:

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

上面几个方法中,除了onRestart方法,其它都是成对的,从而又可以将Activity分为以下3中生存期:

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

体验Activity的生命周期

这里新建一个项目,命名ActivityLifeCycleTest,并允许自动创建Activity和布局,然后再创建两个子Activity,分别命名为NormalActivity和DialogActivity。修改normal_layout为:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a normal activity"
        />

</LinearLayout>

修改dialog_layout为:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a dialog activity"
        />

</LinearLayout>

两个内容差不多,都是使用TextView显示一行文字。

修改AndroidManifest.xml文件将DialogActivity配置为对话框式:

        <activity
            android:name=".DialogActivity"
            android:exported="true" 
            android:theme="@style/Theme.AppCompat.Dialog" />
        <activity
            android:name=".NormalActivity"
            android:exported="true" />

上面使用android:theme属性指定了该Activity的主题。

修改activity_main布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/startNormalActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start NormalActivity"
        tools:ignore="DuplicateIds" />
    <Button
        android:id="@+id/startDialogActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start DialogActivity"/>
        

</LinearLayout>

上面添加了两个按钮,一个用于启动NormalActivity,一个用于启动DialogActivity。

最后修改MainActivity中的代码:

package com.example.activitylifecycletest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private val tag = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(tag, "onCreate")
        setContentView(R.layout.activity_main)
        startNormalActivity.setOnClickListener {
            val intent = Intent(this, NormalActivity::class.java)
            startActivity(intent)
        }
        startDialogActivity.setOnClickListener {
            val intent = Intent(this, DialogActivity::class.java)
            startActivity(intent)
        }
    }

    override fun onStart() {
        super.onStart()
        Log.d(tag, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(tag, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(tag, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(tag, "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(tag, "onDestroy")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(tag, "onRestart")
    }
}

在onCreate方法中,分别为两个按钮注册了点击事件,点击第一个按钮会启动NormalActivity,点击第二个按钮会启动DialogActivity,然后覆写之前提到的几个方法。

运行程序后的结果为:

 对应出现的打印信息为:

2022-09-12 13:50:36.745 11707-11707/com.example.activitylifecycletest D/MainActivity: onCreate
2022-09-12 13:50:37.291 11707-11707/com.example.activitylifecycletest D/MainActivity: onStart
2022-09-12 13:50:37.295 11707-11707/com.example.activitylifecycletest D/MainActivity: onResume

 可以看出,当MainActivity第一次被创建时会一次执行onCreate,onStart和onResume方法,然后点击第一个按钮,启动NormalActivity:

 此时打印信息为:

2022-09-12 13:52:34.493 11707-11707/com.example.activitylifecycletest D/MainActivity: onPause
2022-09-12 13:52:35.455 11707-11707/com.example.activitylifecycletest D/MainActivity: onStop

此时NormalActivity已经把MainActivity完全遮挡住,因此会执行onPause和onStop方法,然后按下Back键返回MainActivity,打印信息为:

2022-09-12 13:54:37.474 11707-11707/com.example.activitylifecycletest D/MainActivity: onRestart
2022-09-12 13:54:37.486 11707-11707/com.example.activitylifecycletest D/MainActivity: onStart
2022-09-12 13:54:37.488 11707-11707/com.example.activitylifecycletest D/MainActivity: onResume

由于之前MainActivity已经进入了停止状态,所以会执行onRestart犯法,然后会执行onStart和onResume方法。

然后点击第二个按钮,启动DialogActivity:

 此时打印信息为:

2022-09-12 13:56:23.189 11707-11707/com.example.activitylifecycletest D/MainActivity: onPause

可以看到,此时只执行了onPause方法,这是因为DialogActivity并没有完全遮挡主MainActivity,此时MainActivity进入了暂停状态,并没有进入停止状态。

按下back后的打印信息为:

2022-09-12 13:59:24.829 11707-11707/com.example.activitylifecycletest D/MainActivity: onResume

此时也只有onResume方法执行,最后按back推出MainActivity后的打印信息为:

2022-09-12 14:00:22.132 11707-11707/com.example.activitylifecycletest D/MainActivity: onPause
2022-09-12 14:00:23.358 11707-11707/com.example.activitylifecycletest D/MainActivity: onStop
2022-09-12 14:00:23.377 11707-11707/com.example.activitylifecycletest D/MainActivity: onDestroy

依此会执行onPause,onStop和onDestroy方法,最终销毁MainActivity。

Activity被回收了怎么办

之前提到,当一个Activity进入了停止状态,是有可能被系统回收的。而假设存在这样的场景,应用中存在一个Activity A,用户在Activity A的基础之上启动了Activity B,A就进入了停止状态,而由于系统内存的问题,A被回收,此时用户按下back返回A,此时便会调用onCreate方法而不是onRestart方法,重新创建Activity A进行显示。

上边这种情况看起来很合理,但是如果在Activity A中存在用户输入的信息或处理后的数据,那么此时回收Activity A后调用onCreate方法重建便会丢失这些内容,这是很影响用户体验的。

而Activity中提供了一个onSaveInstanceState回调方法,该方法可以保证在Activity被回收前一定会被调用,因此可以使用该方法解决该问题。

onSaveInstanceState方法会携带一个Bundle类型的参数,Bundle提供了一系列方法用于保存数据,比如putString/putInt,每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。

在MainActivity中添加如下代码以保存临时数据:

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        val tempData = "Something you just typed"
        outState.putString("data_key", tempData)
    }

数据保存后,需要在onCreate方法中恢复,该方法有一个Bundle类型的参数,该参数一般为null,但是如果在Activity被系统回收之前,通过onSaveInstanceState方法保存数据,该参数就会带有之前保存的全部数据,只是需要使用对应的取值方法获取数据即可。

        if (savedInstanceState != null) {
            val tempData = savedInstanceState.getString("data_key")
            Log.d(tag, "tempData is $tempData")
        }

拿到数据之后,进行对应的处理即可。

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在父类的构造方法中调用可能被子类覆写的方法可能会引发一些意想不到的问题,因为在子类的构造方法中,它的成员变量可能还未被初始化完成,此时调用被覆写的方法可能会导致意想不到的结果,例如空指针异常等。 具体来说,当父类的构造方法中调用一个被子类覆写的方法时,如果子类中的该方法引用了子类中的成员变量,而这些成员变量在父类的构造方法中还未被初始化,那么就可能会导致子类中的该方法出现异常。 例如,以下代码演示了在父类的构造方法中调用被子类覆写的方法可能导致的问题: ```java public class Animal { public Animal() { move(); // 调用被子类覆写的方法 } public void move() { System.out.println("Animal is moving"); } } public class Dog extends Animal { private String name; public Dog(String name) { super(); this.name = name; } @Override public void move() { System.out.println(name + " is running"); } } public class Test { public static void main(String[] args) { Dog dog = new Dog("Tom"); } } ``` 在上面的代码中,父类Animal的构造方法中调用了被子类Dog覆写的方法move(),而子类Dog中的move()方法引用了成员变量name,而这个成员变量在父类Animal的构造方法中还未被初始化。因此,在运行上面的代码时,会出现空指针异常。 为了避免这种问题,我们应该尽量避免在父类的构造方法中调用被子类覆写的方法。如果必须要调用,可以将该方法设置为final类型,或者将该方法放在父类的构造方法之后进行调用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值