Android 基础 -- Activity 生命周期实践总结

Activity / Fragment 的生命周期是每个 Android 开发者最最基础的知识点。所以特别有必要自己整理一番。总看别人博客和书上的死知识,还不如自己动手实践,然后输出要印象深刻,理解透彻。

Activity 生命周期

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

  1. 针对一个特定的 Activity ,第一次启动,回调如下:onCreate —-> onStart —-> onResume

Log 日志
D/KLog: (MainActivity.java:19) onCreate
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume

  1. 切换回到桌面的时候,回调如下:onPause —-> onStop

Log 日志
D/KLog: (MainActivity.java:50) onPause
D/KLog: (MainActivity.java:68) onStop

  1. Back 键退出的话,最后会 onDestroy

  2. 启动一个新的 Activity , 我们看看两个 Activity 的生命周期:

Log 日志
D/KLog: (MainActivity.java:50) onPause
D/KLog: (OtherActivity.java:25) onCreate
D/KLog: (OtherActivity.java:31) onStart
D/KLog: (OtherActivity.java:49) onResume
D/KLog: (MainActivity.java:68) onStop
可以得到顺序是:onPause(A) —-> onCreate(B) —-> onStart(B) —-> onResume(B) —-> onStop(A)

  1. 这个时候我们 Back 回到第一个 Activity 时发生的回调:

Log 日志
D/KLog: (OtherActivity.java:37) onPause
D/KLog: (MainActivity.java:56) onRestart
D/KLog: (MainActivity.java:44) onStart
D/KLog: (MainActivity.java:62) onResume
D/KLog: (OtherActivity.java:55) onStop
D/KLog: (OtherActivity.java:61) onDestroy
可以得到顺序是: onPause(B) —-> onRestart(A) —-> onStart(A) —-> onResume(A) —-> onStop(B) —-> onDestroy(B)

  1. 如果我在 B Activity 中的 onCreate 回调中直接 finish()

Log 日志
D/KLog: (MainActivity.java:50) onPause
D/KLog: (OtherActivity.java:25) onCreate
D/KLog: (MainActivity.java:62) onResume
D/KLog: (OtherActivity.java:62) onDestroy
我们发现 B Activity 只会执行 onCreateonDestroy

  1. 接下来我们启动一个特殊的 Activity (半透明或者对话框样式)到关闭它:

Log 日志
D/MainActivity: onPause
D/DialogActivity: onCreate
D/DialogActivity: onStart
D/DialogActivity: onResume
D/DialogActivity: onPause
D/MainActivity: onResume
D/DialogActivity: onStop
D/DialogActivity: onDestroy

在正常使用应用的过程中,前台 Activity 有时会被其他导致 Activity 暂停的可视组件阻挡。 例如,当半透明 Activity 打开时(比如对话框样式中的 Activity ),上一个 Activity 会暂停。 只要 Activity 仍然部分可见但目前又未处于焦点之中,它会一直暂停。

<div class="tip">
问题:如果是启动一个普通的 Dialog ,Activity 的会执行 onPause 吗?
</div>

答案是不会的,我们可以这样理解,普通的 dialog 是依附在本 Activity 的,相当于是一个整体,所以它本身的焦点也是属于 Activity 的。故不会调用 onPause 。

异常状态下的生命周期

onSaveInstanceState 方法只会出现在 Activity 被异常终止的情况下,它的调用时机是在 onStop 之前,它和 onPause 方法没有既定的时序关系,可能在它之前,也可能在它之后。当 Activity 被重新创建的时候, onRestoreInstanceState 会被回调,它的调用时机是 onStart 之后。
系统只会在 Activity 即将被销毁并且有机会重新显示的情况下才会去调用 onSaveInstanceState 方法。
Activity 在异常情况下需要重新创建时,系统会默认为我们保存当前 Activity 的视图结构,并且在 Activity 重启后为我们恢复这些数据,比如文本框中用户输入的数据、listview 滚动的位置等,这些 view 相关的状态系统都会默认为我们恢复。具体针对某一个 view 系统能为我们恢复哪些数据可以查看 view 的源码中的 onSaveInstanceStateonRestoreInstanceState 方法。

Demo 代码:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    KLog.d(getClass().getSimpleName(),"onSaveInstanceState");
    outState.putString(STATE, "test");
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    KLog.d(getClass().getSimpleName(),"[onRestoreInstanceState]: " + savedInstanceState.getString(STATE));
}

为了方便我们旋转下屏幕来异常终止 Activity :

Log 日志
D/MainActivity: onPause
D/MainActivity: onSaveInstanceState
D/MainActivity: onStop
D/MainActivity: onDestroy
D/MainActivity: onCreate
D/MainActivity: onStart
D/MainActivity: [onRestoreInstanceState]: test
D/MainActivity: onResume

摘自 Android 开发者艺术探索 一书:

关于保存和恢复 View 的层次结构,系统工作流程是: Activity 异常终止, Activity 调用 onSaveInstanceState 去保存数据,然后 Activity 会委托 Windows 去保存数据,接着 Window 再委托它上面的顶层容器去保存数据。顶层容器是一个 ViewGroup ,一般来说它很可能是 DectorView ,最后顶层容器再去通知它的子元素保存数据。(这是一种委托思想,上层委托下层,父容器委托子元素去处理事情,如 View 的绘制过程,事件分发都是采用类似的思想)

Fragment

普通的 Fragment

从图可以看出,Fragment 生命周期大部分的状态与 Activity 相似,特殊的是

  • onAttach() —— 当 Fragment 被加入到 Activity 时调用(在这个方法中可以获得所在的 Activity ).
  • onCreateView() —— 当 Activity 要得到 Fragment 的 Layout 时,调用此方法,Fragment 在其中创建自己的 Layout (界面)。
  • onActivityCreated() —— 当 Activity 的 onCreated() 方法返回后调用此方法
  • onDestroyView() —— 当 Fragment 中的视图被移除的时候,调用这个方法。
  • onDetach() —— 当 Fragment 和 Activity 分离的时候,调用这个方法。

ViewPager 中的 Fragment

我们开发中经常会用到 ViewPager + Fragment 组合的形式来完成特定的需求。本身 Fragment 生命周期就比 Activity 要复杂很多,当它在 ViewPager 中又是怎么回调呢?

我先给 ViewPager 加入三个 Fragment:

viewPager = (ViewPager) findViewById(R.id.viewpager);
fragmentList.add(new OneTextFragment());
fragmentList.add(new TwoTextFragment());
fragmentList.add(new ThreeTextFragment());
viewPager.setAdapter(new FtAdapter(getSupportFragmentManager(), fragmentList));

启动这个 Activity 的日志如下:

D/ViewPagerHostActivity: onCreate
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/OneTextFragment: onAttach
D/OneTextFragment: onCreate
D/TwoTextFragment: onAttach
D/TwoTextFragment: onCreate
D/TwoTextFragment: onActivityCreated
D/OneTextFragment: onActivityCreated
D/OneTextFragment: onStart
D/OneTextFragment: onResume
D/TwoTextFragment: onStart
D/TwoTextFragment: onResume

我们发现启动后,有两个 Fragment(one,two) 被创建,为什么会创建两个?这个问题我们留着后面说。

当 Activity 进入后台:

D/ViewPagerHostActivity: onPause
D/ViewPagerHostActivity: onSaveInstanceState
D/TwoTextFragment: onStop
D/OneTextFragment: onStop
D/ViewPagerHostActivity: onStop

当 Activity 返回前台:

D/ViewPagerHostActivity: onRestart
D/TwoTextFragment: onStart
D/OneTextFragment: onStart
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/TwoTextFragment: onResume
D/OneTextFragment: onResume

当 Activity 销毁:

D/ViewPagerHostActivity: onPause
D/TwoTextFragment: onStop
D/OneTextFragment: onStop
D/ViewPagerHostActivity: onStop
D/TwoTextFragment: onDestroyView
D/TwoTextFragment: onDestroy
D/TwoTextFragment: onDetach
D/OneTextFragment: onDestroyView
D/OneTextFragment: onDestroy
D/OneTextFragment: onDetach
D/ViewPagerHostActivity: onDestroy

滑动一页:

D/ThreeTextFragment: onAttach
D/ThreeTextFragment: onCreate
D/ThreeTextFragment: onActivityCreated
D/ThreeTextFragment: onStart
D/ThreeTextFragment: onResume

当前显示的页面是 TwoTextFragment 然后 ThreeTextFragment 也已经创建好了。

再滑动一页:

D/OneTextFragment: onStop
D/OneTextFragment: onDestroyView

当前显示的页面是 ThreeTextFragment ,我们发现 OneTextFragment 已经销毁。

我们可以得到默认状态下的 ViewPager 会缓存 1 个 Fragment,相当于有两个 Fragment 是创建状态。
当我们增加一行代码:

viewPager.setOffscreenPageLimit(2);

当 Activity 创建时的生命周期:

D/ViewPagerHostActivity: onCreate
D/ViewPagerHostActivity: onStart
D/ViewPagerHostActivity: onResume
D/OneTextFragment: onAttach
D/OneTextFragment: onCreate
D/TwoTextFragment: onAttach
D/TwoTextFragment: onCreate
D/ThreeTextFragment: onAttach
D/ThreeTextFragment: onCreate
D/TwoTextFragment: onActivityCreated
D/OneTextFragment: onActivityCreated
D/OneTextFragment: onStart
D/OneTextFragment: onResume
D/TwoTextFragment: onStart
D/TwoTextFragment: onResume
D/ThreeTextFragment: onStart
D/ThreeTextFragment: onResume

三个 Fragment 都创建好了,并且左右切换不会走任何生命周期(虽然是废话)。

setOffscreenPageLimit 源码:

public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
                DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
        populate();
    }
}

通过源码我们可以知道,ViewPager 的缓存的默认值和最小值是 1

启动模式

