×

Activity生命周期探索

96
水月沐風
2018.04.18 11:17 字数 2873

我们都知道,activity作为Android的四大组件之一,在页面渲染方面起到至关重要的作用。面试中,activity生命周期也是一个必定考察的问题,那么本文就来针对其生命周期做一个详细讲解,让我们看看Android界面从不可见到可见过程都经历了些什么。

概述

在我们平时开发过程中,创建一个activity已经成了家常便饭。那么,除了它的onCreate()方法以外,你是否对其他生命周期方法也熟悉呢?正常情况下,我们可能知道启动一个activity一般会经历onCreate,onstart,onResume,onPause,onStop,onDestory这六个过程,但这也只是一般情况。如果我们期间启动了另一个activity呢?或者当系统内存资源不足或者系统资源配置改变时,activity又经历了怎样的生命周期变化呢?下面,我将和大家一起来探索这几个问题。首先,我们需要先熟悉一下,在正常情况下,activity生命周期是怎样的。

正常情况下的Activity生命周期

首先,国际惯例,先来看一张activity生命周期图:

Activity生命周期图

从图中可以看出,一般情况下,activity会经历如下生命周期:

  • onCreate:activity创建时期,我们每次新建一个activity,系统会自动为我们生成一个onCreate方法,包含一个setContentView方法去加载布局资源。在onCreate方法里,我们可以做一些布局中view的初始化以及数据初始化工作。
  • onStart:这是activity的启动时期,表示activity正在启动中。这时候,activity已经可见了,但没有出现在让用户可以看见的前台,尚不能与用户产生交互性。
  • onResume:这个时期,activity已经可以被用户看见了,处于前台,用户可以与之交互。这里需要注意的是,虽然onResume和onStart都表示已经可见,但是与之不同的是,onStart处于后台,用户无法感知,onResume处于前台,用户可以看得到。
  • onPause:这个时期表示activity正在被停止,一般情况下,onStop方法会被马上调用,但是特殊情况下,因为某些操作快速回到了该activity,并不会走onStop,而是调用onResume方法,重新回到前台。我们可以在onPause方法里做一些简单的操作,如停止动画、停止摄像头、数据存储等。但一定要注意,不能做一些过于耗时的操作,因为在系统源码中,新的activity要在当前activity调用onPause方法之后才会显示,所以,耗时操作会影响新活动的出现(onResume)时间。
  • onStop:该时期表示activity即将停止。此时,activity已经不可见,处于后台。我们可以在这个方法中做一些轻量级的回收工作,但不能太耗时。
  • onDestory:该时期activity即将被摧毁,同时也是生命周期里最后一个回调方法。我们可以在这里进行一些最终资源的回收以及释放工作。

以上是最为常见的activity工作周期,我们再来看下几种常见的activity生命周期切换的情况(包含流程图中的分支):

  1. 一般地,我们启动一个Activity并为用户可视化,会调用如下方法:onCreate->onStart->onResume。
  2. 当用户启动新的Activity或者home 键切到桌面时,会调用如下方法:onPause->onStop,进入后台不可视状态。但如果新的Activity采用了透明主题,那么它不会调用onStop方法。
  3. 如果用户再次回到原Activity,那么回调如下:onRestart->onStart->onResume。
  4. 如果用户点击home 键,会调用如下方法:onPause->onStop->onDestory。

总结:就整个生命周期来说,onCreate和onDestory配对,分别表示活动的创建和销毁,整个周期只调用一次。onStart和onStop是配对的,它们分别标志着activity可见与不可见,这两个方法会伴随着用户操作和息屏而调用多次。onResume和onPause配对,分别标志前台可见性与不可见性,即是否为用户看见,它们也会随着某些操作而调用多次。

异常情况下Activity生命周期

正常情况下Activity生命周期如上所示,但某些特殊情境下,比如系统资源配置发生改变或者内存不足时,Activity就可能会被杀死,我们来具体看一下这两种情况下Activity生命周期的变化以及该如何处理才能让用户尽可能的接受其变化。

系统资源配置发生改变引起的Activity被杀死重建

最常见的就是横竖屏切换时发生的系统配置改变了,一般情况下,Activity就会被杀死重新创建。这时候,如果我们不做一些处理,activity便会重新加载一遍,可能会对用户造成不好的体验效果。我们需要先了解当系统配置发生改变时,activity会经历一个怎样的过程。

首先,当前Activity会被销毁,依次调用onPause、onStop和onDestory方法,在此期间,Activity还会调用onSavedInstanceState方法来保存当前Activity的状态,以便在重启Activity时在onCreate或者onRestoreInstanceState方法中恢复之前的Activity状态。一般来说,onSavedInstanceState方法会在onStop之前调用,但具体时序不固定,也可能在onPause之前调用,而onRestoreInstanceState方法一般在新创建的Activity的onStart方法之后调用。需要注意的是:onSavedInstanceState方法只会在activity被异常终止的时候才会调用,正常finish掉activity并不会调用这个方法。

在onSavedInstanceState和onRestoreInstanceState方法中,系统已经为我们做了一些恢复性工作。例如,在activity因异常而被杀死重建时,系统为我们自动保存activity的视图结构(onSavedInstanceState),并在重建activity后为我们恢复这些数据(onRestoreInstanceState),这些数据包含EditText的输入内容,列表控件listview、recyclerview等滑动位置等。

