你仍未知道那些有关 Activity典型、异常情况下的生命周期解析

平常在移动开发过程中,说起Activity的生命周期,开发人员不会陌生,但是此处概念切勿混淆,其生命周期分为典型情况下的生命周期与异常情况下的生命周期。第一种代表在用户交互情况下,Activity所经历的生命周期变化;第二种是指Activity被系统回收或者当前设备的Configration发生改变从而导致Activity被销毁重建。我们通常较熟悉Activity的典型生命周期,从而稍有忽略异常情况下的生命周期,仅以此篇来总结剖析这两种情况下的Activity生命周期,及存在的重点和易忽略的问题。

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

这里写图片描述

1. 七大基本生命周期介绍

在正常情况下,Activity会经历如下生命周期:

(1)onCreate
含义:表示Activity正在被创建,这是生命周期的第一个方法。

处理事件:在此方法中,开发者可以做一些初始化工作,例如最常见的调用setContentView 方法去加载界面布局资源,初始化Activity所需的数据。


(2)onRestart
含义:表示Activity正在重新启动。

注意:一般情况下,若当前Activity从不可见状态重新变成可见状态时,onRestart方法会被调用。这种情形一般是用户行为所致,例如用户按Home键切换到桌面 或者 用户打开了一个新的 Activity,此时当期Activity会暂停,也就是调用生命周期中的onPauseonStop方法,接着用户又回到此Activity,onRestart方法会被回调。


(3)onStart
含义:表示Activity正在被启动。

注意:此时Activity已经是可见状态了,但是还没有出现在前台,无法与用户交互。这种状态可以理解成:Activity已经显示出来了,可我们还看不到。


(4)onResume
含义:表示Activity已经可见了,并且出现在前台开始活动。

注意:要注意此方法与 onStart的对比,两者都表示Activity已经可见,但是onStart时的Activity还在后台,而onResume时的Activity已经显示到前台


(5)onPause
含义:表示Activity正在停止。

注意:在正常情况下,当onPause被调用时,紧接着onStop方法会被调用。在特殊情况下,如果这个时候快速地再回到当前Activity,那么onStop方法不会被调用,调用onResume,但是这属于极端情况,用户操作很难实现这一场景。

处理事件:此时可以做一些存储数据、停止动画等工作,但是注意不能做耗时操作,因为这会影响到新Activity的显示,onPause必须先执行完,新的ActivityonResume方法才会被调用。


(6)onStop
含义:表示Activity正在停止。

处理事件:此时可以做一些稍微重量级的回收工作,但同样不能太耗时。


(7)onDestroy
含义:表示Activity即将被销毁。

处理事件:这是Activity生命周期的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。



2. 常用场景方法回调

(1)针对一个特定的 Activity,第一次启动,回调方法如下:
onCreate –> onStart –> onResume

(2)当用户打开新的Activity或者切换桌面时,回调如下:
onPause –> onStop
这里有一种特殊情况,如果新Activity 采用了透明主题,那么当前Activity不会回调 onStop

(3)当用户再次回到原Activity时,回调如下:
onRestart –> onStart –> onResume

(4)当用户按 back 键回退时,回调如下:
onPause –> onStop –> onDestroy

(5)当Activity 被系统回收后再次打开,生命周期方法回调过程和(1)相同。注意是生命周期方法一样,不代表所有的过程一样。

(6)从整个生命周期来说,onCreateonDestroy是配对的,分别标识Activity的创建和销毁,并且只可能有一次调用。从Activity是否可见来说onStartonStop是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可以被调用多次;从Activity是否在前台来说onResumeonPause是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可以被调用多次。



3. 典型生命周期下的问题剖析

(1)onStart 和 onResume,onPause 和 onStop 从描述上来看差不多,有什么实质上的不同吗?

从实际使用过程来说,onStart 和 onResume、onPause 和 onStop看似差不多,但是Android系统不会提供具有重复作用的接口。根据以上使用场景的分析,这两个配对的回调分别代表不同的意义,onStartonStop从Activity是否可见这个角度来回调的,而onResumeonPause从Activity是否在前台这个角度来回调的,除了这个区别,在实际应用中没有其他明显区别。


(2)假设当前Activity为A,如果这时用户打开一个新的Activity B,那么B的onResume方法和A的onPause方法哪个先执行呢?

分析方法一:

