Activity的生命周期(典型和异常生命周期)

Activity的构成:

    实际上视图会被设置给一个Window类,这个Window中含有一个DecorView,这个DecorView才是整个窗口的顶级视图,Activity下有一个PhoneWindow,这个PhoneWindow是Window的实现类,Window之下包含一个DecorView,DecorView实际上是页面的顶级视图,它从一些系统布局中加载,并且在运行时将开发人员设置给Activity的布局资源添加到系统布局的mContentParent中,此时用户界面就被添加进系统布局中,而系统布局会为我们设置好标题栏区域等。


Activity的声明周期

    Activity 的声明周期分为典型的生命周期和异常生命周期,典型的生命周期是指在有用户参与的情况下,Activity 所经历的生命周期的改变,异常生命周期是指Activity 被系统回收或者由于当前设备的Configuration 发生改变从而导致Activity 被销毁重建,两者的生命周期略有不同

Activity 典型的生命周期:

    正常情况下,Activity 会经历如下生命周期
1、onCreate()
    表示Activity 正在被创建,每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。在这个方法中我们可以完成活动的初始化操作,比如说加载布局、绑定事件等。

2、onRestart()
    表示Activity 正在重新启动。一般情况下,当当前Activity 从不可见变为可见时,onRestart 会被调用。这种情形一般是用户行为所导致的,比如用户按 Home 键切换到桌面或者用户打开了一个新的 Activity,此时当前的Activity 就会暂停,即onPause 和 onStop 被调用,接着用户重新回到了这个Activity,此时onRestart 就会被调用

3、 onStart()
    表示Activity 正在被启动,即将开始,此时Activity 已经可见了,但还没有出现在前台,即还无法与用户进行交互。可以理解为Activity 已经显现出来了,但是我们还看不到

4、onResume()
    表示Activity 已经可见了,并且出现在前台并开始活动,此时可以与用户进行交互。此时的活动一定位于返回栈的栈顶,并且处于运行状态。

5、onPause()
    表示Activity 正在停止,正常情况下onStop 会紧接着被调用。这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉(比如动画),以及保存一些关键数据,但不能太耗时,不然会影响到新的栈顶活动的使用,onPause 必须先执行完,新 Activity 的onResume 才会执行。

6、 onStop()
    表示Activity即将停止,在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行,可以做一些稍微重量级的回收工作,同样不能太耗时。

7、onDestroy()
    表示Activity 即将被销毁,这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态,这是Activity 生命周期中的最后一个回调,在这里我们可以做一些回收工作和最终的资源释放。

    正常情况下Activity 的常用生命周期就只有上面 7 个,如下图所示,详细的展示了Activity 各种生命周期的切换过程

活动四种状态
    每个活动在其生命周期中最多可能会有四种状态。
1. 运行状态
    当一个活动位于返回栈的栈顶时,这时活动就处于运行状态,即可交互的状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。

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

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

4. 销毁状态
    当一个活动从返回栈中移除后就变成了销毁状态。比如用户按了 back 键,此时Activity 就会被销毁。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。


Q1:onStart 和 onResume、onPause 和onStop 从描述上来看差不多,对我们来说有什么实质上的不同?

     A:这两个配对的回调分别表示不同的意义,onStart 和onStop 是从Activity 是否可见的角度来回调的,而onResume 和 onPause 是从Activity 是否位于前台这个角度来回调的,除了这种区别,在实际使用中没有其他明显的区别


Q2:假设当前 Activity 为 A,如果此时用户打开一个新 Activity B,那么B 的onResume 和 A onPause 那个先执行?

     A:  A onPause 先执行,B 的onResume 后执行,从LOG日志和 ActivityStack 中可以看出,在新 Activity 启动之前,栈顶的 Activity 需要先onPause 后,新Activity 才能启动。

onSaveInstanceState 和 onRestoreInstanceState

1.onSaveInstanceState 和  onRestoreInstanceState 的作用:
     Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。 当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用。 但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
    
     在activity被杀掉之前调用保存每个实例的状态,以保证该状态可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (传入的Bundle参数是由onSaveInstanceState封装好的)中恢复。这个方法在一个activity被杀死前调用,当该activity在将来某个时刻回来时可以恢复其先前状态。
 
2. onSaveInstanceState() 什么时候调用
  当某个activity变得"容易"被系统销毁时,该activity的onSaveInstanceState()就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。
  何为"容易"?意思就是说该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些?通过重写一个activity的所有生命周期的onXXX方法,包括onSaveInstanceState()和onRestoreInstanceState() 方法,我们可以清楚地知道当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState()方法会在什么时候被执行,有这么几种情况:
(1)、当用户按下HOME键时。
  这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,因此系统会调用onSaveInstanceState(),让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
(2)、长按HOME键,选择运行其他的程序时。
(3)、按下电源按键(关闭屏幕显示)时。
(4)、从activity A中启动一个新的activity时。
(5)、屏幕方向切换时,例如从竖屏切换到横屏时。
(6)、 引发activity销毁和重建的其它情况 ,除了系统处于内存不足的原因会摧毁activity之外, 某些系统设置的改变也会 导致activity的摧毁和重建. 例如改变屏幕方向, 改变设备语言设定, 键盘弹出等。
  在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState()一定会被执行,且也一定会执行onRestoreInstanceState()。
  总而言之,onSaveInstanceState()的调用遵循一个重要原则,即当系统存在“未经你许可”时销毁了我们的activity的可能时,则onSaveInstanceState()会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。如果调用,调用将发生在onPause()或onStop()方法之前。(虽然测试时发现多数在onPause()前)

3. onRestoreInstanceState()什么时候调用 
  onRestoreInstanceState()被调用的前提是,activity A“确实”被系统销毁了并重建,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行 此也说明上二者,大多数情况下不成对被使用。
  onRestoreInstanceState()在onStart() 和 onResume()之间调用。

