Android进阶——Activity的生命周期和启动模式

前言

本文会讲述一些Activity在正常情况和异常情况下的生命周期分析,Activity启动模式任务栈的图解以及Activity中IntentFilter过滤器的匹配规则,不会讲述生命周期和启动模式的基本概念。

如果需要关于Activity的生命周期和启动模式基础知识,可以查看如下博客:
Android初体验——活动的状态与周期

Android初体验——活动的启动模式

Activity正常情况下的生命周期分析

1.特定场景下Activity的生命周期分析

  1. 当开启一个新的Activity时,由于是第一次启动,所以回调是onCreate()->onStart()->onResume()。
  2. 当开启一个新的Activity时或者切换到桌面时,原页面的Activity回调是onPause()->onStop(),这里有一个特殊情况就是如果新的Activity采用了透明主题,那么原Activity将不会调用onStop()。
  3. 当用户从新Activity返回到原Activity时,原Activity的回调是onRestart()->onStart()->onResume()。
  4. 当用户从新Activity按Back键回到原Activity时,新Activity的回调是onPause()->onStop()->onDestroy()。

2.关于Activity生命周期方法的一些问题

  1. onStart和onResume、onPause和onStop对用户来说有什么实质的不同呢?

答:从实际使用过程中,用户很难感知onStart和onResume、onPause和onStop之间的区别,其实他们的回调分别代表着不同的意义,onStart和onStop是从Activity是否可见这个角度来回调的,而onResume和onPause是从Activity是否位于前台这个角度回调的,除了这个区别,从实际上其实区别不大。

  1. 假设当前Activity为Activity A,先用户打开一个新的Activity B,那么B的onResume和A的onPause那个先执行呢?

答:旧Activity的onPause先调用,然后新Activity才启动。究其原因我们可以在Android源码里得到解释,在这里我们就只简单介绍以下Activity的启动过程,对源码有兴趣的同学可以自行了解。

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

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

除了用户正常调度Activity的情况,还有一些异常情况也需要进行分析,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。

情况一:资源相关的系统配置发生改变导致Activity被杀死并重建

比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建,其生命周期可以用下图表示:

在这里插入图片描述
当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestory均会被调用,但是由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。该方法的调用时机是在onStop之前,但与onPause没有既定的时序关系,它既有可能在onPause之前调用,也有可能在onPause之后调用。

注:该方法只会出现在Activity被异常终止的情况下,正常情况下系统不会调用该方法

当Activity被重新创建时,系统会调用onRestoreInstanceState,并把Activity销毁时onSaveInstanceState保存的Bundle对象作为参数传递给onRestoreInstanceState和onCreate方法,那么我们就可以取出之前保存好的数据并恢复。

注:onRestoreInstanceState在onStart之后调用

和Activity一样,View都有onSaveInstanceState和onRestoreInstanceState方法,他们不同的实现方法就决定了系统能为每个View自动恢复哪些数据,比如文本框EditText会保存用户输入的信息,ListView保存滚动所在的位置。

关于保存和恢复View中数据,系统的工作流程如下:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window在委托它上面的顶级容器去保存数据。顶级容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。

我们来拿TextView来看一看它的onSaveInstanceState方法保存了哪些数据

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

        // Save state if we are forced to
        final boolean freezesText = getFreezesText();
        boolean hasSelection = false;
        int start = -1;
        int end = -1;

        if (mText != null) {
            start = getSelectionStart();
            end = getSelectionEnd();
            if (start >= 0 || end >= 0) {
                // Or save state if there is a selection
                hasSelection = true;
            }
        }

        if (freezesText || hasSelection) {
            SavedState ss = new SavedState(superState);

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

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

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

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

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

            ss.error = getError();

            if (mEditor != null) {
                ss.editorState = mEditor.saveInstanceState();
            }
            return ss;
        }

        return superState;
    }

可以从源码看出,TextView保存了自己的文本选中状态和文本内容。

onCreate和onRestoreInstanceState恢复数据的不同
onRestoreInstanceState一旦被调用其参数Bundle savedInstanceState一定是有值的,我们并不用额外的判断其是否为空,但是onCreate不行,onCreate在Activity正常启动的情况下,其参数Bundle savedInstanceState为空,根据需要做额外判断。虽然这两方法都可以恢复数据,但是官方推荐采用onRestoreInstanceState恢复数据

情况二:资源内存不足导致优先级低的Activity被杀死

该情况下的数据存储和恢复过程和情况1完全一致。这里我们描述以下Activity的优先级情况:

  1. 前台Activity——正在和用户交互的Activity,优先级最高。(onResume)

  2. 可见但非前台Activity——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户交互。(onPause)

  3. 后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。(onStop)

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

通过实例讲解Android启动模式

  1. standard模式:一个任务栈中可以有多个实例,每个实例也可以属于不同的任务栈。在这种启动模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。比如Activity A启动了 B(B是标准模式),那么B就会进入到A所在的栈中。
    在这里插入图片描述