此问题追根溯源关系到Activity的启动过程,此过程相当复杂,涉及到 Instrumentation、ActivityThread 和 ActivityManagerService(简称AMS),这里并不详细分析此过程,只从此问题角度出发简单分析,详细过程可看此篇blog,也是由本人总结而来:http://blog.csdn.net/itermeng/article/details/71250158

首先简单描述Activity启动过程:启动Activity的请求会交给 Instrumentation 处理,它通过 Binder 向 AMS 发送请求,AMS 内部维护这一个 ActivityStack 并负责栈内的 Activity 的状态同步,AMS 通过 ActivityThread 来同步 Activity的状态而完成声明周期方法的调用。

在ActivityStack 中的 resumeTopActivityInnerLocked 方法中,有如下代码:

    //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, dontWaitForPause);
        if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing" + mResumedActivity);
     }

上述代码可以看出,在新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,
                    app.repProcState, r.icicle, results, newIntents, !andResume,
                    mService.isNextTransitionForward(), profileFile, profileFd,
                    profileAutoStop);

该 app.thread 的类型是 IApplicationThread,而IApplicationThread的具体实现是 ActivityThread 中的 ApplicationThread 所以,这段代码实际调用了ActivityThread中的 ApplicationThread类中的scheduleLaunchActivity 方法,而此方法最终会完成新Activity的onCreateonStartonResume的调用过程。

因此,可得出结论:系统会先调用原先Activity的onPause 方法,再启动新Activity,即调用它的onCreateonStartonResume方法。


分析方法二:

以上分析方法是从源码底层的角度去分析问题,未了解过Activity启动相关知识的初学者可能理解起来有些费劲,此方法比较简单明了,直接编写两个Activity,从日志打印的结果来证实分析方法一的结论,代码如下:

public class OneActivity extends AppCompatActivity {

    private static final String Tag = "OneActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_one);
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d(Tag, "onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();

        Log.d(Tag, "onStop");
    }
}
public class TwoActivity extends AppCompatActivity {
    private static final String Tag = "TwoActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_two);
    }

    @Override
    protected void onStart() {
        super.onStart();

        Log.d(Tag, "onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();

        Log.d(Tag, "onResume");
    }
}

日志打印结果:

这里写图片描述

通过日志打印,再次证实了以上结论。注意:以上的源码分析版本来自于Android 5.0,也许你会怀疑版本不同其逻辑可能有改变,但是作为Android运行过程的基本机制,随着版本的更新并不会有太大影响,因为Android系统也需要兼容性,只能说Android运行的基本机制在不同版本上具有延续性。

最后,在Android官方文档中对onPause 方法的解释有提到:不能在此方法中做重量级的操作,因为此方法执行完后新Activity才能处于前台(即新Activity被调用onResume方法)。此话也间接证明了以上结论。通过对此问题的一系列分析,可知onPauseonStop 都不能执行耗时的操作,尤其是onPause方法,所以尽量在onStop 方法中做操作,从而使得新Activity尽快显示出来并切换到前台!





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

我们知道,Activity除了受用户操作所导致的正常的生命周期方法调度,还有一些异常的情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。下面具体分析这两种情况:

1. 资源相关的系统配置发生改变

在理解此问题之前,简单说明一下系统的资源加载机制:拿最简单的图片来说,当我们把一张图片放到 drawable目录后,就可以通过Resources去获取这张图片。同时为了兼容不同的设备,还需要在放置到不同要求的目录中,比如 drawable-mdpi 、drawable-hdpi。当应用程序启动时,系统会根据当前设备的情况去加载合适的Resources资源,比如横屏和竖屏情况下手机会展现两张不同的图片(设定了landscape 或 portrait状态下的图片)。若当前Activity处于竖屏状态,突然旋转屏幕,由于系统配置发送改变,在默认情况下Activity会被销毁并且重新创建(当然也可以阻止系统重新创建)。

了解了简单的资源加载机制后,我们知道在默认情况下,若Activity不做特殊处理,那么当系统配置发生改变后,Activity就会被销毁并重新创建,其生命周期如下所示:

这里写图片描述

(1) onSaveInstanceState 介绍

当系统配置发生改变后,Activity会被销毁,其 onPause、onStop、onDestroy均会被调用,而且由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState 来保存当前Activity的状态。这个方法调用时机是在onStop之前,它和onPause没有规定的时序关系。需要强调的是,此方法只会出现在Activity被异常终止的情况下,正常情况下不会回调这个方法。


