第一章 Activity 的生命周期和启动模式.md
第一章 Activity 的生命周期和启动模式
1.1 Activity 生命周期
1.1.1 正常状态下的生命周期
这里正常状态下的生命周期指因用户的操作引起的生命周期改变。具体生命周期可以参考下图:

有几种情况需要说明:
- 针对一个特定的 Activity ,第一次启动,其回调如下:
onCreate -> onStart -> onResume - 当用户打开新的 Activity 或切换到桌面的时候,回调如下:
onPause -> onStop,如果新的 Activity采用透明主题,则原 Activity 的onStop不会回调 - 当用户再次回到原 Activity 时,回调如下:
onRestart -> onStart -> onResume - 当用户按下 back 键时,回调如下:
onPause -> onStop -> onDestroy - 当 Activity 被系统回收后再次打开,生命周期回调过程和 1 一样,但只是方法一样,过程可能不一样
- 就 Activity 整个生命周期来说,
onCreate和onDestroy是配对的;就 Activity 是否可见来说,onStart和onStop是配对的;就 Activity 是否在前台来说,onResume和onPause是配对的
有两个问题需要回答:
onStart和onResume、onPause和onStop对我们来说有什么实质的不同?- 假设当前处于 Activity A,现在用户打开一个 Activity B,那么请问 A 的
onPause和 B 的onResumt哪一个先执行?
- 对于问题一,在正常的生命周期中其实没有很大的不同,只不过这两对生命周期表示的意义不同,
onStart和onStop是从是否可见角度,onResume和onPause是从 Activity 是否位于前台的角度,在某些时候这些生命周期有可能会有区别 - 对于问题二,是 A 的
onPause先执行,对于为什么要进入到源码来分析,后面会涉及
1.1.2 异常状态下的生命周期
-
资源相关配置发生改变导致 Activity 重建
具体可以看下图:

在 Activity 发生改变导致重建后,会先调用 onSaveInstanceState方法来保存数据,然后再新的 Activity 创建时,将数据传入 onCreate 方法,以便恢复数据,同时新的 Activity 的 onRestoreInstanceState 方法也会调用。
- 官方推荐在
onRestoreInstanceState中恢复数据,因为该方法一定被调用,其参数一定不为空,而对于·onCreate,其参数可能为空 - 如果我们不重写
onSaveInstanceState和onRestoreInstanceState方法,其系统的默认会 通过递归调用布局树中Veiw的onSaveInstanceState方法来保存数据,恢复数据时也是递归调用View中onRestoreInstanceState方法 - Activity 的
onSaveInstanceState方法只会在 Activity 异常中止时才会调用,对于正常调用finish方法终止,是不用保存数据的
具体资源配置的项目如下:
当我们不想要 Activity 在相关配置文件改变时重建时,我们可以在 AndroidMenifest.xml 中给 activity 标签加上 configChanges 属性,多个属性用 | 分割,例如我们不想要在 屏幕旋转 (orientation) 和 屏幕布局发生改变 (screenLayout)时重建我们可以这么写:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenLayout"/>
特别的,当用户设置 onfigChanges 时,相应事件发生时系统不会重建 Activity,取而代之的是执行了 onConfigurationChanged 方法
-
资源内存不足导致低优先级的 Activity 被杀死
Activity 的优先级:
- 前台 Activity
- 可见但非前台 Activity
- 后台 Activity
当内存不足时,系统会按照以上优先级杀死低优先级的 Activity 并通过
onSaveInstanceState和onRestoreInstanceState方法来恢复数据
1.2 Activity 启动模式
1.2.1 任务栈
当多个Activity 启动的时候,系统是以 任务栈 的形式来管理,例如我们此时任务栈中只有一个 Activity A,此时启动 Activity B,则会将 Activity B入栈,当用户按 Back 键 finish Activity B 时,此时 Activity B 出栈,回到 Activity A
- 任务栈是独立于应用的概念,在全局起作用,你的 Activity 可能在别的应用创建的任务栈中
- 任务栈体现在近期任务中,近期任务页面中一个任务就是一个返回栈
- 对于默认 Activity ,系统会为其创建一个默认的 任务栈
- 任务栈有前台任务栈和后台任务栈之分,一般情况下只有一个前台任务栈
- 前后台任务栈是可以切换的,当你通过近期任务点击一个后台任务栈时,此时的前台任务栈会变为后台,而后台任务栈被唤醒到前台
- 我们常用的桌面软件就是一个
SingleIntance启动模式的 Activity ,其拥有一个独立的任务栈,并且不会出现在近期任务中,可见下面SingleInstance的特性
1.2.2 启动模式
Activity 的启动模式有四种,他们都可以在 AndroidMenifest.xml 中指定,例如下面表示 MainActivity 的启动模式为 standard
<activity
android:name=".MainActivity"
android:launchMode="standard"/>
四个启动模式的特性如下:
standard:当不指定时默认为此类型。每次都会创建新的实例,首先系统会尝试将其放进调用它的 Activity 的返回栈,如果调用它的 Activity 所在返回栈不能放入,则会尝试放入其 想要的任务栈 中,如果还是不能放入,则新建任务栈放入。singleTop:栈顶复用。如果这个 Activity 当前位于当前栈的栈顶,则不会创建新实例,否则创建新,直接调用栈顶 Activity 的onNewIntent方法。注意,这里栈顶复用是对于当前前台任务栈来说singleTask:栈内复用。当使用这个模式的 Activity 启动时,系统会找到其想要的任务栈,并在该栈中寻找这个 Activity,如果其这个栈中存在实例,则会将该栈不断出栈知道该栈浮到栈顶然后调用其onNewIntent方法。否则系统会尝试创建一个新的实例放入这个新的栈中 ,然后将该栈至于前台。singleInstance:单实例。首先该模式满足simgleTask模式的行为,只是其想要的任务栈永远为一个新的独立的任务栈,并且当其启动其他的 Activity 也会在其他的栈中,即这个栈是不可进入的
1.2.3 启动 Flags
可以通过启动 Activity 时的 Intent 来指定启动模式,这种指定方式优先级要高于 在 AndroidMenifest.xml中指定的模式
FLAG_ACTIVITY_NEW_TASK:与singleTask一致FLAG_ACTIVITY_SINGLE_TOP: 与singleTop一致FLAG_CLEAR_TOP:如果前台任务栈中拥有该 Activity 实例,则将其顶所有其他 Activity 出栈,然后的行为要看其他指定效果,如果为 standard 则会创建新实例放入。
以上结论部分与书中结论有出入,是我根据实验得出的猜想。
注意:
standard只有当调用我的 Activity 的任务栈不可进入时才会进入第二步寻找想要的任务栈,如果不存在这样一个返回栈,则会直接抛异常(在安卓7、8两个版本也会进入第二步寻找想要的任务栈,其他版本中会报错),例如用 Application 启动。singleInstance的任务栈是特殊的,是不可进入的,当其在后台时,你可以通过返回回到该栈,但当你打开近期任务页面时,处于后台的singleInstance创建的栈会被销毁。singleTask不会每次都创建新的任务栈,它首先会寻找当前该 应用 的任务栈中是否存在该实例,如存在则直接将该实例通过不断出栈上浮到顶,然后同onNewIntent将Intent传入。如果不存在,则会将其放到其想要的任务栈中,若想要的任务栈恰巧是调用该 Activity 的任务栈,则和Standard一样会放入调用我 的 Activity 的任务栈,如果该栈不可进入,则会创建新任务栈。
1.2.4 TaskAffinity
这个参数可以指定一个 Activity 想要的任务栈的编号,当不指定时,默认为包名。当指定时,字符串不能以包名作为前缀。当新任务栈创建时,其任务栈内所有 Activity 的 TaskAffinity 都可以作为该任务栈的编号。
指定示例:
<activity android:name=".SecondActivity"
android:taskAffinity="com.heyanle.task"/>
1.3 IntentFilter 匹配规则
1.3.1 Intent 与 IntentFilter
从语义分析,Intent 是意图,表示我们想要实现某个功能,而完成这个功能需要某个组件。而 IntentFilter 是意图过滤器,它可以匹配一个或多个 Intent。
简单地说,启动组件的时候,我们需要构造一个 Intent 并发送。而组件在注册的时候可以注册自己的 IntentFilter ,当系统收到 Intent 时,会去寻找有没有已经注册的组件的 IntentFilter 可以匹配这个 Intent,并启动组件,如果有多个匹配,则会弹出一个 窗口 让用户选择,例如用哪个组件打开该文件,用哪个组件来分享图片(将图片分享到哪)。
注册 IntentFilter 的方式是在 AndroidMenifest.xml 中指定,一个组件可以注册多个 IntentFilter
注册示例:
<activity android:name=".ui.search.SearchActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<data android:mimeType="text\plain"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.ANSWER"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text\plain"/>
</intent-filter>
</activity>
其实从示例中可以看见,一个 IntentFilter 有 action、category 和 data 三个属性,对于一个 IntentFilter 只有这三个同时匹配,整个 Intent 才能成功匹配,而对于组件来说,只要有一个 IntentFilter 匹配,则整个组件就可以匹配。接下来分析三个属性的匹配规则:
1.3.2 action 匹配规则
action 是一个字符串,其值可以为 Intent 中预定义的一些字符常量,也可以为自定义字符串。从语义来说,这个匹配规则主要是匹配 Intent 的行为。例如我们常用的默认 Activity 中就指定了一个 IntentFilter 中 action 为 android.intent.action.MAIN ,这就是系统预定义的一个常量。
action 匹配遵循以下规则:
- 一个
IntentFilter可以指定多个action,而只要Intent中的action可以匹配其中一个,则这个IntentFilter的整个action属性就可以被匹配。 - 一个
Intent只能指定一个action。 Intent指定的action必须要和IntentFilter中其中一个匹配,一个没有指定action的Intent不能匹配没有指定任何action的IntentFilteraction的匹配区分大小写
1.3.3 category 匹配规则
category 是一个字符串,其值可以为 Intent 中预定义的一些字符串常量,也可以为自定义字符串。从语义来说,这是指定 Intent 的类型。例如默认 Activity 中就指定了一个 android.intent.category.LAUNCHER 的 category。
category 匹配遵循以下规则:
- 一个
IntentFilter中可以指定多个category,一个Intent中也可以指定多个category。一个Intent与IntentFilter匹配的条件是IntentFilter中指定的所有category要包含Intent中指定的所有。 - 如果
Intent中没有指定任何category,那么它能匹配所有IntentFilter,因为空集被所有集合包含,包括另一个空集。 category的匹配同样区分大小写- 当我们调用
startActivity或者startActivityForResult隐式启动的时候,系统会自动为我们的Intent中加上android.intent.category.DEFAULT的category,那么按照我们上面的匹配规则,如果你的 Activity 想要被隐式启动,则必须在IntentFilter中加入该category
1.3.4 data 的匹配规则
data 分两部分组成,第一部分是 mineType ,从语义分析是媒体类型。第二部分是 URI ,即统一资源标识符,自然是用于表示一个资源。这里分两部分分析。
-
mineType是一个字符串,用于指定媒体格式,虽然说是字符串可以自定义,但是其有其一般写法,例如对于所有视频文件为video/*,对于jpg格式的图片为image/jpeg。并且这里支持通配符匹配*,即Intent的image/jpeg可匹配image/*的IntentType -
URI包含的内容较多,以下是一条URI的格式:<scheme>://<host>:<port>/<path><scheme>: 模式,比如http,file或content等。<host>:主机名,可以是 域名 如heyanle.com,也可以是IP如127.0.0.1。<port>:端口。可选。<path>:路径,可以是完整路径,也可包含通配符*,同时,如果需要*字符本身,则需要转转义。
对于 URI ,其各个部分可指定也可不指定,并且具有线性依赖关系,
- 未指定
scheme,则会忽略host - 未指定
host,则会忽略port - 未指定
host和port,则会忽略path
注意,这里指的忽略并不是真正将其丢弃,而是匹配的时候将其忽略,例如对于文件,一般为 file:///sdcard/a.jpg,此时没有指定 host ,而最终组件启动后是可以拿到完整的 URI。
URI 匹配时,系统会对 IntentFilter 有指定的部分进行比较,如:
- 若
IntentFilter有指定scheme而未指定其他,则所有包含该scheme的URI都可匹配 - 若
IntentFilter指定了scheme和host与port,则所有包含该scheme、host与port的URI均可匹配,无论path - 若
IntentFilter指定了所有部分,则必须所有部分都一致才可匹配(path中支持通配符)。
对于部分未指定 mineType 但指定了 URI 的 Intent 而言,系统会尝试从 URI 中分析 mineType,例如对于 scheme 的 file,如果在其 path 中包含文件名,则会从后缀名中分析。
对于整个 data 而言。其匹配遵循以下规则:
- 如果
Intent中不包含data,则其只可匹配同样不包含data的IntentFilter,这里隐含了data可以为空的规则。 IntentFilter可指定多个data,而Intent只可指定一个data,并且只要IntentFilter与Intent其中一个的data不为空,则IntentFilter中必须存在一个与Intent中data完全匹配 的data才可匹配(Intent中若未指定mineType,则可能会自动分析自动指定)。这里的完全匹配包括未指定的部分,例如IntentFilter其中一个data指定了URI但未指定mineType,则只有同样未指定mineType且指定了URI可匹配的Intent才可匹配。除了下一点列出的特殊情况。- 如果
IntentFilter其中一个data只指定了mineType而未指定URI。此时如果Intent指定了可匹配的mineType,同时指定了file与content的scheme的URI,则依然可以匹配,此时不需要完全匹配的那种未指定URI的条件。
以上是 data 的匹配规则,接下来讲一下 data 的指定
对于 Intent,可以调用其三个方法指定三种情况:setData,setType 与 setDataAndType。值得注意的是,对于前两者会将对方制空,如果你需要同时指定 mineType 与 URI ,则只能调用第三个方法,不能通过分别调用两次前两个方法。
对于 IntentFilter,可以参考以下示例:
<activity android:name=".SecondActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="com.heyanle.activitydemoii.t"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="video/*" android:scheme="file"/>
</intent-filter>
</activity>
1.3.5 查找是否有匹配的 Activity
对于隐式启动,如果 Intent 找不到匹配的组件,则会抛出一个运行时异常,因此常规做法是在启动之前查找一下是否有匹配的组件,可以调用以下两个方法:
这两份方法是 PackageManager 中的方法:
public List<ResolveInfo> queryIntentActivities(Intent intent, int flags){}
public ResolveInfo resolveActivity(Intent intent, int flags){}
其中,第一个返回所有可匹配的对象,而第二个返回一个最佳匹配对象。对于第二个参数,可以使用 MATCH_DEFAULT_ONLY,表示只匹配 category 包含 android.intent.category.DEFAULT 的 Activity,即只匹配可以被隐式启动的 Activity。