Activity的生命周期可分为两部分,一种是在正常的执行过程中的生命周期,另一种则是在执行过程中发生异常情况的生命周期,这两种情况在实际开发中都有着的极为重要的作用,因此弄清楚这里面的关系就显得十分必要。
额外补充的说明:
onCreate
-> onStart
-> onResume
onPause
-> onStop
,特殊情况:新的Activity采用了透明主题,那么当前Activity不会调用onStop
onRestart
-> onStart
-> onResume
onPause
-> onStop
-> onDestroy
onStart
对应onStop
,onResume
对应onPause
,使用中,二者极其类似,其之所以这样存在,是应为出发的角度不同。前者从Activity是否可见的角度来回调,后者从Activity是否位于前台来回调。
一个有趣的问题:当前 Activity 为 A,在此时打开 Activity B,那么 B 的onResume
先执行还是 A 的onPause
先执行?
回答这个问题并不难,这里就是采用 阅读源码 + 实验验证 的方式,在源码中新的Activity启动之前,栈顶的Activity需要先onPause
后,新的 Activity 才能启动。同时在 Activity 的生命周期的回调中输出日志,验证正确性。最后的结论就是 A 的 Activity 先onPause
,然后 B 的 Activity 再启动。在onPause
与onStop
中都不可执行耗时操作,尤其是在onPause
中。
另外,在Android的说明文档中还有着这样一张图,看图说话
一个简单的例子,在横竖屏切换的时候,在默认配置的情况下,Activity 就会销毁并重建,那么在这个过程中发生的事情自然就是数据的保存和恢复了。
这里依旧是调用了之前图片中的onSaveInstanceState
,用以保存数据,这个调用发生在onStop
之前,但其顺序与onPause
没有关系,可能在之前也可能在之后。而当 Activity 被重建之时,会调用onRestoreInstanceState
来恢复数据,其调用时机在onStart
之后,值得注意的是,这个状态值Bundle
会同时传递给onCreate
和onRestoreInstanceState
,也就是说在没有数据保存的时候,onCreate
的形参Bundle
为null
,而当有数据保存的时候则不为null
。
在系统调用onSaveInstanceState
与onRestoreInstanceState
的时候,会默认为我们保存当前 Activity 的视图结构以及恢复数据。这其中使用的是委托思想,一级一级委托上层保存元素,在一级一级通知下层恢复数据。这种思想在view
绘制和事件分发中都有着应用。
Activity有着优先级一说,优先级越高,越不容易被系统杀死。按照其高低可分为以下三种
onStop
,优先级最低,在内存不足的时候会优先回收此类 Activity另外说明一点,在没有四大组件执行的时候,进程就会很快被杀死。
AndroidManifest.xml
中配置android:configChanges
了,其目的是告诉系统不要重建在指定的变化时不要重建 Activity,其配置值有很多,但常用的只有screenSize
(屏幕尺寸信息发生改变,例如屏幕旋转),smallestScreenSize
(物理屏幕尺寸发生改变,例如外接屏幕)和keyboardHidden
(键盘的可访问性发生改变,例如调出虚拟键盘)启动模式的必要性:为了更好的对任务栈进行管理
standard
:标准模式,系统默认的启动模式,每次伴随 Activity 的启动而创建一个新的实例,不管这个实例是否已经存在。在这种启动模式下,注意两点,第一:每次 Activity 的启动都会调用完整的 Activity 生命周期流程。第二:谁启动了这个 Activity,那么这个 Activity 就会运行在启动它的那个 Activity 所在的栈中,用一个例子来说明,Activity A 启动了 Activity B(标准模式),那么 B 就会进入 A 所在的栈中。这里引申出一个问题:用 ApplicationContext 启动标准模式的 Activity 会报错,这是因为非 Activity 类型的 Context 是不存在任务栈一说的,所以就会报错,当添加了 FLAG_ACTIVITY_NEW_TASK 时,便不会有问题,这是因为当存在这个Flag的时候,启动时就会创建出一个新的任务栈,而这时候启动的 Activity 实际上是以 singleTask 模式启动的。singleTop
:栈顶复用模式。这种模式下,新的 Activity 已经位于栈顶,那么此 Activity 实例将不再创建,并且回调onNewIntent
方法。但如果新的 Activity 不在栈顶,不管其是否存在都会新建 Activity 的实例,并将新建的实例置于栈顶。singleTask
:栈内复用模式。简单来说,这就是 Activity 实例的单例模式,只要栈中存在 Activity 实例,就不会创建这个 Activity 的新实例,同时会回调onNewIntent
。对于Activity A,当栈中不存在 A 需要的任务栈,那么便会创建一个新的任务栈,同时将 A 放入新的任务栈中;如果存在 A 需要的任务栈,此时若 A 的实例存在,那么 A 会被调到栈顶(会清空 A 之上的实例),并且执行onNewIntent
,如果 A 的实例不存在,那么便会创建 A 的实例,并将其放入栈中。singleInstance
:单实例模式。加强的singleTask模式,拥有其全部特性。具有此模式的 Activity 只能单独位于一个任务栈,并且只有这么一个实例存在。默认情况下所有 Activity 所需的任务栈的名字为应用的包名,而任务栈可以通过TaskAffinity
标识,TaskAffinity
属性主要和singleTask
启动模式或者allowTaskReparenting
属性配对使用,在其他情况下没有意义。任务栈分为前台任务栈与后天任务栈,后台任务栈中的 Activity 位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。
当TaskAffinity
和singleTask
启动模式配对使用时,他是具有该模式的 Activity 的目前任务栈的名字,待启动的 Activity 就会运行在名字和TaskAffinity
相同的任务栈中。
当TaskAffinity
和allowTaskReparenting
结合使用的时候,当一个应用 A 启动了应用 B 的某个 Activity 后,如果这个 Activity 的allowTaskReparenting
属性为true
的话,那么应用 B 被启动后,此 Activity 会直接从应用 A 的任务栈转移到应用 B 的任务栈中。
指定 Activity 的启动模式主要有两种方式,一种是在AndroidManifest.xml
中指定,另外一种是通过Intent
的标志位来指定。
<activity
android:name="com.cj5785.demo.MainActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"
android:label="@string/app_name" />
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
二者的区别在于:
1. 优先级不同,代码添加的优先级高于XML中添加,同时存在时,以代码为准
2. 范围不同,XML方式无法设置`FLAG_ACTIVITY_CLEAR_TOP`,代码无法设置`singleInstance`
查看任务栈信息:adb shell dumpsys activity
关于任务栈,当前台任务栈都出栈时,回将后台任务栈调至前台。
Activity 的 Flags 有很多,除了几个常用的,其余可以在用到的时候去文档查询
为 Activity 指定singleTask
启动模式,效果和在XML中指定该启动模式相同
为 Activity 指定singleTop
启动模式,效果和在XML中指定该启动模式相同
具有此标记的的 Activity,当其启动时,在同一个任务栈中所有位于它上面的 Activity 都要出栈
具有这个标记的 Activity 不会出现在历史 Activity 列表中,等同于XML中的android:excludeFromRecents="true"
Activity 的启动分为两种,隐式调用和显式调用,而 IntentFilter 正是用来匹配隐式调用的。IntentFilter 中的过滤信息有action,category,data。只有一个 Intent 同时匹配一组action,category,data才算完全匹配,也只有完全匹配才能启动目标 Activity,一个 Activity 可以包含多个intent-filter
,而intent-filter
中正是包含了三个信息的组合。
action 是一个字符串,系统预定义了一些 action,也可以自定义 action。action的匹配要求字符串完全一致(区分大小写),一个intent-filter
可以存在多个action,只要能够与其中任意一个完全一致则视为匹配成功。也就是说,action 的匹配要求 Intent 中的 action 存在且必须和过滤规则中的其中一个 action 相同。
category 是一个字符串,系统预定义了一些 category,也可以自定义 category。category 匹配要求 Intent 中如果有 category,那么所有的 category 都必须和过滤规则中的其中一个 category 相同。如果不写,那么在startActivity的时候会默认加上android.intent.category.DEFAULT
,为了能够接受隐式调用,也必须加上android.intent.category.DEFAULT
。
data 的匹配规则与 action 类似,如果过滤规则中定义了 data,那么 Intent 中也必须要定义可匹配的 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。
URI的默认值为content和file,也就是说如果指定了,那么就必须是content或者file
<data android:mimeType="image/*" />
intent.setDataAndType(Uri.parse("file://abc"), "image/*");
intent的setData
和setType
会使得后调用的清除先调用的值
隐式调用启动 Activity,如果没有能够匹配的项,就会报错ActivityNotFoundException
,这种情况一般使用PackageManager
的resolveActivity
或者Intent
的resolveActivity
方法先做判断,找不到匹配项则会返回null
,其参数第二位为标志为,一般设置为MATCH_DEFAULT_ONLY
,以防止匹配到不含 default category 的 Activity,而那些 Activity 是无法通过隐式调用启动的。