一、概述
本篇博客为笔者的学习笔记,主要是基于《Android开发艺术探索》以及其他的一些相关书籍进行一些要点的记录,注重的是 Android 基础方面的相关知识。本篇记录的是 Activity
的基础知识,包括 Activity
是什么,Activity
的生命周期,Activity
的启动模式和显隐式启动 Activity
的方式。注意,学习笔记并不会对相关细节过多深挖,只会对重要内容做出相应的记录和概括。
二、什么是 Activity
在日常的开发中,Activity
应该是和我们打交道最多的组件了,我们来看看 Android 官方文档中对 Activity
的介绍:
An activity is a single, focused thing that the user can do. Almost all activities interact with the user。
从这段话大致翻译为:Activity
是专注于与用户产生交互的组件,它提供了一个窗口来让用户可以执行点击、滑动等操作。
Activity
的继承结构如下所示:
可以看到,它是间接继承自 Context
这个类的,所以在 Activity
中调用如 Intent
的构造方法需要传入 Context
参数的时候我们可以直接传入 this
,将 Activity
向上转型成 Context
对象就是这个原因。
三、Activity 的生命周期
Activity
的生命周期图:
3.1 生命周期方法
相应的生命周期方法介绍如下:
- onCreate:生命周期的第一个方法,
Activity
被创建时回调。一般在这个方法中做一些初始化工作如加载布局资源,初始化相关数据等。 - onStart:
Activity
正在启动时回调,此时Activity
为可见状态,但是还没有出现在前台,此时 Activity 不可触摸。 - onResume:
Activity
此时为可见状态,出现在了前台,并且能与用户发生交互了。 - onPause:
Activity
此时正在停止,正常情况下很快就会调用onStop
方法。在该方法中可以做一些停止动画、存储资源的操作,但是不能太耗时。 - onStop:
Activity
即将停止,可以做一些简单的回收工作,同样不能太耗时。 - onDestroy:
Activity
即将被系统销毁,在这里我们可以做一些资源的释放。 - onRestart:
Activity
重新启动时回调此方法,会使Activity
从不可见状态变为可见状态,常见场景有点击 Home 键之后又返回Activity
,以及屏幕由灭变亮等。
注意事项:
onStart 和 onResume 方法的异同点:
- 相同点:
onStart
和onResume
下的Activity
都是可见的。 - 不同点:
onStart
中Activity
还未显示到前台,用户此时无法与Activity
发生交互。而onResume
中Activity
已经显示到前台了,此时用户可以与Activity
产生交互了。
Activity
的生命周期可分为典型情况下的生命周期和异常情况下的生命周期,下面是对这两种情况生命周期的介绍。
3.2 典型情况下的生命周期
典型情况下的生命周期方法调用可大致分为 2 种情况:
- 单个
Activity
的创建和销毁。 - 从
Activity
中打开另一个Activity
。
3.2.1 单个 Activity 的创建和销毁
- 当我们开启一个
Activity
时,会执行:onCreate - onStart - onResume
。 - 如果我们此时点击 Home 键退回桌面,此时
Activity
是不可见的,会执行:onPause - onStop
。 - 当我们再次返回这个
Activity
时,执行:onRestart - onStart - onResume
,而如果在Activity
置于后台时由于内存不足被杀了,那么就会执行onCreate - onStart - onResume
。 - 最后如果
Activity
所在的进程被系统销毁了或者我们按 back 键退出Activity
,就会执行onDestroy
方法。
上述过程的流程图如下所示:
3.2.2 从 Activity 中打开另一个 Activity
这里把已经存在的 Activity
叫做 A,待打开的 Activity
命名为 B,从 Activity 中打开另一个 Activity 也分为两种情况:
- B完全覆盖了A
- B 未完全覆盖 A 或者 B 是透明的 Activity
B 完全覆盖了 A
如果打开的 B 完全覆盖住了 A,那么这时 A 就处于不可见状态,那么 A 就会调用 onPause
方法,然后回调 B 的 onCreate - onStart - onResume
这 3 个方法开启 B,最后回调 A 的 onStop
方法,因为此时 A 是不可见的。它的流程图如下:
B 未完全覆盖 A 或者 B 是透明的 Activity
这种情况像对话框或者完全透明的 Activity
覆盖在 A 上,此时 A 只是失去了焦点,但它依然是可见的。所以它流程和上面的几乎是一致的,唯一的区别是此时 A 依然是可见的,所以我们最后不会回调 onStop
方法,它的流程图如下:
3.2.3 注意事项
- 在 Activity A 中打开 Activity B,无论 B 是什么样子的,执行流程一定是先调用 A 的
onPause
,再开启 B。 - 结合上述,在
Activity
的onPause
中应当尽量避免耗时操作,以免新的Activity
迟迟无法打开,甚至产生 ANR 问题。
以上均为 Activity
在典型情况下的生命周期方法调用,下面是异常情况下的生命周期方法调用。
3.3 异常情况下的生命周期
异常情况指的是以下两种情况:
- 资源相关的系统配置发生改变导致
Activity
被杀死。 - 低优先级的
Activity
在内存不足时被系统杀死。
3.3.1 资源相关的系统配置发生改变的情况
这种情况最为典型的就是屏幕发生旋转的时候,由于屏幕发生了旋转,导致 Activity
的相关资源配置发生了改变,如图片。此时 Activity
会被销毁然后进行重建。这种情况下会回调如下方法:
- 首先回调
onSaveInstanceState
方法,用于保存Activity
的状态(文本信息、列表滑动位置等)。该方法在onStop
前回调,与onPause
无既定时序关系。原Activity
最终会回调onDestroy
方法销毁。 - 接着回调
onCreate
方法重新创建Activity
,onCreate
方法的Bundle
参数为第 1 步onSaveInstanceState
方法所保存下来的对象,异常情况下该Bundle
参数值才不为null
。 - 再接着会回调
onRestoreInstanceState
方法,它的Bundle
参数也是由onSaveInstanceState
传给它的,它的调用位于onStart
之后。
时序图如下所示:
注意事项:
onSaveInstanceState
和onRestoreInstanceState
方法只有在Activity
异常终止时才得到回调。onCreate
和onRestoreInstanceState
均能恢复Activity
的状态,系统更加推荐使用onRestoreInstanceState
方法进行恢复。onCreate
和onRestoreInstanceState
的区别在于onCreate
的Bundle
参数可能为空,需要进行非空判定,而onRestoreInstanceState
方法如果得到回调,则它的Bundle
参数一定不为空。onSaveInstanceState
对数据的保存处理有点类似于事件分发机制的模式,首先Activity
保存自身的相关数据,接着Activity
会委托Window
进行数据保存,Window
则会委托DecorView
,DecorView
继续通知它的子元素保存数据,如果子元素是ViewGroup
类型的,那么就会继续通知到里面的子View
保存数据…依此类推,完成对数据的保存。
3.3.2 内存不足时被系统杀死的情况
当 Activity
位于后台的时候,此时它的优先级是比较低的,当设备的内存不足的时候,就存在被杀死的几率。这种情况下的生命周期和前面由于资源配置改变引起的生命周期变化时序图是一样的,Activity
的优先级自高到低如下:
- 前台
Activity
—— 可与用户交互的Activity
,优先级最高。 - 可见非前台
Activity
——Activity
可见但不可与用户交互,如开启对话框的情况。 - 后台
Activity
—— 不可见的Activity
,优先级最低。
当内存不足时,系统会按照该优先级顺序杀死 Activity
所在进程,在后续还是会通过 onSaveInstanceState
和 onRestoreInstanceState
方法恢复数据。如果想要提高所在进程的优先级,可以考虑使用前台 Service
。例子:下载的时候经常会用前台 Service
。
四、Activity 的启动模式
Activity
的启动模式有 3 个知识点:任务栈、Activity
启动模式类型以及启动模式的设置方式。
4.1 任务栈
任务栈是系统创建用于存放 Activity
实例的区域,任务栈的默认名字为应用的包名。它是一种“后进先出”的结构。如果需要指定 Activity
的任务栈,可以在 AndroidManifest.xml
通过 android:taskAffinity
属性进行指定,该任务栈的名字不能与应用包名相同,否则相当于没指定其所在任务栈。
在 AndroidManifest.xml
文件中,我们还可以为 Activity
设置一个属性 allowTaskReparenting
,当其值为 true
时,可以产生如下效果:
当应用 A 启动应用 B 的 Activity
时,如果该 Activity
的 allowTaskReparenting
为 true
,当启动应用 B 时,该 Activity
会直接从 A 的任务栈转移到 B 的任务栈中,也就是说应用 B 打开后直接就是显示的该 Activity
。
4.2 Activity 启动模式类型
Activity
的启动模式分为如下 4 种:
- standard:标准模式。这也是
Activity
的默认模式,无论任务栈中是否存在该Activity
的实例,都会新创建一个该Activity
的实例。 - singleTop:栈顶复用模式。启动
Activity
之前会查询该Activity
的实例是否位于任务栈的栈顶,如果位于栈顶的话会复用该Activity
的实例,否则的话仍然会创建该Activity
的实例。 - singleTask:栈内复用模式。如果任务栈中存在该
Activity
的实例,就会复用该任务栈中的Activity
的实例,使其位于栈顶,而在该Activity
之上的Activity
实例则会被销毁出栈。 - singleInstance:单实例模式。属于
singleTask
的加强版,加强的地方是该Activity
只能单独地位于一个任务栈当中。
4.3 Activity 启动模式设置
Activity
的启动模式可通过两种方式进行设置:在 AndroidManifest.xml
中声明或通过 FLAG
的方式进行设置。
4.3.1 通过 AndroidManifest.xml 设置
在 AndroidManifest.xml
中,可以在 Activity
的标签中通过设置 launchMode
的属性进行设置,它可以设置上面的 4 种模式,即 standard
、singleTop
、singleTask
以及 singleInstance
。
4.3.2 通过 FLAG 进行设置
这是一种代码的设置方式,它需要借助于 Intent
进行设置。常见 FLAG
如下:
- FLAG_ACTIVITY_NEW_TASK:对应于
singleTask
启动模式。 - FLAG_ACTIVITY_SINGLE_TOP:对应于
singleTop
启动模式。 - FLAG_ACTIVITY_CLEAR_TOP:当
Activity
具有此标记位时,同一任务栈中位于它之上的Activity
都要出栈。它和singleTask
模式非常像,唯一一点不同是,如果Activity
的启动模式设置为standard
,那么它连同它之上的Activity
都会销毁出栈,然后将新创建的Activity
实例置于栈顶。
通过 FLAG
进行设置的时候,需要借助 Intent#setFlags
方法,当我们通过 startActivity
启动一个 Activity
时,就可以通过这种方式对待启动的 Activity
进行设置。
4.3.3 注意事项
当我们的 Activity
在 AndroidManifest.xml
和 FLAG
中都进行了相关设置时,如果发生了设置的冲突(例如 launchMode 指定为 standard 而 FLAG 则是设置为 FLAG_ACTIVITY_NEW_TASK),那么应当以 FLAG
设置的为准,因为通过 FLAG
设置的启动模式优先级比在 AndroidManifest.xml
中设置的启动模式优先级高。
4.4 onNewIntent
这个方法主要是在启动模式为 singleTop
以及 singleTask
得到调用,当在这两个模式下的 Activity
符合复用的条件时,该 Activity
就不会创建相应的实例,而是取而回调 onNewIntent
方法,通过此方法的参数我们可以取得当前请求的信息。
五、显隐式开启 Activity
开启 Activity
有两种方式,显示和隐式。它们均需要通过 Intent
进行设置。
5.1 显式开启 Activity
显式的意思就是直接指定要开启的 Activity
的名称,这种用法比较简单:
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
简单的 2 步,就可以开启一个 Activity
,这种启动方式有一个局限性,只能启动应用内的 Activity
。
5.2 隐式开启 Activity
隐式的意思是不指明 Activity
的名称,通过 IntentFilter
的匹配来开启相应的 Activity
,这种启动方式应用相当广泛,如在应用中开启其它应用的 Activity
或者一些系统的应用。
5.2.1 IntentFilter
IntentFilter
的设置位于 AndroidManifest.xml
中,它含有 3 个类型信息:
-
action:是一个字符串,系统预定义了一些
action
,我们也可以自定义action
。一个IntentFilter
中可以有多个action
,只要Intent
中的action
能与该Activity
中的某一个action
的字符串完全匹配,就是匹配成功。 -
category:也是一个字符串,系统预定义了一些
category
,我们同样能自定义category
。与action
不同的是如果Intent
中如果含有category
,那么它的所有category
必须都存在于该Activity
的IntentFilter
中才能匹配成功。Intent
可以不设置category
,它的默认值为android.intent.category.DEFAULT
。 -
data:它的结构比较复杂,如下所示:
<data android:scheme="string"
android:host="string"
android:port="string"
android:path="string"
android:pathPattern="string"
android:pathPrefix="string"
android:mimeType="string"/>
它由两部分组成:URI 和 mimeType。URI 的表示如下所示:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
- scheme:URI 的模式,如 http、content、file 等。必须指定,否则该 URI 无效。
- host:URI 的主机名。
- port:URI 的端口号。
- path、pathPrefix 和 pathPattern:均表示路径信息,path 表示完整的路径信息,pathPrefix 表示路径的前缀信息,pathPattern 也表示完整路径信息,它可以使用通配符进行表示。
mimeType 表示媒体类型,如 image/jpeg 等,它支持通配符的表达方式,如 image/*。
一个 data 的例子:URI 为 https://blog.csdn.net:10/qq_38182125 ,mimeType 为 image/jpeg,那么它的 data 可以设置为如下:
<data android:scheme="https"
android:host="blog.csdn.net"
android:port="10"
android:path="/qq_38182125"
android:mimeType="image/jpeg"/>
同样 data 在一个 IntentFilter
也可以设置多个,在 Intent
设置中只要有一个 data 和 IntentFilter
中的 data 完全匹配就匹配成功。
data 还有一点值得注意:在 Android 7.0 之后,如果 scheme
设置的是 file
,需要借助 FileProvider
进行相应的设置,具体见下面文章:
https://www.jianshu.com/p/3f9e3fc38eae
一个完整的 IntentFilter
的标签例子如下所示:
<intent-filter>
<action android:name="android.intent.action.SECOND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https"
android:host="blog.csdn.net"
android:port="10"
android:path="/qq_38182125"
android:mimeType="image/jpeg"/>
</intent-filter>
接下来看看代码中的 Intent
对象应当如何设置匹配。
5.2.2 Intent 的隐式使用方法
例子就用上面的 IntentFilter
设置,那么它的 Intent
初始化如下:
Intent intent = new Intent("android.intent.action.SECOND"); // action
// 这是一个可省略的调用
// intent.addCategory("android.intent.category.DEFAULT"); // category
intent.setDataAndType(Uri.parse("https://blog.csdn.net:10/qq_38182125"), "image/jpeg"); // data
startActivity(intent); // 启动符合条件的 Activity
初始化步骤如下:
- 首先是设置
action
,Intent
的构造方法传入的就是action
的值, - 接着是设置
category
。这里无论调用Intent#addCategory
方法与否实现效果一样,因为在IntentFilter
中我们的category
设置的值是android.intent.category.DEFAULT
,即默认值。而如果我们未对Intent
的category
进行设置,那么它就会为我们自动添加该默认值。 - 最后设置
Intent
的data
,如果data
的URI
和mimeType
均有设置,调用的是Intent#setDataAndType
方法;如果只设置了URI
,调用的是Intent#setData
方法;如果只设置了mimeType
,调用的是Intent#setType
方法。
注意事项:
- 在
URI
和mimeType
均有设置的情况下不能分别调用Intent#setData
和Intent#setType
方法进行设置,具体原因看源码。 - 如果没有相匹配的
Activity
,那么会抛出ActivityNotFoundException
异常,为了我们应用的健壮性,应当包含try/catch
语块进行异常处理,防止程序崩溃。 - 如果在
IntentFilter
的data
中scheme
为file
,在 Android 7.0 之后的版本应当采用FileProvider
进行相应适配。
Activity
的基本知识点就先记录到这里,这里还欠缺一块非常重要的知识点 —— Activity
的启动流程,它会在后面的笔记中进行提及,本笔记难免会存在一些错误,如果你发现了什么错误的话可以直接私信我或者在下方评论区给我留言!