Android-生命周期和启动模式

Android的生命周期

典型情况下的生命周期

如上图所示,正常情况下,Activity会经历以下的生命周期

1. onCreate : 与onDestroy配对,表示Activity正在被创建,这是生命周期的第一个方法。在这个方法中可以做一些初始化的工作(加载布局资源、初始化Activity所需要的数据等),耗时的工作在异步线程上完成。

2. onRestart : 表示Activity正在重新启动。一般情况下,在当前Activity从不可见重新变为可见的状态时onRestart就会被调用。这种情形一般是由于用户的行为所导致的,比如用户按下Home键切换到桌面或者打开了一个新的Activity(这时当前Activity会暂停,也就是onPause和onStop被执行),接着用户有回到了这个Activity,就会出现这种情况。

3. onStart : 与onStop配对,表示Activity正在被启动,并且即将开始。但是这个时候要注意它与onResume的区别。两者都表示Activity可见,但是onStart时Activity还正在加载其他内容,正在向我们展示,用户还无法看到,即无法交互。

4. onResume : 与onPause配对,表示Activity已经创建完成,并且可以开始活动了,这个时候用户已经可以看到界面了,并且即将与用户交互(完成该周期之后便可以响应用户的交互事件了)。

5. onPause : 与onResume配对,表示Activity正在暂停,正常情况下,onStop接着就会被调用。在特殊情况下,如果这个时候用户快速地再回到当前的Activity,那么onResume会被调用(极端情况)。一般来说,在这个生命周期状态下,可以做一些存储数据、停止动画的工作,但是不能太耗时,如果是由于启动新的Activity而唤醒的该状态,那会影响到新Activity的显示,原因是onPause必须执行完,新的Activity的onResume才会执行。

6. onStop : 与onStart配对,表示Activity即将停止,可以做一些稍微重量级的回收工作,同样也不能太耗时(可以比onPause稍微好一点)。

7. onDestory : 与onCreate配对,表示Activity即将被销毁,这是Activity生命周期的最后一个回调,我们可以做一些回收工作和最终的资源释放。

这里提出两个问题:
1.onStart和onResume、onPause和onStop从描述上来看差不多,对我们来说有什么实质上的不同呢?
2.假设当前Activity为A,如果这时用户打开了一个新的活动B,那么B的onResume和A的onPause谁先执行呢?

别看妹子啦,先看第一个问题,从实际的使用过程中来说,onStart和onResume、onPause和onStop看起来确实差不多。但Android为什么要提供看似重复的接口呢?根据上面的分析我们可以知道,这两对回调具有不同的意义,onStart和onStop是根据应用是否可见来进行回调的 ,onResume和onPause是根据应用是否位于前台来进行回调的,除此之外,无其他明显区别。

第二个问题,我们可以从Android源码里得到解释,A的onPause执行后B的onResume才会被调用。从另一个角度来说Android的官方文档中对onPause有这样一句解释:不能在onPause里进行重量级操作,因为必须在onPause执行过后,新的Activity才能Resume。

异常情况下的生命周期

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

当我们旋转屏幕过后,可以看到如下的log:

10-23 22:50:56.032 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onCreate
10-23 22:50:56.036 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onStart
10-23 22:50:56.040 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onResume
10-23 22:51:05.456 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onPause
10-23 22:51:05.460 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onSaveInstanceState
10-23 22:51:05.460 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onStop
10-23 22:51:05.460 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onDestroy
10-23 22:51:05.484 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onCreate
10-23 22:51:05.496 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onStart
10-23 22:51:05.496 1613-1613/com.hugo.demo.activitydemo D/MainActivity: [onRestoreInstanceState]: test
10-23 22:51:05.496 1613-1613/com.hugo.demo.activitydemo D/MainActivity: onResume

onSaveInstanceState里面保存的test,确实在重新创建活动的时候在onRestoreInstanceState被还原出来了。

Android开发艺术探索上有这样一句话:

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

Fragment的生命周期