4. onSaveInstanceState()方法的默认实现
  如果我们没有覆写onSaveInstanceState()方法, 此方法的默认实现会自动保存activity中的某些状态数据, 比如activity中各种UI控件的状态.。android应用框架中定义的几乎所有UI控件都恰当的实现了onSaveInstanceState()方法,因此当activity被摧毁和重建时, 这些UI控件会自动保存和恢复状态数据. 比如EditText控件会自动保存和恢复输入的数据,而CheckBox控件会自动保存和恢复选中状态.开发者只需要为这些控件指定一个唯一的ID(通过设置android:id属性即可), 剩余的事情就可以自动完成了.如果没有为控件指定ID, 则这个控件就不会进行自动的数据保存和恢复操作,如果控件的ID相同,即不同子布局下具有相同ID的控件,则后面ID指向的控件内容会覆盖前一个ID控件指向的内容 
  由上所述, 如果我们需要覆写onSaveInstanceState()方法, 一般会在第一行代码中调用该方法的默认实现:super.onSaveInstanceState(outState)。

    重写Activity中还提供了一个onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
    onSaveInstanceState()方法会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle中取值,第二个参数是真正要保存的内容。

异常情况下的生命周期

1、资源相关的系统配置发生改变导致Activity 被杀死并重新创建

    比如手机屏幕横竖屏切换就会导致系统配置发生改变,从而造成Activity 的销毁和重建。在默认情况下,如果我们对Activity 不做特殊处理,那么当系统配置发生改变后,其生命周期如下所示:

当系统配置发生变化后,Activity 会被销毁,它的onPause、onStop、onDestroy会被调用,不过由于是 在异常情况下终止的,系统会在调用onStop 方法之前调用 onSaveInstanceState 方法保存 Activity 的状态(UI状态和数据),在Activity 重建时,从onCreate 或onRestoreInstanceState 中获取保存的Activity的状态,重新恢复Activity。

栗子:

public class MainActivity
        extends AppCompatActivity
{

    private static final String TAG = "MainActivity";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState!=null){
            String test = savedInstanceState.getString("extra_test");
            Log.e(TAG, "[onCreate] restore extra_test:" + test);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.e(TAG, "onSaveInstanceState");
        outState.putString("extra_test", "test");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        String test = savedInstanceState.getString("extra_test");
        Log.e(TAG, "[onRestoreInstanceState] restore extra_test:" + test);
    }
}
    上述代码很简单,就是在 onSaveInstanceState 方法中保存了一个字符串,在Activity 销毁重建后再去获取这个字符串。接收的位置可以是 onRestoreInstanceState 或者 onCreate,二者唯一的区别是 onRestoreInstanceState 一旦被调用,其参数 Bundle savedInstanceState 一定有值,而onCreate 则不一定,因为如果正常启动Activity,则onCreate 中携带的 Bundle 参数为 null。
LOG 日志如下:

2、资源不足导致低优先级的 Activity 被杀死

    Activity 的优先级从高到低可以分为如下三种:

    a、前台Activity-----正在和用户交互的Activity,优先级最高

    b、可见但非前台 Activity ---比如Activity 中弹出了一个对话框,导致Activity 可见但是位于后台无法和用户直接交互

    c、后台Activity-----已经被暂停的Activity,比如执行了onStop,优先级最低

    当系统资源不足时,系统就会按照上述优先级去杀死目标Activity 所在的进程,并在后续通过 onSaveInstanceState 和 onRestoreInstanceState 来存储和恢复数据。

    如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此一些后台工作不适合头里四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的做法是将后台工作放入 Service 中从而保证进程有一定的优先级,这样就不会轻易地被杀死。

    默认情况下横竖屏切换手机屏幕,Activity 会销毁并重建,如果不想让Activity 在屏幕旋转时重新创建,我们可以在清单文件中给 Activity 的 configChanges 属性添加 orientation 这个值,如下所示:

android:configChanges="orientation|keyboardHidden|screenSize"

    多个值用 “ | ” 连接。此时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。            configChanges 的属性值如下所示:

                                                                           configChanges 的属性及其含义

属性值

含义

mcc

SIM卡唯一标识IMSI(国际移动用户标识码)中的国家代码,由三位数字组成,中国为:460 这里标识mcc代码发生了改变

mnc

SIM卡唯一标识IMSI(国际移动用户标识码)中的运营商代码,有两位数字组成,中国移动TD系统为00,中国联通为01,电信为03,此项标识mnc发生了改变

locale

设备的本地位置发生了改变,一般指的是切换了系统语言

touchscreen

触摸屏发生了改变

keyboard

键盘类型发生了改变,比如用户使用了外接键盘

keyboardHidden

键盘的可访问性发生了改变,比如用户调出了键盘

navigation

系统导航方式发生了改变

screenLayout

屏幕布局发生了改变,很可能是用户激活了另外一个显示设备

fontScale

系统字体缩放比例发生了改变,比如用户选择了个新的字号

uiMode

用户界面模式发生了改变,比如开启夜间模式-API8新添加

orientation

屏幕方向发生改变,比如旋转了手机屏幕

screenSize

当屏幕尺寸信息发生改变(当编译选项中的minSdkVersiontargeSdkVersion均低于13时不会导致Activity重启)-API13新添加

smallestScreenSize

设备的物理屏幕尺寸发生改变,这个和屏幕方向没关系,比如切换到外部显示设备-API13新添加

layoutDirection

当布局方向发生改变的时候,正常情况下无法修改布局的layoutDirection的属性-API17新添加


猜你喜欢

转载自blog.csdn.net/daxiong25/article/details/80745697