当前位置:   article > 正文

Android Activity的生命周期学习记录_android 程序后台运行再进入显示的生命周期

android 程序后台运行再进入显示的生命周期

Activity的生命周期

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

一、典型情况下的生命周期分析

1.1 主要生命周期

  • (1)onCreate:表示Activity正在被创建,这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。当Activity创建实例完成,并调用attach方法赋值PhoneWindow、ContextImpl等属性之后,调用此方法。该方法在整个Activity生命周期内只会调用一次。调用该方法后Activity进入ON_CREATE状态。我们需要在这个方法中初始化基础组件和视图。如viewmodel,textview。同时必须在该方法中调用setContentView来给activity设置布局。这个方法接收一个参数,该参数保留之前状态的数据。如果是第一次启动,则该参数为空。该参数来自onSaveInstanceState存储的数据。只有当activity暂时销毁并且预期一定会被重新创建的时候才会被回调,如屏幕旋转、后台应用被销毁等
  • (2)onRestart:表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会执行onPause和onStop,接着用户又回到了这个Activity,就会调用onRestart。
  • (3)onStart:当Activity准备进入前台时会调用此方法。调用后Activity会进入ON_START
    状态。前台activity一般只有一个,所以这也意味着其他的activity都进入后台了。这里的前后台需要结合activity返回栈来理解,后续笔者再写一篇关于返回栈的。这个方法一般用于从别的activity切回来本activity的时候调用。
  • (4)onResume:onStart方法是后台与前台的区分,而这个方法是是否可交互的区分。使用场景最多是在当弹出别的activity的窗口时,原activity就会进入ON_PAUSE状态,但是仍然可见;当再次回到原activity的时候,就会回调onResume方法了。
  • (5)onPause:表示Activity正在停止,正常情况下,紧接着onStop就会被调用。在特殊情况下,如果这个时候快速地再回到当前Activity,那么onResume会被调用。笔者的理解是,这种情况属于极端情况,用户操作很难重现这一场景。此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示,onPause必须先执行完,新Activity的onResume才会执行。当前activity窗口失去焦点的时候,会调用此方法。调用后activity进入ON_PAUSE状态,并进入后台。一般的使用场景为界面进入后台时的轻量级资源释放。但是!!仍然是可见的,只是无法进行交互。
  • (6)onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。当新的activity界面显示出来的时候,原Activity才会进入ON_STOP状态,并回调onStop方法。同时,新activity创建的界面是在onResume方法之后才显示出来,所以onStop方法会在新activity的onResume方法回调之后再被回调。也可以这么说,新启动的activity并不会等待旧Activity的onStop执行完毕之后再显示。因而如果onStop方法里做一些比较耗时的操作也不会导致被启动的activity启动延迟。onStop方法的目的就是做资源释放操作。因为是在另一个activity显示之后再被回调,所以这里可以做一些相对重量级的资源释放操作,如中断网络请求、释放相机资源等。我们还应使用 [onStop()](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onStop())执行 CPU 相对密集的关闭操作。例如,如果无法找到更合适的时机来将信息保存到数据库,可以在 [onStop()](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onStop())期间执行此操作。在 [onStop()](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onStop())方法中,应用应释放或调整在应用对用户不可见时的无用资源。例如,应用可以暂停动画效果,或从精确位置更新切换到粗略位置更新。使用 [onStop()](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onStop())而非 [onPause()](https://developer.android.com/reference/android/app/Activity?hl=zh-cn#onPause())可确保与界面相关的工作继续进行,即使用户在多窗口模式下查看我们的 Activity 也能如此。
  • (7)onDestroy:表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。当activity被系统杀死或者调用finish方法之后,会回调该方法。调用该方法之后activity进入ON_DESTROY状态。这个方法是activity在被销毁前回调的最后一个方法。我们需要在这个方法中释放所有的资源,防止造成内存泄漏问题。回调该方法后的activity就等待被系统回收了。如果再次打开该activity需要从onCreate开始执行,重新创建activity。

在这里插入图片描述

启动Activity的请求会由Instrumentation来处理,然后它通过Binder向AMS发请求,AMS内部维护着一个ActivityStack并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。ActivityStack中的resumeTopActivity-InnerLocked方法

// We need to start pausing the current activity so the top one
        // can be resumed...
        boolean   dontWaitForPause   =   (next.info.flags&ActivityInfo.FLAG_RESUME_
        WHILE_PAUSING) ! = 0;
        boolean   pausing   =   mStackSupervisor.pauseBackStacks(userLeaving,   true,
        dontWaitForPause);
        if (mResumedActivity ! = null) {
            pausing |= startPausingLocked(userLeaving, false, true, dontWait-
            ForPause);
            if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " +
            mResumedActivity);
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在新Activity启动之前,桟顶的Activity需要先onPause后,新Activity才能启动。最终,在ActivityStackSupervisor中的realStartActivityLocked方法会调用如下代码。

app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
        System.identityHashCode(r), r.info, new Configuration(mService.
        mConfiguration),
        r.compat, r.task.voiceInteractor, app.repProcState, r.icicle,
        r.persistentState,
        results, newIntents, ! andResume, mService.isNextTransition-
        Forward(),
        profilerInfo);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这个app.thread的类型是IApplicationThread,而IApplicationThread的具体实现是ActivityThread中的ApplicationThread。所以,这段代码实际上调到了ActivityThread的中ApplicationThread的scheduleLaunchActivity方法,在其中最终会完成新Activity的onCreate、onStart、onResume的调用过程。因此,可以得出结论,是旧Activity先onPause,然后新Activity再启动。至于ApplicationThread的scheduleLaunchActivity方法为什么会完成新Activity的onCreate、onStart、onResume的调用过程,请看下面的代码。scheduleLaunchActivity最终会调用如下方法,而如下方法的确会完成onCreate、onStart、onResume的调用过程。