(2) onRestoreInstanceState 介绍

当Activity被重新创建后,系统会调用 onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState 方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceStateonCreate方法,因此可以通过这两个方法来判断Activity是否被重建了,若被重建可以取出之前保存的数据并恢复。注意:从时序上来说,onRestoreInstanceState的调用时机在onStart之后。


(3) onSaveInstanceState 搭配 onRestoreInstanceState

同时需要注意,在onSaveInstanceState 和 onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作,当Activity在异常情况下需要重新创建时,系统会默认保存当前Activity的试图结构,并且在Activity重启后为我们恢复这些数据,比如文本框中用户输入的数据、ListView滚动的位置等,这些View相关的状态系统都能够自动恢复。具体针对某一特定的View,系统可以恢复对应的数据。在View的源码中,和Activity一样,每个View都有onSaveInstanceState 和 onRestoreInstanceState方法。


(4) 以TextView为例

关于保存和恢复View层次结构,系统工作流程如下:

  • 首先Activity被意外终止时会调用onSaveInstanceState 去保存数据
  • 然后Activity会委托 Window去保存数据
  • 接着Window再委托它上面的顶级容器去保存数据。
  • 最后顶层容器(顶层容器是一个ViewGroup,一般来说是DectorView)再一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。

以上过程时一种典型的委托思想,上层委托下层、父容器委托子容器去处理一件事,这种思想其实在Android中应用广泛,比如View的绘制过程、时间分发等。数据恢复过程便是如此,接下来以TextView为例,分析它保存了哪些数据。

【TextView类源码】

    @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 SpannableString(mText);

                for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
                    sp.removeSpan(cw);
                }

                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;
    }

从上述源码可以看出,TextView保存了自己的文本选中状态和文本内容,而且通过查看其 onRestoreInstanceState 方法的源码,可以证实它确实恢复了这些数据。例如在一个文本框中输入了“恢复的文本”后旋转屏幕,Activity被销毁又被重新创建,此时文本框的内容会被正确还原。这说明系统的确能够自动地做一些View层次结构方面的数据存储和恢复。


(5)Activity实例验证数据存储和恢复

下面通过个简单的Activity实例来验证数据存储和恢复,代码如下:

public class MainActivity extends AppCompatActivity {

    private 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.d(Tag, "[onCreate]restore extra_test:" + test);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        Log.d(Tag, "onSaveInstanceState");
        outState.putString("extra_test","test");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        String test = savedInstanceState.getString("extra_test");

        Log.d(Tag, "[onRestoreInstanceState] extra_test:" + test);
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d(Tag, "onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();

        Log.d(Tag, "onStop");
    }

    @Override
    protected void onDestroy() {
        super.onStop();

        Log.d(Tag, "onDestroy");
    }
}

如上所示,代码并不难,主要逻辑为:首先在onSaveInstanceState() 方法中存储一个字符串,然后当Activity被销毁并重新创建后,再去获取之前存储的字符串。注意,此处获取可以在onRestoreInstanceState(Bundle savedInstanceState)onCreate(Bundle savedInstanceState) 方法,二者的区别是:onRestoreInstanceState 方法一旦被调用,其参数 savedInstanceState 一定是有值的,即非空;而 onCreate 方法若是正常启动的,其参数为空,所以需要额外的判断(官方文档建议通过 onRestoreInstanceState 方法来恢复数据)。经过前几小点的分析,可以通过此例来证实运行顺序,结果如下图:

这里写图片描述

如上结果,Activity被销毁后声明周期依次调用 onPause –> onSaveInstanceState –> onStop –> onDestroy,在onSaveInstanceState 方法中保存数据,Activity重新创建后可在onCreateonResaveInstanceState 中正确恢复之前存储的字符串,此例也证实以上的结论。

onSaveInstanceState 注意点
关于onSaveInstanceState 方法需注意一点,系统只会在Activity即将被销毁并且有机会重新显示的情况下才会调用此方法。试考虑一种情况,当Activity正常销毁时,系统不会调用onSaveInstanceState 方法,因为被销毁的Activity何时再被显示不得而知,换句话说,它可能立刻被用户重新打开显示,可能过段时间再被打开显示。相较于旋转屏幕而造成Activity异常销毁的情况,两者过程区别就非常明显了,此时旋转后的Activity销毁后的结果只有一个,即立刻重新创建新的Activity实例,所以它是百分百会再次被显示,所以系统会进行数据存储。