那么,系统是如何为我们存储这些view等状态数据等呢?首先,Activity被异常销毁后,会调用onSavedInstanceState来保存数据,接着去委托上层Window去保存数据,然后,Window又会委托它的顶层容器(一般为DecorView)去存储数据,最后,顶层容器通知它的子元素保存数据。同时,View中也提供了onSavedInstanceState和onRestoreInstanceState方法老保存它们各自的数据,这样,整个流程就一目了然了。

让我们通过屏幕旋转来模拟一下异常终止activity的情况,同时,我们用代码来对数据进行存储和恢复:

public static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if(savedInstanceState!=null){
        String data = savedInstanceState.getString("data_key");
        Log.i(TAG, "onCreate: "+data);
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("data_key","data");
    Log.i(TAG, "onSaveInstanceState");
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    String data = savedInstanceState.getString("data_key");
    Log.i(TAG, "onRestoreInstanceState: "+data);
}

我们通过在onSaveInstanceState中保存数据,然后activity重新创建时,在onCreate和onRestoreInstanceState中读取并恢复数据。运行上面代码可以看到如下结果:

04-18 09:01:13.038 30035-30035/com.moos.example I/MainActivity: onSaveInstanceState
04-18 09:01:13.067 30035-30035/com.moos.example I/DecorView: setWindowBackground mBackgroundPadding = Rect(0, 0 - 0, 0), mFramePadding = Rect(0, 0 - 0, 0), pkg = com.moos.example
04-18 09:01:13.079 30035-30035/com.moos.example I/MainActivity: onCreate: data
04-18 09:01:13.080 30035-30035/com.moos.example I/MainActivity: onRestoreInstanceState: data

通过结果可以看出,在activity被销毁重建后,onCreate和onRestoreInstanceState方法中都获取到了之前保存的数据。两者的区别是:onRestoreInstanceState方法一旦调用,里面的参数Bundle一定是有值的,我们无须判空;而onCreate肯定是不行的,因为activity被正常创建依旧会走该方法,所以需要对参数Bundle进行判空处理。如果我们需要恢复数据,官方建议使用onRestoreInstanceState方法。

通过以上的讲解,我们得到以下结论:当activity因为异常终止并重建时,我们可以使用onSavedInstanceState来存储数据,接着在onRestoreInstanceState或者onCreate方法中获取数据,推荐使用onRestoreInstanceState来获取数据。此外,系统只在activity被异常终止的时候才会调用onSavedInstanceState和onRestoreInstanceState来存储和恢复数据,请他情况并不会触发该过程。

内存资源不足导致Activity被销毁

这种情况下,我们同样可以使用onSavedInstanceState和onRestoreInstanceState来进行数据的保存和恢复。需要注意的是,系统为我们应用的activity进行了优先级排序,一般情况下,内存资源不足时,低优先级的activity容易被杀死。优先级大致可以分为下面三类(从高到低):

  • 前台Activity优先级最高,是表示正在与用户进行交互的activity。
  • 并非前台Activity,但是可见,这种情况可以是activity上面被覆盖了个弹窗,导致它可见,但是无法和用户交互。
  • 后台Activity,指被暂停的activity,比如执行了onStop,没有处于栈顶,优先级最低。

当系统的内存不足时,倘若一个进程中没有四大组件在运行,那么该进程可能很快被杀死,因此一些后台的工作不适合脱离四大组件而独立运行在后台,这样进程很快会被杀死。比较好的解决方案是将后台工作放在Service中执行,从而保证进程具有一定的优先级,这样就不会被轻易杀死。

系统配置改变后阻止Activity重建

在上面我们说过,当系统资源配置发生改变后,会引起Activity的销毁和重建,这只是在我们不干预的情况下发生的。如果系统配置改变后,我们希望Activity不被销毁和重建,可以通过为activity指定configChanges属性,常用的有:locale、orientation、keyboardHidden和screenSize,更多属性可以查看这里。至于使用方法,只需要在AndroidManifest中对应的activity加入configChanges声明即可,如:

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity 
        android:name=".MainActivity"
        android:configChanges="keyboardHidden|screenSize|orientation|locale">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

同时,我们可以在activity中通过onConfigurationChanged方法来获取当前activity的系统配置信息,比如:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){

        Log.i(TAG, "onConfigurationChanged: 切到横屏了");
    }
}

通过以上代码来监听系统配置是否发生改变,比如这里的代码用来判断是否横屏,然后我们将手机横屏后,可以看到如下日志信息:

04-18 10:45:10.702 19503-19503/com.moos.example I/MainActivity: onConfigurationChanged: 切到横屏了
04-18 10:45:10.804 19503-19503/com.moos.example I/DecorView: onConfigurationChanged mBackgroundPadding = Rect(0, 0 - 0, 0), mFramePadding = Rect(0, 0 - 0, 0), pkg = com.moos.example
04-18 10:45:13.415 19503-19503/com.moos.example I/DecorView: onConfigurationChanged mBackgroundPadding = Rect(0, 0 - 0, 0), mFramePadding = Rect(0, 0 - 0, 0), pkg = com.moos.example

可以看到,切到横屏后,的确是打印了相关信息。此外,我们发现,之前的onSavedInstanceState、onCreate和onRestoreInstanceState方法并没有调用,说明我们的确阻止了activity的销毁和重建。

声明:本文大部分内容来自于Android开发艺术探索,仅供个人总结和学习,欢迎大家提出意见。

Android日常系列
Web note ad 1