Activity生命周期和启动模式

本篇博客是笔者看过《Android开发艺术探索》才写的,有些是借鉴了此本书的内容,当有些内容进行了精进。

Activity生命周期和启动模式

1.Activity的生命周期全面分析

1.1 典型情况下的Activty生命周期分析

  典型情况下的Activity的生命周期在基础阶段你已经学过啦,,我们再来回顾一次,这次就整清楚些,把Activity生命周期的每一个过程弄清楚:

Actvvity生命周期图

1.启动了一个Activity,通常是Intent来完成。启动一个Activity首先要执行的回调函数是onCreate(),通常在代码中你需要在此函数中绑定布局,绑定控件,初始化数据等做一些初始化的工作。

2.即将执行Activity的onStart()函数,执行之后Activity已经可见,但是还没有出现在前台,无法与用户进行交互。这个时候通常Activity已经在后台准备好了,但是就差执行onResume()函数出现在前台。

3.即将执行Activity的onResume()函数,执行之后Activity不止可见而且还会出现在前台,可以与用户进行交互啦。

4.由于Activity执行了onResume()函数,所以Activity出现在了前台。也就是Activity处于运行状态。

5.处于运行状态的Activity即将执行onPause()函数,什么情况下促使Activity执行onPause()方法呢?
 [1]启动了一个新的Activity
 [2]返回上一个Activity
 可以理解为当需要其他Activity,当前的Activity必须先把手头的工作暂停下来,再来把当前的界面空间交给下一个需要界面的Activity,而onPause()方法可以看作是一个转接工作的过程,因为屏幕空间只有那么一个,每次只允许一个Activity出现在前台进行工作。通常情况下onPause()函数不会被单独执行,执行完onPause()方法后会继续执行onStop()方法,执行完onStop()方法才真正意味着当前的Activity已经退出前台,存在于后台。

6.Activity即将执行onStop()函数,在“5”中已经说得很清楚了,当Activity要从前台切换至后台的时候会执行,比如:用户点击了返回键,或者用户切换至其他Activity等。

7.当前的Activity即将执行onDestory()函数,代表着这个Activity即将进入生命的终结点,这是Activity生命周期中的最后一次回调生命周期,我们可以在onDestory()函数中,进行一些回收工作和资源的释放工作,比如:广播接收器的注销工作等。

8.执行完onDestory()方法的Activity接下来面对的是被GC回收,宣告生命终结。

9.很少情况下Activity才走“9”,网上一些关于对话框弹出后Activity会走“9”的说法,经过笔者验证,在某个Activity内弹出对话框并没有走“9”,所以网上大部分这样说法的文章要么是没验证,要么直接转载的,这个例子说明,实验出真知,好了,不废话了,那么什么情况下,Activity会走“9”呢?看看下面这位博主才是真的懂得“实验出真知”的人:

http://blog.csdn.net/a872822645/article/details/62217965

10.当用户在其他的Activity或者桌面回切到这个Activity时,这个Activity就会先去执行onRestart()函数,Restart有“重新开始”的意思,然后接下来执行onStart()函数,接着执行onResume()函数进入到运行状态。

11.在“10”中讲的很清楚了。

12.高优先级的应用急需要内存,此时处于低优先级的此应用就会被kill掉。

13.用户返回原Activity。

下面来着重说明一下Activity每个生命周期函数:

onCreate():

 表示Activity正在被创建,这是Activity生命周期的第一个方法。通常我们程序员要在此函数中做初始化的工作,比如:绑定布局,控件,初始化数据等。

onStart():

 表示Activity正在被启动,这时候的Activity已经被创建好了,完全过了准备阶段,但是没有出现在前台,需要执行onResume()函数才可以进入到前台与用户进行交互。

onResume():

 表示Activitiy已经可见了,并且Activity处于运行状态,也就是Activity不止出现在了前台,而且还可以让用户点击,滑动等等操作与它进行交互。