普通的Fragment

Activity的生命周期 和 Fragment的生命周期的对比

从图中可以看出fragment的生命周期大部分和activity一致,不同的是:

  • onAttach() :当Fragment 和 Activity产生关联时调用.
  • onCreateView():为Fragment加载布局的时候调用
  • onActivityCreated():当 Activity 的 onCreated() 方法返回后调用此方法
  • onDestroyView():当 Fragment 中的视图被移除的时候,调用这个方法。
  • onDetach():当 Fragment 和 Activity 分离的时候,调用这个方法。

除了上面的这些回调函数,fragment还有三个回调是值得我们注意的:

  • onViewCreated():它在onCreateView()执行之后立马被调用,但它执行的时候,之前保存的状态还未被被恢复到了视图
  • onSaveInstanceState():可在此保存fragment的一些数据,与activity不同的是,它可能在onDestroy之前的任何时间调用
  • onViewStateRestored():在onActivityCreated()之后,onStart()之前被调用,以前在onSaveInstanceState()里保存的数据可以在这里还原。

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后,出现如下log:

10-23 23:17:19.332 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onCreate
10-23 23:17:19.388 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStart
10-23 23:17:19.392 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onResume
10-23 23:17:19.404 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onAttach
10-23 23:17:19.404 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onCreate
10-23 23:17:19.408 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onActivityCreated
10-23 23:17:19.408 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onStart
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onResume
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onAttach
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onCreate
10-23 23:17:19.412 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onActivityCreated
10-23 23:17:19.416 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onStart
10-23 23:17:19.416 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onResume

当活动进入到后台的时候:

10-23 23:19:37.536 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onPause
10-23 23:19:38.308 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onSaveInstanceState
10-23 23:19:38.308 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onStop
10-23 23:19:38.312 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onStop
10-23 23:19:38.312 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStop

当活动返回前台的时候:

10-23 23:28:19.568 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onRestart
10-23 23:28:19.568 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onStart
10-23 23:28:19.568 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onStart
10-23 23:28:19.572 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStart
10-23 23:28:19.572 1613-1613/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onResume
10-23 23:28:19.576 1613-1613/com.hugo.demo.activitydemo D/OneTextFragment: onResume
10-23 23:28:19.576 1613-1613/com.hugo.demo.activitydemo D/TwoTextFragment: onResume

当我滑动一页的时候:

10-23 23:30:26.560 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onAttach
10-23 23:30:26.564 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onCreate
10-23 23:30:26.568 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onActivityCreated
10-23 23:30:26.568 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onStart
10-23 23:30:26.572 30297-30297/com.hugo.demo.activitydemo D/ThreeTextFragment: onResume

可以看到在fragment进行滑动切换的时候,对下一个fragment进行了预加载。

再滑动一页:

10-23 23:32:29.612 30297-30297/com.hugo.demo.activitydemo D/OneTextFragment: onStop
10-23 23:32:29.612 30297-30297/com.hugo.demo.activitydemo D/OneTextFragment: onDestroyView

这个时候第一个fragment已经被销毁了。

当我们增加一行代码:viewPager.setOffscreenPageLimit(int limit)

10-23 23:38:47.236 4139-4139/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onCreate
10-23 23:38:47.272 4139-4139/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onStart
10-23 23:38:47.276 4139-4139/com.hugo.demo.activitydemo D/ViewPagerHostActivity: onResume
10-23 23:38:47.280 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onAttach
10-23 23:38:47.284 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onCreate
10-23 23:38:47.284 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onActivityCreated
10-23 23:38:47.288 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onStart
10-23 23:38:47.288 4139-4139/com.hugo.demo.activitydemo D/OneTextFragment: onResume
10-23 23:38:47.292 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onAttach
10-23 23:38:47.292 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onCreate
10-23 23:38:47.292 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onActivityCreated
10-23 23:38:47.296 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onAttach
10-23 23:38:47.296 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onCreate
10-23 23:38:47.300 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onActivityCreated
10-23 23:38:47.300 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onStart
10-23 23:38:47.304 4139-4139/com.hugo.demo.activitydemo D/TwoTextFragment: onResume
10-23 23:38:47.304 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onStart
10-23 23:38:47.308 4139-4139/com.hugo.demo.activitydemo D/ThreeTextFragment: onResume