Activity 的四种启动模式

  • Standard:标准模式,一调用 startActivity() 方法就会产生一个新的实例。

  • SingleTop: 来了 intent, 每次都创建新的实例,仅一个例外:当栈顶的activity 恰恰就是该activity的实例(即需要创建的实例)时,不再创建新实例。这解决了栈顶复用问题

  • SingleTask: 来了 intent 后,检查栈中是否存在该 activity的实例,如果存在就把 intent 发送给它,否则就创建一个新的该activity的实例,放入一个新的 task 栈的栈底。肯定位于一个 task 的栈底,而且栈中只能有它一个该 activity 实例,但允许其他 activity 加入该栈。解决了在一个 task 中共享一个 activity。

  • SingleInstance: 这个跟 SingleTask 基本上是一样,只有一个区别:在这个模式下的Activity实例所处的task中,只能有这个activity实例,不能有其他的实例。一旦该模式的activity的实例已经存在于某个栈中,任何应用在激活该activity时都会重用该栈中的实例,解决了多个task共享一个 activity。

这些启动模式可以在功能清单文件 AndroidManifest.xml 中进行设置,中的 launchMode 属性。

具体实践

  • SingleTop 栈顶复用模式
<activity android:name=".dLaunchChapter.OneActivity"
    android:launchMode="singleTop"/>

我们在清单里先给 OneActivity 启动模式设置为 singleTop ,然后代码启动活动的顺序为 One --> One,反复点击多次,然后我们看看栈内情况。

adb 命令 :dumpsys activity | grep -i run

root@vbox86p:/ # dumpsys activity | grep -i run
    Running activities (most recent first):
        Run #1: ActivityRecord{23e3b5b u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t595}
        Run #0: ActivityRecord{1a2c6f3 u0 com.hugo.demo.activitydemo/.LaunchActivity t595}

该启动模式下并且 OneActivity 在栈顶所以不会创建新的实例,其生命周期调用 onPause —-> onNewIntent —-> onResume

  • SingleTask 栈内复用模式

修改 OneActivity 的启动模式为 SingleTask ,然后我们代码启动的顺序为 One —-> Two —-> One,接了下看看栈内情况:

One —-> Two 我们记录下当前的 Activity 栈:

Running activities (most recent first):
        Run #2: ActivityRecord{1e8701b7 u0 com.hugo.demo.activitydemo/.dLaunchChapter.TwoActivity t632}
        Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}

接下来我们执行 Two —-> One ,当前 Activity 栈信息:

Running activities (most recent first):
       Run #1: ActivityRecord{39e11719 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t632}

当 TwoActivity 启动 OneActivity(SingleTask) 的时候,堆栈信息里只剩下了 OneActivity 并且和第一次内存信息 39e11719 相同,所以确实是复用了没有新建实例,接下来我们看看 Log 日志,再验证下我们的猜想,看看具体走了哪些生命周期:

D/TwoActivity: onPause
D/OneActivity: onNewIntent
D/OneActivity: onRestart
D/OneActivity: onStart
D/OneActivity: onResume
D/TwoActivity: onStop
D/TwoActivity: onDestroy

果然此时 OneActivity 没有重新创建,并且系统把它切换到了栈顶并调用 onNewIntent 方法,同时我们发现, SingleTask 默认具有 clearTop 效果,导致 TwoActivity 出栈。

<div class="tip">
我们代码指定 OneActivity 的栈,效果还是一样的吗?
</div>

带着问题我们修改下代码,增加一行:

<activity
    android:name=".dLaunchChapter.OneActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.hugo.demo.singleTask"/>

然后我们按照刚刚顺序 One —-> Two —-> One 启动 Activity ,现在的栈内信息还会更上次一样吗?

Running activities (most recent first):
        Run #2: ActivityRecord{1bc18519 u0 com.hugo.demo.activitydemo/.dLaunchChapter.OneActivity t636}
        Run #1: ActivityRecord{36e5e368 u0 com.hugo.demo.activitydemo/.dLaunchChapter.TwoActivity t635}

我们发现,虽然是复用了 OneActivity 而且移到了栈顶,但是并没有销毁 TwoActivity 。

原因在于 singleTask 模式受 taskAffinity 影响,TwoActivity 和 OneActivity 所在的 Activity 栈不同。

总结,启动一个 lauchMode 为 singleTask 的 Activity 时有两种情况:

  1. 若系统中存在相同 taskAffinity 值的任务栈 (tacks1 )时,会把 task1 从后台调到前台,若实例存在则干掉其上面的所有 Activity 并调用 onNewInstance 方法重用,没有该实例则新建一个。
  2. 否则,新建一个任务栈,并以此 Activity 作为 root 。
  • SingleInstance 单实例模式

这是一种加强的 singleTask 模式,它除了具有 singleTask 模式的所有特性以外,还加强了一点,就是具有此模式的 Activity 只能单独地位于任务栈。

<div class="tip">
好了,关于生命周期和启动模式实践+知识点整理已经完成啦,
非常推荐大家下载源码自己运行看看 Log 日志,查看源码:Github
,这样可以对这篇文章知识更加深刻。
</div>

参考文档

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

推荐阅读更多精彩内容