onPause():

 表示Activity正在暂停,大多数情况下,Activity执行完onPause()函数后会继续执行onStop()函数,造成这种函数调用的原因是当前的Activity启动了另外一个Activity或者回切到上一个Activity。还有一种情况就是onPause()函数被单独执行了,并没有附带执行onStop()方法,造成这种函数调用的原因很简单,就是当前Activity里启动了类似于对话框的东东。

onStop():

 表示Activity即将停止,我们程序员应该在此函数中做一些不那么耗时的轻量级回收操作。

onRestart():

 表示Activity正在重新启动。一般情况下,一个存在于后台不可见的Activity变为可见状态,都会去执行onRestart()函数,然后会继续执行onStart()函数,onResume()函数出现在前台并且处于运行状态。

onDestory():

 表示Activity要被销毁了。这是Activity生命中的最后一个阶段,我们可以在onDestory()函数中做一些回收工作和资源释放等,比如:广播接收器的注销等。

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

  对于正常情况下的Activity生命周期,你应该是比较清楚了,通常情况下,Activity会碰到一些异常的情况,如果你没对这些异常作合理的逻辑处理,某些情形下会让用户给你开发的应用给差评。最严重的情况是用户在使用应用写了一篇字数相当之多的文章,某个操作突然让当前的Activity发生异常,如果你不把用户辛苦半天写的文章数据保存下来,这个用户肯定会骂爹的,而且会在应用市场给你的应用来波差评加吐槽,所以异常的逻辑处理是非常重要的,所以接下来,我们来学习一下异常情况下的Activity生命周期,学完后,你将会知道各种异常情况下如何作相应的逻辑处理了。

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

  了解这个问题之前,首先你得对Android资源加载机制有足够的了解:
  Android资源加载机制链接:http://blog.csdn.net/thesingularityisnear/article/details/51581311

  在Activity系统配置发生改变之际,如果你没对Activity做任何处理,Activity就会被销毁并且重新创建,这种情况下Activity会发生以下重建过程:

异常情况下Activity的重建过程(Android开发艺术探索)

  ,可以从图中看出当Activity发生意外的情况的时候,这里的意外指的就是系统配置发生改变,Activity会被销毁,其onPause,OnStop,onDestory函数均会被调用,同时由于Actiivty是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity状态。调用onSaveInstanceState的时机总会发生在onStop之前,至于会不会调用时机发生在onPause方法之前,那就说不定了,这个没有固定的顺序可言,正常情况下一般onSaveInstanceState不会被调用。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Actiivty销毁时onSaveInstanceState方法所保存的Bundle对象作为参数传递给onRestoreInstanceState和onCreate方法。所以我们可以通过onRestoreInstanceState和onCreate方法来判断Actiivty是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复,从时序上来看,onRestoreInstanceState的调用时机发生在onStart之后。

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

关于保存和恢复View层次结构,系统的工作流程是这样的:

  首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window在委托它上面的顶级容器去保存数据。顶级容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一个典型的委托思想,上层委托下层,父容器去委托子元素去处理一件事情,这种思想在Android中有很多应用,比如:View的绘制过程,事件分发等都是采用类似的思想。至于数据恢复过程也是类似的,这样就不再重复介绍了。

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

  首先,Activity有优先级?你肯定怀疑,代码中都没设置过啊!优先级从何而来,其实这里的Activity的优先级是指一个Activity对于用户的重要程度,比如:正在与用户进行交互的Activity那肯定是最重要的。我们可以按照重要程度将Activity分为以下等级:

优先级最高: 与用户正在进行交互的Activity,即前台Activity。

优先级中等:可见但非前台的Activity,比如:一个弹出对话框的Activity,可见但是非前台运行。