源码:ActivityThread# handleLaunchActivity

private  void  handleLaunchActivity(ActivityClientRecord  r,  Intent  custom-
Intent) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;
    if (r.profilerInfo ! = null) {
        mProfiler.setProfiler(r.profilerInfo);
        mProfiler.startProfiling();
    }

    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    if (localLOGV) Slog.v(
        TAG, "Handling launch of " + r);

    //这里新Activity被创建出来,其onCreate和onStart会被调用
    Activity a = performLaunchActivity(r, customIntent);

    if (a ! = null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        //这里新Activity的onResume会被调用
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && ! r.startsNotResumed);
    //省略
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

从上面的分析可以看出,当新启动一个Activity的时候,旧Activity的onPause会先执行,然后才会启动新的Activity。

二、异常情况下的生命周期分析

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

系统的资源加载机制,拿图片来说,我们把可以通过Resources去获取drawable目录下的图片。同时为了兼容不同的设备,我们可能还需要在其他一些目录放置不同的图片,比如drawable-mdpi、drawable-hdpi、drawable-land等。当应用程序启动时,系统就会根据当前设备的情况去加载合适的Resources资源,比如说横屏手机和竖屏手机会拿到两张不同的图片(设定了landscape或者portrait状态下的图片)。

在默认情况下,当系统配置发生改变后,Activity就会被销毁并重新创建系统会调用onSaveInstanceState来保存当前Activity的状态。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果被重建我们就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState的调用时机在onStart之后(我没试)在onSaveInstanceState和onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作。

当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前Activity的视图结构,并且在Activity重启后为我们恢复这些数据,比如文本框中用户输入的数据、ListView滚动的位置等。具体针对某一个特定的View系统能为我们恢复哪些数据,我们可以查看View的源码。和Activity一样,每个View都有onSaveInstanceState和onRestoreInstanceState这两个方法,看具体实现就能知道每个View恢复哪些数据

在这里插入图片描述

保存和恢复View层次结构,系统的工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window再委托它上面的顶级容器去保存数据。顶层容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一种典型的委托思想,上层委托下层、父容器委托子元素去处理一件事情,这种思想在Android中有很多应用,比如View的绘制过程、事件分发等都是采用类似的思想

TextView# onSaveInstanceState

        @Override
        public Parcelable onSaveInstanceState() {
            Parcelable superState = super.onSaveInstanceState();

            // Save state if we are forced to
            boolean save = mFreezesText;
            int start = 0;
            int end = 0;
          if (mText ! = null) {
                start = getSelectionStart();
                end = getSelectionEnd();
                if (start >= 0 || end >= 0) {
                    // Or save state if there is a selection
                    save = true;
                }
            }

            if (save) {
                SavedState ss = new SavedState(superState);
                // XXX Should also save the current scroll position!
                ss.selStart = start;
                ss.selEnd = end;

                if (mText instanceof Spanned) {
                          Spannable sp = new SpannableStringBuilder(mText);

                          if (mEditor ! = null) {
                              removeMisspelledSpans(sp);
                              sp.removeSpan(mEditor.mSuggestionRangeSpan);
                          }

                          ss.text = sp;
                      } else {
                          ss.text = mText.toString();
                      }

                      if (isFocused() && start >= 0 && end >= 0) {
                          ss.frozenWithFocus = true;
                      }

                      ss.error = getError();

                      return ss;
                  }

                  return superState;
              }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