结论
系统只在Activity异常终止的情况下才会调用onSaveInstanceStateonResaveInstanceState 方法来存储和恢复数据,其它情况下不会触发此过程。



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

此种情况不好模拟,但是其数据存储和恢复过程与上述情况完全一致。首先描述Activity优先级情况,Activity可按照优先级从高到低分为如下三种:

(1)Activity优先级

  • 前台Activity :正在和用户进行交互的Activity,优先级最高。
  • 可见但非前台的Activity:例如Activity中弹出的对话框,导致Activity可见但是位于后台无法与用户直接交互。
  • 后台Activity:已经被暂停的Activity,例如已经执行了生命周期的onStop 方法,优先级最低。

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


(3)系统配置改变,防止Activity重新创建 —— configChanges

以上分析了系统的数据存储和恢复机制,即当系统发生改变后,Activity会被重新创建,接下来分析Activity不被重新创建相关内容。

系统配置中有很多内容,当某项内容发生改变后,若不想重新创建Activity,可以给Activity指定 configChanges 属性。例如不想让 Activity在屏幕旋转时重新创建,就可以在AndroidManifest.xml配置文件中给对应Activity的属性设置 orientation值,代码如下:

android:configChanges="orientation"

若想同时指定多个值,可以用“|”连接起来,例如:

android:configChanges="orientation|keyboardHidden"

configChanges 属性重点选项

项目含义
mccSIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由三位数字组成,中国为460。此项标识mcc代码发生了改变
mncSIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03。此项标识mnc发生改变
locale设备的本地位置发生了改变,一般指切换了系统语言
keyboard键盘类型发生了改变,例如用户使用了外插键盘
keyboardHidden键盘的可访问性发生了变化,例如用户调出了键盘
screenLayout屏幕布局发生了改变,很可能用户激活了一个显示设备
fontScale系统字体缩放比例发生了改变,例如用户选择了一个新字号
uiMode用户界面模式发生了变化,例如开启夜间模式(API8新添加)
orientation屏幕方向发生了改变,例如旋转手机屏幕
screenSize当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发送变化。此选项较特殊,与编译选项有关,当编译选项的 minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致Activity重启,反之会导致重启(API)重启
smallestScreenSize设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没关系,仅仅表示在实际的物理屏幕的尺寸改变的时候改变,例如用户切换到了外部的显示设备。此选项与screenSize 一样,当编译选项的 minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致Activity重启,反之会导致重启(API)重启

从上表可得,若不在AndroidManifest.xml配置文件中给对应Activity的属性设置 orientation选项,当配置发生改变后会导致Activity重新创建。以上最常用的只有 localekeyboardorientation这三个选项。


(4)Acticity实例验证防止重新创建

【AndroidManifest.xml配置文件】

 <activity 
            android:name=".activity.MainActivity"
            android:configChanges="orientation|screenSize"/>

注意:本项目的minSdkVersion 和 targetSdkVersion 均高于13时,所以在 configChanges 属性中还需额外指定screenSize选项。

public class MainActivityextends AppCompatActivity {
    private String Tag = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test2);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        Log.d(Tag, "onSaveInstanceStateresponse");
    }

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

        Log.d(Tag, "onRestoreInstanceState response");
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        Log.d(Tag, "onConfigurationChanged, newOrientation:" + newConfig.orientation);
    }
}

注意:检测configChanges 属性中的值变化,是在Activity的 onConfigurationChanged(Configuration newConfig) 方法内,相关属性选项信息存储于方法参数newConfig中。

日志打印结果:

这里写图片描述

由上图结果可知,转向横屏时打印Log,参数newConfig.orientation的值为2,代表横屏,重新转向竖屏时,打印1,代表竖屏,只有onConfigurationChanged 方法中的日志打印记录。如此可见,Activity确实没有被重建,也没有调用onSaveInstanceStateonRestoreInstanceState 方法来存储和恢复数据,取而代之的是调用了Activity的onConfigurationChanged 方法,此时若有特殊需求也可在此进行处理。



声明:以上内容总结于任玉刚老师的《Android开发艺术探索》,综合了个人理解,将知识点分成不同的小节,以博客的形式更好的总结出来。这本书真的是温故而知新,感恩~

若有错误,虚心指教~

希望对你们有帮助 :)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值