优先级最低:完全存在与后台的Activity,比如:执行了onStop。

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

  • 总结:
    上面分析了系统的数据存储和恢复机制,我们知道,当系统配置发生改变之后,Activity会被重新创建,那么有没有办法不重新创建呢?答案是有的,接下来我们就来分析这个问题。系统配置中有很多内容,如果某项内容发生了该变后,我们不想系统重新创建Activity可以给Activity指定configChanges属性。比如我们不想让Actiivty在屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation这个值,如下所示。

    android:configChanges = "orientation"

  如果我们指定多个值,那么就用“|”连接起来即可,下表是configChanges的属性含义表:

configChanges的属性含义表

2.Activity的启动模式全面分析

  Activity的启动模式,你在初学期间一定很熟悉了吧!不管你是否熟悉还是不熟悉,跟随笔者的思路把Activity的启动模式整理一遍:

问题1:Activity为什么需要启动模式?
问题2:Activity的启动模式有哪些?特性如何
问题3:如何给Activity选择合适的启动模式

问题1:Activity为什么需要启动模式?

  我们都知道启动一个Activity后,这个Activity实例就会被放入任务栈中,当点击返回键的时候,位于任务栈顶层的Activity就会被清理出去,当任务栈中不存在任何Activity实例后,系统就回去回收这个任务栈,也就是程序退出了。这只是对任务栈的基本认识,深入学习,笔者会在之后文章中提到。那么问题来了,既然每次启动一个Activity就会把对应的要启动的Activity的实例放入任务栈中,假如这个Activity会被频繁启动,那岂不是会生成很多这个Activity的实例吗?对内存而言这可不是什么好事,明明可以一个Activity实例就可以应付所有的启动需求,为什么要频繁生成新的Activity实例呢?杜绝这种内存的浪费行为,所以Activity的启动模式就被创造出来去解决上面所描述的问题。

问题2:Activity的启动模式有哪些?特性如何

  Activity的启动模式有4种,分别是:standard,singleTop,singleTask和singleInstance。下面一一作介绍:

1.系统默认的启动模式:standard
  标准模式,这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否存在。被创建的实例的生命周期符合典型情况下的Activity的生命周期。在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity的任务栈中。比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的任务栈中。有个注意的地方就是当我们用ApplicationContext 去启动standard模式的Activity就会报错,这是因为standard模式的Actiivty默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,所以这就会出现错误。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候启动Activity实际上以singleTask模式启动的,读者可以自己仔细体会。

2.栈顶复用模式:singleTop
  在这种模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法被回调,通过此方法的参数我们可以取出当前请求的信息。需要注意的是,这个Activity的onCreate,onStart不会被系统调用,因为它并没有发生改变。如果新的Activity已经存在但不是位于栈顶,那么新的Activity仍然会重新重建。举个例子,假设目前栈内的情况为ABCD,其中ABCD为四个Activity,A位于栈低,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为singleTop,那么栈内的情况依然为ABCD;如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况为ABCDD。

3.栈内复用模式:singleTask
  这是一种单例实例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent。具体一点,当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先寻找任务栈中是否已存在Activity A的实例,如果已经存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果Activity A实例不存在,就创建A的实例并把A压入栈中。举几个栗子:

  • 比如目前任务栈S1的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例并将其投入到S2任务栈中。
  • 另外一种情况是,假设D所需的任务栈为S1,其他情况如同上面的例子所示,那么由于S1已经存在,所以系统会直接创建D的实例并将其投入到S1。
  • 如果D所需的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。

  通过以上3个例子,你应该能比较清晰地理解singleTask的含义了。

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

  • 总结
    上面介绍了4种启动模式,这里需要指出一种情况,我们假设目前有2个任务栈,前台任务栈的情况为AB,而后台任务栈的情况为CD,这里假设CD的启动模式均为singleTask。现在请求启动D,那么整个后台任务栈都会被切换到后台,这个时候整个后退列表变成了ABCD。当用户按back键的时候,列表中的Activity会一一出栈,如下图1所示:
图1 任务栈演示图1

  如果不是请求的D而是请求的C,那么情况就不一样了,如下图2所示:

图2 任务栈演示图2

  如何指定活动的启动模式呢?在AndroidManifest.xml文件当注册活动的代码中去指定
