读书笔记-艺术探索-Activity的生命周期

1. Activity 的生命周期

1.0 前言

本文总结自任玉刚老师的《Android开发艺术探索》,文章中的【示例】在这里

1.1 Activity的总体介绍

  在Android开发中,正常情况下,除了Window , Dialog , Toast ,我们能见到的界面只有Activity。所以毋庸置疑Activity是很重要的。
  平时开发中对于Activity的生命周期,Flags,IntentFilter的匹配规则,Intent的匹配过程这类的实际应用可能比较少,所以对此还是比较陌生的,但是Activity对要深入了解一个应用程序,优化一个应用程序等等来说是还是非常重要的,现在就来回顾一下:

1.2 Activity的生命周期

1.2.1 典型情况下的生命周期

  典型情况就是正常的情况...不正常的下一小节再说,先上一张所有Android学习者都看过的图:

[图片上传失败...(image-5c19fc-1525574855834)]

  • (1) onCreate : 表示Activity正在被创建,这个方法中通常做一些初始化工作,比如调用setContentView加载布局资源,初始化Activity所需数据等    [示例:针对一个特定的Activity,第一次启动回调如下:onCreate -> onStart -> onResume]

  • (2) onStart : 表示Activity正在被启动,即将开始,这是Activity已经可见了,但是还没有出现在前台(还在后台),还无法和用户交互,这个时候可以理解为Activity已经显示出来了,但是我们还看不到。

  • (3) onResume : 表示Activity已经可见了,并且出现在前台并开始活动,接下来Activity已经完全运行了。

  • (4) onPause : 表示Activity正在停止,正常情况下,紧接着onStop就会被调用,特殊情况下,如果这个时候快速地回到当前Activity,那么onResume会被调用(极端情况,用户很难复现该情况)。此时可以做一些储存数量、停止动画等工作,但是不能太耗时,因为这会影响新Activity的显示,onPause必须先执行完,新Activity的onResume才会执行。

  • (5) onStop : 表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。  [示例:用户打开新的Activity/切换到桌面/关闭屏幕时:onPause -> onStop (特殊情况:如果新Activity采用了透明主题或是一个dialog,那么当前Activity不会回调onStop),再次回到原Activity时,onRestart -> onStart -> onResume]

  • (6) onRestart : 表示Activity正在重新启动,一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。

  • (7) onDestory : 表示Activity即将被销毁,这是Activity生命周期最后一个回调,在这里可以做一些回收工作和最终的资源释放。[示例:用户按back键回退时,回调onPause -> onStop -> onDestory]

1.2.2 一个问题:假设当前Activity为A,如果用户这时候打开一个新Activity B, 那么B的onResume和A地onPause哪个先执行?

  Activity 的启动过程的源码相当复杂,简概来说是:启动Activity的请求会由Instrumentation来处理,然后通过Binder向ActivityManagerService(AMS)发请求,AMS内部维护着一个ActivityStack并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。(看不懂)

  在ActivityStack中的resumeTopActivityInnerLocked方法中,有这么一段代码:

//ActivityStack.resumeTopActivityInnerLocked()
// If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity
// to be paused, while at the same time resuming the new resume activity only if the
// previous activity can't go into Pip since we want to give Pip activities a chance to
// enter Pip before resuming the next activity.
final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0
        && !lastResumedCanPip;

boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, false);
if (mResumedActivity != null) {
    if (DEBUG_STATES) Slog.d(TAG_STATES,
            "resumeTopActivityLocked: Pausing " + mResumedActivity);
    pausing |= startPausingLocked(userLeaving, false, next, false);
}

  上面代码的意思就是在新Activity启动之前,栈顶的Activity需要先onPause后,新Activity才能启动。最终在ActivityStackSupervisor中的realStartActivityLocked方法中调用如下代码:

//ActivityStackSupervisor.realStartActivityLocked()
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
    System.identityHashCode(r), r.info,
    // TODO: Have this take the merged configuration instead of separate global and
    // override configs.
    mergedConfiguration.getGlobalConfiguration(),
    mergedConfiguration.getOverrideConfiguration(), r.compat,
    r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
    r.persistentState, results, newIntents, !andResume,
    mService.isNextTransitionForward(), profilerInfo);

  这个app.thread的类型是IApplicationThread,而IApplicationThread的具体实现是ActivityThread中的ApplicationThread。所以这段代码实际调到了ActivityThread中,即ApplicationThread的scheduleLaunchActivity方法,而scheduleLaunchActivity方法最终会完成新Activity的onCreate、onStart、onResume的调用过程。因此可以得出结论:是旧Activiy先onPause,然后新Activity再启动(onCreate, onStart, onResume),旧activity再调用onStop

  ActivityStackSupervisor.scheduleLaunchActivity最终会调用如下代码,从而完成onCreate、onStart、onResume的调用过程:

//ActivityThread.handleLaunchActivity()
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // 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);

        // Initialize before creating the activity
        WindowManagerGlobal.initialize();

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            reportSizeConfigurations(r);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        ......
}

   从另外一个角度上说,通过分析这个问题,我们知道onPause和onStop都不能执行耗时的操作,尤其是onPause,这也意味着,我们尽量应当在onStop中做操作,从而使得新的Activity尽快显示出来到前台。

  【示例:当启动一个新Activity的时候,是否旧Activity的onPause先执行,再启动新的Activity】。

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

1.2.3.1 情况1 :资源相关的资源配置发生改变导致Activity被杀死并重新创建

  简单说明一下资源加载机制:用图片来说,当我们把一张图片方法drawable目录后,就可以通过Resources去获取这章图片。同时为了兼容不同的设备,我们可能还需要在其他一些目录防止不同的照片,不如drawable-mdpi、drawable=hdpi、drawable-land等。这样,当应用程序启动时,系统会根据当前设备的情况去加载合适的Resources资源,比如说横屏手机和竖屏手机会拿到两张不同的图片(设定了landscape或者portrait状态下的图片)。比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建,当然我们也可以阻止系统重新创建Activity。

  在默认情况下,如果Activity不做特殊处理,那么当系统配置发生改变后,Activity就会被销毁并重新创建,其生命周期如图:

[图片上传失败...(image-7d3374-1525574855834)]

  当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestory均会被调用,同时由于Activity在异常情况下终止,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机实在onStop之前,他和onPause没有既定的时序关系。需要注意的是,这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时的onSaveInstanceState方法保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果是,那就可以取出之前保存的数据并恢复(在Activity重建的过程中,系统自动为我们做了一定的重建工作,每个View都有onSaveInstanceState和onRestoreInstanceState方法,看一下具体实现就知道系统为每个View恢复哪些数据)。

  保存和恢复View层次结构的流程:Activity被意外终止 -> Activity调用onSaveInstanceState保存数据 -> Activity委托Window保存数据 -> Window委托它上面的顶级容器(ViewGroup,一般是DecorView)保存数据 -> 顶层容器再一一通知它的子元素来保存数据 (典型的委托思想) 。

  下面拿TextView举例分析一下它到底保存了哪些数据:

//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();     //保存文本选中状态(Text selected state)
            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();     //保存文本内容(Text content)
                }
            }

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

  简单理解:系统只有在Activity异常终止的时候才会调用onSaveInstanceStateonRestoreInstanceState来存储和恢复数据,其他情况不会触发这个过程。
  【示例:Activity异常情况下重新创建(屏幕旋转)时,数据的储存与恢复】

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

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

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

  上文说的都是当系统配置发生改变后,Activity会被重新创建,下面讲到的是不重新创建的方法:给Activity指定configChanges属性,比如不想让Activity在屏幕旋转时重新创建:

    android:configChanges = "orientation"
项目(常用的) 含义
locale 设备所在地区发生变化,一般指切换了系统语言。
orientation 设备旋转,横向显示和竖向显示模式切换。
keyboardHidden 键盘的可访问性发生了改变,比如用户调出了键盘。
screenSize 当屏幕的尺寸信息发生了改变。当旋转设备屏幕时,屏幕尺寸会发生变化。
smallestScreenSize 设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没关系。

  当指定android:configChanges = "orientation|screenSize "时屏幕旋转,Activity不会被重新创建,也没有调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,取而代之的是系统调用了Activity的onConfigurationChanged方法, 这时候就可以做一些自己的特殊处理了。
  【示例:Activity指定configChanges属性时屏幕旋转】

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容