当我们用ApplicationContext去启动standard模式的Activity时,系统会报错,这是因为standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,因此就会报错。

  1. singleTop模式:如果新Activity 的实例已存在但不是位于栈顶,那么新Activiy仍然会重新重建。举个例子,假设目前栈内的情况为ABCD,其中ABCD为四个Activity, A位于栈底,D位于栈项,这个时候假设要再次启动D,如果D的启动模式为singleTop,那么栈内的情况仍然为ABCD;如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况就变为ABCDD。在这里插入图片描述
  2. singleTask模式:当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例后把A放到栈中。如果存在A所需的任务栈,这时要看A是否在栈中有实例存在,如果有实例存在,那么系统就会把A调用到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并把A压入栈中。顶并调用其

注:如果D所需的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用原则,此时D不会重新创建,系统会把D切换到栈
onNewIntent方法。同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。(singleTask模式的Activity切换到栈顶会导致在它之上的栈内的Activity出栈)

  1. singleInstance模式:这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Actiivty只能单独地位于一个任务栈中。(比如Activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。

在这里插入图片描述
我们来通过一个例子来加深理解!!

我们将SecondActivity和ThirdActivity都设成singleTask并指定它们的taskffinity属性为“com.ryg.task1",注意这个taskAffinity属性的值为字符串,且中间必须含有包名分隔符“”。然后做如下操作,在MainActivity中单击按钮启动SecondActivity,在SecondActivity中单击按钮启动ThirdActivity,在ThirdActivity中单击按钮又启动MainActivity, 最后再在MainActivity中单击按钮启动SecondActivity,现在按back键,然后看到的是哪个Activity?答案是回到桌面。是不是有点摸不到头脑了?没关系,接下来我们分析这个问题。

首先,从理论上分析这个问题,先假设MainActivity 为A, SecondActivity 为B,Application的taskAffinity,而Application默认taskAffinity为包名,所以A的taskAffinity值继承自Application的taskAffinity,而Application默认taskAffinity为包名,所以A的taskAffinity为包名。由于我们在XML中为B和C指定了taskAffinity 和启动模式,所以B和C是singleTask模式且有相同的taskAffinity值“com.ryg.task1”。A启动B的时候,按照singleTask的规则,这个时候需要为B重新创建一个任 务栈“com.ryg task1"。B再启动C,按照singleTask的规则,由于C所需的任务栈(和B为同一-任务栈)已经被B创建,所以无须再创建新的任务栈,这个时候系统只是创建C的实例后将C入栈了。接着C再启动A, A是standard模式,所以系统会为它创建–个新的实例并将到加到启动它的那个Activity的任务栈,由于是C启动了A,所以A会进入C的任务栈中并位于栈顶。这个时候已经有两个任务栈了,一个是名字为包名的任务栈,里面只有A,另一个是名字为“com.ryg.task1"的任务栈,里面的Activity为BCA。接下来,A再启动B,由于B是singleTask, B需要回到任务栈的栈顶,由于栈的工作模式为“后进先出”,B想要回到栈顶,只能是CA出栈。所以,到这里就很好理解了,如果再按back键,B就出栈了,B所在的任务栈已经不存在了,这个时候只能是回到后台任务栈并把A显示出来。注意这个A是后台任务栈的A,不是“com.ryg.taskl”任务栈的A,接着再继续back,就回到桌面了。分析到这里,我们得出一条结论,singleTask 模式的Activity 切换到栈顶会导致在它之上的栈内的Activity 出栈。

我们来画图分析一下

在这里插入图片描述
无法理解前台和后台任务栈区分可以查看如下博客:

《Android开发艺术与探索》中对前台任务栈和后台任务栈的正确理解

Activity的Flags

标记为的作用有很多,有的标记位可以设定Activity的启动模式,还有的标记位可以影响Activity的运行状态,下面我们主要来介绍几个比较常用的标记位。

  1. FLAG_ACTIVITY_NEW_TASK:该标记位的作用是为Activity指定“singleTask”启动模式。

  2. FLAG_ACTIVITY_SINGLE_TOP:该标记为的作用是为Activity指定“singleTop”启动模式。

  3. FLAG_ACTIVITY_CLEAR_TOP:具有此标记位的Activity,当它启动时,在同一任务栈中所有位于它上面的Activity都要出栈。(当Activity具有此标记位,启动它时若实例已存在,那么系统就会调用它的onNewIntent方法)

  4. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有这个标记的Activity不会出现在历史Activity的列表中。(设置后在任务管理器中就看不到当前的应用了,该属性会对整个栈有效)

IntentFilter的匹配规则

IntentFilter的匹配规则详解见作者另一篇博客:

IntentFilter的匹配规则

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值