比如:我要把MainActivity活动的启动模式指定为singleInstance模式

设置Activity的启动模式代码截图

Activity的Flags

  Activity的Flags有很多,这里笔者就不罗嗦了,直接贴一个链接:

http://www.cnblogs.com/xgjblog/p/3994990.html

3.IntentFilter的匹配规则

  在初学期间,你已经知道Activity的启动分为隐式和显式,那么什么是显式启动一个Activitiy,什么又是隐式启动一个Activity呢?显式启动一个Activity需要明确地指定被启动对象的组件信息,包含包名和类名,显式调用目的性太强,满足组件信息的只有那么一个Activity类而已,而隐式调用需要Intent能够匹配目标的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。IntentFilter中的过滤信息有action,category,data。原则上一个Intent是不应该同时拥有隐式和显式的Activity配置信息,但是如果同时都有的情况下启动的应该是显式的启动。下面是一个过滤规则的示例:

//此处来个截图

  为了匹配过滤列表,需要同时匹配过滤列表的action,category,data信息,否则匹配失败。一个过滤列表中的action,category,data可以有多个,所有的action,category,data分别构成不同类别,同一类别的信息共同约束当前类别的匹配过程。只有一个Intent同时匹配action类别,category类别,data类别才算完全匹配,只有完全匹配才能成功启动目标Activitry。另外一点,一个Activity可以有多个intent-filter规则,组合你应该知道哈,反是有多个action,category,data的都会有多个匹配规则,比如:假设一个Activity的action有3个,category有4个,data有5个,那么这个Activity的匹配规则会有多少个呢?345个咯,是不是很简单呢。下面我们就来分别详细讲讲action,category,data的匹配规则。

3.1 action的匹配规则:

  action是一个字符串,系统预定义一些action,同时我们也可以在应用种定义自己的action。action的匹配规则是Intent中的action必须能够和过滤规则中的action匹配,这里说的是指action的字符串值完全一样。一个过滤规则中可以有多个action,那么只要Intent中的action能够和过滤规则中的任何一个action相同即可匹配成功。另外,action是区分大小写,大小写不同字符串相同的action会匹配失败。

3.2 category的匹配规则:

  category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个categroy相同。换句话说,Intent中如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义的category。当然,Intent可以没有category,如果没有category的话,Intent仍然可以匹配成功的,说到这里你可能会觉得这不科学啊,其实这个匹配规则理解起来很简单,就是高中的集合思想,就是判断Intent中的category构成的集合是否为启动Activity的category过滤规则构成集合的子集,这很简单吧!但是注意与action的匹配规则相区别,action是要求Intent必须有一个action且必须能够和过滤规则中的某个action相同,这不就是交集的意思吗?瞬间你就会懂得了action的匹配规则其实就是集合的交集思想,而category的匹配规则其实就是集合的子集思想。总结来说:对于action的匹配规则是这样的,Intent中的action所构成的集合和Activity过滤规则中action构成的集合有交集的话才能匹配上,而对于category的匹配规则是这样的,Intent中的category所构成的集合是Activity过滤规则中category构成的集合的子集能匹配上。其实很简单action-->交集,categroy-->子集。

3.3 data的匹配规则:

  data的匹配规则和action相似,如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data。在介绍data的匹配规则之前,我们需要先了解一下data的结构:

data的结构:
  data的语法如下所示:

<data android:scheme="string"
      android:host="string"
      android:port="string"
      android:path="string"
      android:pathPattern="string"
      android:pathprefix="string"
      android:mimeType="string"/>

  data由两部分组成,mimeType和URI。mimeType指媒体类型,比如:image/jpeg,audio/mpeg4-generic和vedio/*等,可以表示图片,文本,视频等不同的媒体格式,而URI中包含的数据就比较多了,下面是URI的结构:

<scheme>://<host>:<port>/<path>|<pathprefix>|<pathPattern>

推荐阅读更多精彩内容