可以看到除了本来要显示的那个fragment外,还有其的2个fragment也被创建了出来。所以说我们通过viewPager.setOffscreenPageLimit(int limit)可以设置缓存fragment的个数。

Android的启动模式

Activity 的四种启动模式

  • standard:标准模式,每次启动一个Activity都会产生一个新的实例,不论这个实例是否存在。
  • singleTop: 栈顶复用模式,在这种情况下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被调用,通过此方法我们可以取出当前请求的信息。
  • singleTask:栈内复用模式,这是一种单例模式,在这种模式下,只要有一个activity在一个栈中存在,那么多次启动这个activity都不会创建新的实例,和singleTop一样,系统也会回调其onNewIntent方法。注意:由于singleTask默认具有clearTop的效果,所以会导致所有在该活动上的其他activity全部出栈。如现在栈内的情况是ABCD,如果B以singleTask模式启动,启动B后,B上面的所有活动都会出栈,最后栈内的情况是AB。
  • singleInstance:单实例模式,这是一种加强的singleTask实例,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的activity只能单独的位于一个任务栈中。

这些启动模式可以在功能清单AndroidManifest.xml 中设置launchMode属性。

实际操作

  • singleTop:我们在清单里设置oneActivity的launchMode为singleTop,然后代码里设置活动的启动顺序为one-->one,反复点击多次,查看活动栈里的情况如下。
    执行adb命令:adb shell dumpsys activity activitys
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}

我们可以看到活动栈中只有一个one,说明oneActivity并没有被重新创建

  • singleTask:如果我们将oneActivity的launchMode设置为singleTask,然后在代码里设置活动的启动顺序为one->Two->one。
    one -> Two 我们记录下当前activity的栈
    我们记录下当前的 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}

可以看见,oneActivity上面的TwoActivity被出栈了,而oneActivity和上次内存信息相同,说明确实是复用了,没有创建新的实例。

TaskAffinity-任务相关性和allowTaskReparenting

这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需的任务栈的名字为应用的包名。当然,我们可以为每个activity单独指定TaskAffinity属性,这个属性必须和包名不一致,否则相当于没有指定。TaskAffinity属性主要和singleTask或allowTaskReparenting属性配对使用,在其他情况下没有意义。
举例:当应用A启动了应用B的一个活动C,然后按home键回到桌面,再在这个时候打开应用B,如果活动C的allowTaskReparenting属性值为true的话,那么当B被启动的时候,活动C会从应用A的任务栈移到应用B的任务栈中。可以这么理解,由于A启动了C,那么C肯定是在A的任务栈中的,但C属于B应用,那么C的TaskAffinity的值肯定不会和A的任务栈相同(因为包名不同)。所以当B被启动过后,B会创建自己的任务栈。系统在这个时候发现C所需要的任务栈已经被创建了,所以就把C从A的任务栈移到了B的任务栈。
提问:现在我们有3个活动,我们将oneActivity的launchMode属性设置为standard,twoActivity和threeActivity的launchMode属性设置为singleTask,并指定他们的taskAffinity属性为:"com.ryg.task1",注意taskAffinity属性的值为字符串。然后做如下操作。在oneActivity里面启动twoActivity,在twoActivity里面启动threeActivity,在threeActivity里面启动oneActivity,最后在oneActivity里面启动twoActivity,那现在按back键,显示的是哪个activity?
答案是返回到oneActivity。如此时再次按back键,将返回到桌面。

参考文章及Demo源代码

Android开发艺术探索第一章
Android 基础 -- 生命周期和启动模式实践总结
查看源码:Github,源代码来自@谢三弟

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

推荐阅读更多精彩内容