读书笔记 | 四大组件之 Activity

一、概述

本篇博客为笔者的学习笔记,主要是基于《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 被创建时回调。一般在这个方法中做一些初始化工作如加载布局资源,初始化相关数据等。
  • onStartActivity 正在启动时回调,此时 Activity 为可见状态,但是还没有出现在前台,此时 Activity 不可触摸。
  • onResumeActivity 此时为可见状态,出现在了前台,并且能与用户发生交互了。
  • onPauseActivity 此时正在停止,正常情况下很快就会调用 onStop 方法。在该方法中可以做一些停止动画、存储资源的操作,但是不能太耗时。
  • onStopActivity 即将停止,可以做一些简单的回收工作,同样不能太耗时。
  • onDestroyActivity 即将被系统销毁,在这里我们可以做一些资源的释放。
  • onRestartActivity 重新启动时回调此方法,会使 Activity 从不可见状态变为可见状态,常见场景有点击 Home 键之后又返回 Activity,以及屏幕由灭变亮等。

注意事项:

onStart 和 onResume 方法的异同点:

  • 相同点:onStartonResume 下的 Activity 都是可见的。
  • 不同点:onStartActivity 还未显示到前台,用户此时无法与 Activity 发生交互。而 onResumeActivity 已经显示到前台了,此时用户可以与 Activity 产生交互了。

Activity 的生命周期可分为典型情况下的生命周期和异常情况下的生命周期,下面是对这两种情况生命周期的介绍。

3.2 典型情况下的生命周期

典型情况下的生命周期方法调用可大致分为 2 种情况:

  1. 单个 Activity 的创建和销毁。
  2. Activity 中打开另一个 Activity

3.2.1 单个 Activity 的创建和销毁

  1. 当我们开启一个 Activity 时,会执行:onCreate - onStart - onResume
  2. 如果我们此时点击 Home 键退回桌面,此时 Activity 是不可见的,会执行:onPause - onStop
  3. 当我们再次返回这个 Activity 时,执行:onRestart - onStart - onResume,而如果在 Activity 置于后台时由于内存不足被杀了,那么就会执行 onCreate - onStart - onResume
  4. 最后如果 Activity 所在的进程被系统销毁了或者我们按 back 键退出 Activity,就会执行 onDestroy 方法。

上述过程的流程图如下所示:
在这里插入图片描述

3.2.2 从 Activity 中打开另一个 Activity

这里把已经存在的 Activity 叫做 A,待打开的 Activity 命名为 B,从 Activity 中打开另一个 Activity 也分为两种情况:

  1. B完全覆盖了A
  2. 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。
  • 结合上述,在 ActivityonPause 中应当尽量避免耗时操作,以免新的 Activity 迟迟无法打开,甚至产生 ANR 问题。

以上均为 Activity 在典型情况下的生命周期方法调用,下面是异常情况下的生命周期方法调用。

3.3 异常情况下的生命周期

异常情况指的是以下两种情况:

  1. 资源相关的系统配置发生改变导致 Activity 被杀死。
  2. 低优先级的 Activity 在内存不足时被系统杀死。

3.3.1 资源相关的系统配置发生改变的情况

这种情况最为典型的就是屏幕发生旋转的时候,由于屏幕发生了旋转,导致 Activity 的相关资源配置发生了改变,如图片。此时 Activity 会被销毁然后进行重建。这种情况下会回调如下方法:

  1. 首先回调 onSaveInstanceState 方法,用于保存 Activity 的状态(文本信息、列表滑动位置等)。该方法在 onStop 前回调,与 onPause 无既定时序关系。原 Activity 最终会回调 onDestroy 方法销毁。
  2. 接着回调 onCreate 方法重新创建 ActivityonCreate 方法的 Bundle 参数为第 1 步 onSaveInstanceState 方法所保存下来的对象,异常情况下该 Bundle 参数值才不为 null
  3. 再接着会回调 onRestoreInstanceState 方法,它的 Bundle 参数也是由 onSaveInstanceState 传给它的,它的调用位于 onStart 之后。

时序图如下所示:
在这里插入图片描述
注意事项:

  1. onSaveInstanceStateonRestoreInstanceState 方法只有在 Activity 异常终止时才得到回调。
  2. onCreateonRestoreInstanceState 均能恢复 Activity 的状态,系统更加推荐使用 onRestoreInstanceState 方法进行恢复。
  3. onCreateonRestoreInstanceState 的区别在于 onCreateBundle 参数可能为空,需要进行非空判定,而 onRestoreInstanceState 方法如果得到回调,则它的 Bundle 参数一定不为空。
  4. onSaveInstanceState 对数据的保存处理有点类似于事件分发机制的模式,首先 Activity 保存自身的相关数据,接着 Activity 会委托 Window 进行数据保存,Window 则会委托 DecorViewDecorView 继续通知它的子元素保存数据,如果子元素是 ViewGroup 类型的,那么就会继续通知到里面的子 View 保存数据…依此类推,完成对数据的保存。