在onSaveInstanceState中存储一个字符串,然后当Activity被销毁并重新创建后,我们再去获取之前存储的字符串。接收的位置可以选择onRestoreInstanceState或者onCreate,二者的区别是:onRestoreInstanceState一旦被调用,其参数Bundle savedInstanceState一定是有值的,我们不用额外地判断是否为空;但是onCreate不行,onCreate如果是正常启动的话,其参数Bundle savedInstanceState为null,所以必须要额外判断。这两个方法我们选择任意一个都可以进行数据恢复,但是官方文档的建议是采用onRestoreInstanceState去恢复数据

2.2 资源内存不足导致低优先级的Activity被杀死

  • (1)前台Activity——正在和用户交互的Activity,优先级最高
  • (2)可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
  • (3)后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低

当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数数据。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死

当系统配置发生改变后,Activity会被重新创建,那么有没有办法不重新创建呢?答案是有的,接下来我们就来分析这个问题。系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity,可以给Activity指定configChanges属性。比如不想让Activity在屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation这个值android:configChanges="orientation"

系统配置中所含的项目及含义如表所示。

在这里插入图片描述

如果我们没有在Activity的configChanges属性中指定该选项的话,当配置发生改变后就会导致Activity重新创建。上面表格中的项目是我们常用的只有locale、orientation和keyboardHidden

screenSize和smallestScreenSize两个比较特殊,它们的行为和编译选项有关,但和运行环境无关。

  <activity
        android:name="com.ryg.chapter_1.MainActivity"
        android:configChanges="**orientation**|**screenSize**"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        Log.d(TAG, "onConfigurationChanged, newOrientation:" + newConfig.
        orientation);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

由于编译时笔者指定的minSdkVersion和targetSdkVersion大于13,所以为了防止旋转屏幕时Activity重启,除了orientation,我们还要加上screenSize,原因在上面的表格里已经说明了。其他代码还是不变。

在这里插入图片描述

由日志可见,Activity的确没有重新创建,并且也没有调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,取而代之的是系统调用了Activity的onConfigurationChanged方法,这个时候我们就可以做一些自己的特殊处理了

小结

我们在设计程序的时候,我们会说这个界面有什么功能,那个界面有什么功能,多个界面之间如何协调。对于用户来说,他们感知的也是一个个独立的界面。当我们通过通讯软件调用邮箱app的发送邮件界面时,我们喜欢看到的只是发送邮件的界面,而不需要看其他界面具体的调用细节。以功能模块为应用模型的设计从一个主功能模块入口,然后通过用户的输入去调用不同的功能模块。与其类似,android程序也有一个主界面,通过这个主界面,接受用户的操作去调用其他的界面。组成android程序的,是一个个的界面,而每一个界面,对应一个Activity。因此,Activity是android平台应用模型的基本组成成分。

那如何做到每个界面之间彼此解耦、各自的显示不发生混乱、界面之间的跳转有条不紊等等?这些工作,官方都帮我们做好了。Activity就是在这个设计思想下开发出来的。当我们在Activity上开发的时候,就已经沿用了他的这种设计思想。当我们开发一个app的时候,最开始要考虑的,是界面如何设计。设计好界面之后,就是考虑如何开发每个界面了。那我们如何自定义好每一个界面?如何根据我们的需求去设计每个界面的功能?Activity并没有main方法,我们的代码该写在哪里被执行?答案就是:生命周期回调方法。

到这里,你应该可以理解为什么启动activity并不是一句new就可以解决的吧?Activity承担的责任非常多,需要初始化的逻辑也非常多。当Activity被启动,他会根据自身的启动情况,来回调不同的生命周期方法。其中,承担初始化整个界面已经各个功能组件的初始化任务的就是onCreate方法。他有点类似于我们功能模块的入口函数,在这里我们通过setContentView来设计我们界面的布局,通过setOnClickListenner来给每个view设置监听等等。在MVVM设计模式中,还需要初始化viewModel、绑定数据等等。这是生命周期的第一个非常重要的意义所在。

onSaveInstanceState:保存临时数据 & 状态

系统不知道你切换到其他地方后要运行多少其他的程序,自然也不知Activity A是否会被销毁,故系统会调用onSaveInstanceState()。

  • onSaveInstanceState的bundle参数会传递到onCreate方法中,可选择在onCreate()中做数据还原
  • 当系统 未经你许可时,可能销毁了你的Activity,则会被系统调用 。
  • 肯定在 调用onStop()前被调用,但不保证在onPause()前 / 后。

例:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {

// 通过Bundle参数以键值对的方式进行数据的存储
// 数据恢复:onRestoreInstanceState() & onCreate()
// 上述二者都有一个Bundle类型的参数用于恢复数据
        savedInstanceState.putBoolean("MyBoolean", true);
        savedInstanceState.putDouble("myDouble", 1.9);
        savedInstanceState.putInt("MyInt", 1);
        savedInstanceState.putString("MyString", "Welcome back to Android");
        // ...
        super.onSaveInstanceState(savedInstanceState);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

onRestoreInstanceState:恢复临时数据 & 状态

当系统“未经你许可”时,确实销毁了你的Activity,则重新启动时会被系统调用

  • 与onSaveInstanceState()区别:此处是 “确实销毁”后才调用
  • 若是 被用户主动销毁(如 用户按Back键),则不会调用
  • 肯定在调用 onStop()前被调用,但不保证在onPause()前 / 后

若 异常关闭了Activity,即调用了onSaveInstanceState() & 下次启动时会调用onRestoreInstanceState()

注:此时结合Activity的生命周期的调用顺序是:

onCreate()
onStart()
onRestoreInstanceState()
onResume()

例:

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
        double myDouble = savedInstanceState.getDouble("myDouble");
        int myInt = savedInstanceState.getInt("MyInt");
        String myString = savedInstanceState.getString("MyString");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • onSaveInstanceState()、onRestoreInstanceState()不一定 成对被调用

三、场景实例再现

跳转到其他Activity:

在api28及以上版本是先onStop再onSaveInstanceState调用的,

在这里插入图片描述

但是在低版本中,是先onSaveInstanceState再onStop的。

在这里插入图片描述

当activity进入后台的时候,onSaveInstanceState方法则会被调用,而不是异常情况下才会调用onSaveInstanceState方法,因为并不确定在后台时,activity是否会被系统杀死,所以以最保险的方法,先保存数据。当确实是因为异常情况被杀死时,返回activity用户期望界面需要恢复数据,才会调用onRestoreInstanceState来恢复数据**,其他情况不会触发这个过程**。但activity直接按返回键或者调用finish方法直接结束Activity时,非常明确下一次返回该activity用户期望的是一个干净界面的新activity,就不调用。

onSaveInstanceState不能做重量级的数据存储,其存储数据的原理是把数据序列化到磁盘中,如果存储的数据过于庞大,会导致界面卡顿,掉帧等情况出现。正常情况下,每个view都会重写这两个方法,当activity的这两个方法被调用的时候,会向上委托window去调用顶层viewGroup的这两个方法;而viewGroup会递归调用子view的onSaveInstanceState/onRestoreInstanceState方法,这样所有的view状态就都被恢复了。

onPause调用之后,activity会进入后台。而前台交互的activity只能有一个,所以原activity必须先进入后台后,目标activity才能启动并进入前台。onStop调用之后activity变得不可见,因而只有在目标activity即将要与用户交互的时候,需要进行显示了,原Activity才会调用onStop方法进入不可见状态。

从后台回到前台:

当从后台会到前台时,系统先调用onRestart方法,然后调用onStart方法,最后调用onResume方法,Activity又进入了运行状态。

调用android:theme为@android:style/Theme.Dialog的Activity

按下锁屏键

在这里插入图片描述

打开屏幕

执行onRestart

屏幕旋转

在这里插入图片描述

当因资源配置改变时,activity会销毁重建,最常见的就是屏幕旋转。这个时候属于异常情况的Activity生命结束。因而,在销毁的时候,会调用onSaveInstanceState来保存数据,在重新创建新的activity的时候,会调用onRestoreInstanceState来恢复数据。

按下home返回主屏幕

生命周期的一个重要作用就是让activity在不同状态之间切换的时候,可以执行对应的逻辑。举个栗子。我们在界面A使用了相机资源,当我们切换到下个界面B的时候,那么界面A就必须释放相机资源,这样才不会导致界面B无法使用相机;而当我们切回界面A的时候,又希望界面A继续保持拥有相机资源的状态;那么我们就需要在界面不可见的时候释放相机资源,而在界面恢复的时候再次获取相机资源。每个Activity一般情况下可以认为是一个界面或者说,一个屏幕。当我们在界面之间进行导航切换的时候,其实就是在切换Activity。当界面在不同状态之间进行切换的时候,也就是Activity状态的切换,就会回调activity相关的方法。

参考资料

Android开发艺术探索

https://blog.csdn.net/weixin_43766753/article/details/109565384
https://blog.csdn.net/carson_ho/article/details/107242189

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/144787
推荐阅读