第一章 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
的IntentFilter
action
的匹配区分大小写
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。