3.3.2 内存不足时被系统杀死的情况

Activity 位于后台的时候,此时它的优先级是比较低的,当设备的内存不足的时候,就存在被杀死的几率。这种情况下的生命周期和前面由于资源配置改变引起的生命周期变化时序图是一样的,Activity 的优先级自高到低如下:

  • 前台 Activity —— 可与用户交互的 Activity,优先级最高。
  • 可见非前台 Activity —— Activity 可见但不可与用户交互,如开启对话框的情况。
  • 后台 Activity —— 不可见的 Activity,优先级最低。

当内存不足时,系统会按照该优先级顺序杀死 Activity 所在进程,在后续还是会通过 onSaveInstanceStateonRestoreInstanceState 方法恢复数据。如果想要提高所在进程的优先级,可以考虑使用前台 Service。例子:下载的时候经常会用前台 Service

四、Activity 的启动模式

Activity 的启动模式有 3 个知识点:任务栈、Activity 启动模式类型以及启动模式的设置方式。

4.1 任务栈

任务栈是系统创建用于存放 Activity 实例的区域,任务栈的默认名字为应用的包名。它是一种“后进先出”的结构。如果需要指定 Activity 的任务栈,可以在 AndroidManifest.xml 通过 android:taskAffinity 属性进行指定,该任务栈的名字不能与应用包名相同,否则相当于没指定其所在任务栈。

AndroidManifest.xml 文件中,我们还可以为 Activity 设置一个属性 allowTaskReparenting,当其值为 true 时,可以产生如下效果:

当应用 A 启动应用 B 的 Activity 时,如果该 ActivityallowTaskReparentingtrue,当启动应用 B 时,该 Activity 会直接从 A 的任务栈转移到 B 的任务栈中,也就是说应用 B 打开后直接就是显示的该 Activity

4.2 Activity 启动模式类型

Activity 的启动模式分为如下 4 种:

  1. standard:标准模式。这也是 Activity 的默认模式,无论任务栈中是否存在该 Activity 的实例,都会新创建一个该 Activity 的实例。
  2. singleTop:栈顶复用模式。启动 Activity 之前会查询该 Activity 的实例是否位于任务栈的栈顶,如果位于栈顶的话会复用该 Activity 的实例,否则的话仍然会创建该 Activity 的实例。
  3. singleTask:栈内复用模式。如果任务栈中存在该 Activity 的实例,就会复用该任务栈中的 Activity 的实例,使其位于栈顶,而在该 Activity 之上的 Activity 实例则会被销毁出栈。
  4. singleInstance:单实例模式。属于 singleTask 的加强版,加强的地方是该 Activity 只能单独地位于一个任务栈当中。

4.3 Activity 启动模式设置

Activity 的启动模式可通过两种方式进行设置:在 AndroidManifest.xml 中声明或通过 FLAG 的方式进行设置。

4.3.1 通过 AndroidManifest.xml 设置

AndroidManifest.xml 中,可以在 Activity 的标签中通过设置 launchMode 的属性进行设置,它可以设置上面的 4 种模式,即 standardsingleTopsingleTask 以及 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 注意事项

当我们的 ActivityAndroidManifest.xmlFLAG 中都进行了相关设置时,如果发生了设置的冲突(例如 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 必须都存在于该 ActivityIntentFilter 中才能匹配成功。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

初始化步骤如下:

  1. 首先是设置 actionIntent 的构造方法传入的就是 action 的值,
  2. 接着是设置 category。这里无论调用 Intent#addCategory 方法与否实现效果一样,因为在 IntentFilter 中我们的 category 设置的值是 android.intent.category.DEFAULT,即默认值。而如果我们未对 Intentcategory 进行设置,那么它就会为我们自动添加该默认值。
  3. 最后设置 Intentdata,如果 dataURImimeType 均有设置,调用的是 Intent#setDataAndType 方法;如果只设置了 URI,调用的是 Intent#setData 方法;如果只设置了 mimeType,调用的是 Intent#setType 方法。

注意事项:

  1. URImimeType 均有设置的情况下不能分别调用 Intent#setDataIntent#setType 方法进行设置,具体原因看源码。
  2. 如果没有相匹配的 Activity,那么会抛出 ActivityNotFoundException 异常,为了我们应用的健壮性,应当包含 try/catch 语块进行异常处理,防止程序崩溃。
  3. 如果在 IntentFilterdataschemefile,在 Android 7.0 之后的版本应当采用 FileProvider 进行相应适配。

Activity 的基本知识点就先记录到这里,这里还欠缺一块非常重要的知识点 —— Activity 的启动流程,它会在后面的笔记中进行提及,本笔记难免会存在一些错误,如果你发现了什么错误的话可以直接私信我或者在下方评论区给我留言!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值