活动 Activity
本章介绍 Android 四大组件之一 Activity 的基本概念和常见用法。主要包括如何正确地启动和停止活动页面、如何在两个活动之间传递各类消息、如何在意图之外给活动添加额外的信息,等等。
启停活动页面
本节介绍如何正确地启动和停止活动页面,首先描述活动页面的启动方法与结束方法,用户看到的页面就是开发者塑造的活动;接着详细分析活动的完整生命周期,以及每个周期方法的发生场景和流转过程;然后描述活动的几种启动模式,以及如何在代码中通过启动标志控制活动的跳转行为。
Activity 的启动和结束
在第 2 章的“跳到另一个页面”一节中,提到通过 startActivity 方法可以从当前页面跳到新页面,具体格式如“startActivity(new Intent(源页面.this, 目标页面.class));”。由于当时尚未介绍按钮控件,因此只好延迟三秒后才自动调用 startActivity 方法。现在有了按钮控件,就能利用按钮的点击事件去触发页面跳转,譬如以下代码便在重写后的点击方法 onClick 中执行页面跳转动作。
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActStartActivity.java)
// 活动类直接实现点击监听器的接口View.OnClickListener
public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_start);
// setOnClickListener来自于View,故而允许直接给View对象注册点击监听器
findViewById(R.id.btn_act_next).setOnClickListener(this);
}
@Override
public void onClick(View v) { // 点击事件的处理方法
if (v.getId() == R.id.btn_act_next) {
// 从当前页面跳到指定的新页面
//startActivity(new Intent(ActStartActivity.this, ActFinishActivity.class));
startActivity(new Intent(this, ActFinishActivity.class));
}
}
}以上代码中的 startActivity 方法,清楚标明了从当前页面跳到新的 ActFinishActivity 页面。之所以给新页面取名 ActFinishActivity,是为了在新页面中演示如何关闭页面。众所周知,若要从当前页面回到上一个页面,点击屏幕底部的返回键即可实现,但不是所有场景都使用返回键。比如页面左上角的箭头图标经常代表着返回动作,况且有时页面上会出现“完成”按钮,无论点击箭头还是点击完成按钮,都要求马上回到上一个页面。包含箭头图标与“完成”按钮的演示界面如图 4-1 所示。

既然点击某个图标或者点击某个按钮均可能触发返回动作,就需要 App 支持在某个事件发生时主动返回上一页。回到上一个页面其实相当于关闭当前页面,因为最开始由 A 页面跳到 B 页面,一旦关闭了 B 页面,App 应该展示哪个页面呢?当然是展示跳转之前的 A 页面了。在 Java 代码中,调用 finish 方法即可关闭当前页面,前述场景要求点击箭头图标或完成按钮都返回上一页面,则需给箭头图标和完成按钮分别注册点击监听器,然后在 onClick 方法中调用 finish 方法。下面便是添加了 finish 方法的新页面代码例子:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActFinishActivity.java)
public class ActFinishActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_finish);
findViewById(R.id.iv_back).setOnClickListener(this);
findViewById(R.id.btn_finish).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {
// 结束当前的活动页面
finish();
}
}
}另外,所谓“打开页面”或“关闭页面”沿用了浏览网页的叫法,对于 App 而言,页面的真实名称是“活动”——Activity。打开某个页面其实是启动某个活动,所以有 startActivity 方法却无 openActivity 方法;关闭某个页面其实是结束某个活动,所以有 finish 方法却无 close 方法。
Activity 的生命周期
App 引入活动的概念而非传统的页面概念,这是有原因的,但从字面意思理解,页面更像是静态的,而活动更像是动态的。犹如花开花落那般,活动也有从含苞待放到盛开再到凋零的生命过程。每次创建新的活动页面,自动生成的 Java 代码都给出了 onCreate 方法,该方法用于执行活动创建的相关操作,包括加载 XML 布局、设置文本视图的初始文字、注册按钮控件的点击监听,等等。onCreate 方法所代表的创建动作,正是一个活动最开始的行为,除了 onCreate,活动还有其他几种生命周期行为,它们对应的方法说明如下:
- onCreate:创建活动。此时会把页面布局加载进内存,进入了初始状态。
- onStart:开启活动。此时会把活动页面显示在屏幕上,进入了就绪状态。
- onResume:恢复活动。此时活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等。
- onPause:暂停活动。此时活动页面进入暂停状态(也就是退回就绪状态),无法与用户正常交互。
- onStop:停止活动。此时活动页面将不在屏幕上显示。
- onDestroy:销毁活动。此时回收活动占用的系统资源,把页面从内存中清除掉。
- onRestart:重启活动。处于停止状态的活动,若想重新开启的话,无须经历 onCreate 的重复创建过程,而是走 onRestart 的重启过程。
- onNewIntent:重用已有的活动实例。
上述的生命周期方法,涉及复杂的 App 运行状态,更直观的活动状态切换过程如图 4-2 所示。
如果一个 Activity 已经启动过,并且存在当前应用的 Activity 任务栈中,启动模式为 singleTask,singleInstance 或 singleTop(此时已在任务栈顶端),那么再次启动或者回到这个 Activity 的时候,不会创建新的实例,也就是不会执行 onCreate 方法,而是执行 onNewIntent 方法。
Activity 的启动模式
上一小节提到,从第一个活动跳到第二个活动,接着结束第二个活动就能返回第一个活动,可是为什么不直接返回桌面呢?这要从 Android 的内核设计说起了,系统给每个正在运行的 App 都分配了活动栈,栈里面容纳着已经创建且尚未销毁的活动信息。鉴于栈是一种先进后出、后进先出的数据结构,故而后面入栈的活动总是先出栈,假设 3 个活动的入栈顺序为:活动 A-> 活动 B-> 活动 C,则它们的出栈顺序将变为:活动 C-> 活动 B-> 活动 A,可见活动 C结束之后会返回活动 B,而不是返回活动 A 或者别的地方。
假定某个 App 分配到的活动栈大小为 3,该 App 先后打开两个活动,此时活动栈的变动情况如图 4-7 所示。

然后按下返回键,依次结束已打开的两个活动,此时活动栈的变动情况如图 4-8 所示。

结合图 4-7 与图 4-8 的入栈与出栈流程,即可验证结束活动之时的返回逻辑了。
不过前述的出入栈情况仅是默认的标准模式,实际上 Android 允许在创建活动时指定该活动的启动模式,通过启动模式控件该活动的出入栈行为。App 提供了两种办法用于设置活动页面的启动模式,其一是修改 AndroidManifest.xml,在指定的 activity 节点添加属性 android:launchMode,表示本活动以哪个启动模式运行。其二是在代码中调用 Intent 对象的 setFlags 方法,表明后续打开的活动页面采用该启动标志。下面分别予以详细说明。
在配置文件中指定启动模式
打开 AndroidManifest.xml,给 activity 节点添加属性 android:launchMode,属性值填入 standard 表示采取标准模式,当然不添加属性的话默认就是标准模式。具体的 activity 节点配置内容示例如下:
<activity android:name=".JumpFirstActivity" android:launchMode="standard" />其中 launchMode 属性的几种取值说明见表 4-1。
| Intent 类的启动标志 | 说明 |
|---|---|
| Intent.FLAG_ACTIVITY_NEW_TASK | 开辟一个新的任务栈,该值类似于 launchMode="standard";不同之处在于,如果原来不存在活动栈,则 FLAG_ACTIVITY_NEW_TASK 会创建一个新栈 |
| Intent.FLAG_ACTIVITY_SINGLE_TOP | 当栈顶为待跳转的活动实例之时,则重用栈顶的实例。该值等同于 launchMode="singleTop" |
| Intent.FLAG_ACTIVITY_CLEAR_TOP | 当栈中存在待跳转的活动实例时,则重新创建一个新实例,并清除原实例上方的所有实例。该值与 launchMode="singleTask" 类似,但 singleTask 采取 onNewIntent 方法启用原任务,而 FLAG_ACTIVITY_CLEAR_TOP 采取先调用 onDestroy 再调用 onCreate 来创建新任务 |
| Intent.FLAG_ACTIVITY_NO_HISTORY | 该标志与 launchMode="standard" 情况类似,但栈中不保存新启动的活动实例。这样下次无论以何种方式再启动该实例,也要走 standard 模式的完整流程 |
| Intent.FLAG_ACTIVITY_CLEAR_TASK | 该标志非常暴力,跳转到新页面时,栈中的原有实例都被清空。注意该标志需要结合 FLAG_ACTIVITY_NEW_TASK 使用,即 setFlags 方法的参数为`Intent.FLAG_ACTIVITY_CLEAR_TASK |
接下来举两个例子阐述启动模式的实际应用:在两个活动之间交替跳转、登录成功后不再返回登录页面,分别介绍如下。
- 在两个活动之间交替跳转
假设活动 A 有个按钮,点击该按钮会跳到活动 B;同时活动 B 也有个按钮,点击按钮会跳到活动 A;从首页打开活动 A 之后,就点击按钮在活动 A 与活动 B 之间轮流跳转。此时活动页面的跳转流程为:首页->活动 A->活动 B->活动 A->活动 B->活动 A->活动 B->……多次跳转之后想回到首页,正常的话返回流程是这样的:……->活动 B->活动 A->活动 B-> 活动 A->活动 B->活动 A->首页,注意每个箭头都代表按依次返回键,可见要按下许多次返回键才能返回首页。其实在活动 A 和活动 B 之间本不应该重复返回,因为回来回去总是这两个页面有什么意义呢?照理说每个活动返回一次足矣,同一个地方返回两次已经是多余的了,再返回应当回到首页才是。也就是说,不管过去的时候怎么跳转,回来的时候应该按照这个流程:……->活动 B->活动 A->首页,或者按照这个流程:……->活动 A->活动 B->首页,总之已经返回了的页面,决不再返回第二次。
对于不允许重复返回的情况,可以设置启动标志 FLAG_ACTIVITY_CLEAR_TOP,即使活动栈里面存在待跳转的活动实例,也会崇拜创建该活动的实例,并清除原实例上方的所有实例,保证栈中最多只有该活动的唯一实例,从而避免了无谓的重复返回。于是活动 A 内部的跳转代码就改成了下面这般:
(完整代码见 chapter04\src\main\java\com\example\chapter04\JumpFirstActivity.java)
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, JumpSecondActivity.class);
// 栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);当然活动 B 内部的跳转代码也要设置同样的启动标志:
(完整代码见 chapter04\src\main\java\com\example\chapter04\JumpSecondActivity.java)
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, JumpFirstActivity.class);
// 栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);这下两个活动的跳转代码都设置了 FLAG_ACTIVITY_CLEAR_TOP,运行测试 App 发现多次跳转之后,每个活动仅会返回一次而已。
- 登录成功后不再返回登陆页面
很多 App 第一次打开都要求用户登录,登录成功再进入 App 首页,如果这时按下返回键,发现并没有回到上一个登录页面,而是直接退出 App 了,这又是什么缘故呢?原来用户登录成功后,App 便记下用户的登录信息,接下来默认该用户是登录状态,自然不必重新输入用户名和密码了。既然默认用户已经登录,哪里还需要回到登录页面?不光登录页面,登录之前的其他页面包括获取验证码、找回密码等页面都不应进去,每次登录成功之后,整个 App 就焕然一新仿佛忘记了有登录页面这回事。
对于回不去的登录页面情况,可以设置启动标志 FLAG_ACTIVITY_CLEAR_TASK,该标志会清空当前活动栈里的所有实例。不过全部清空之后,意味着当前栈没法用了,必须另外找个活动栈才行,也就是同时设置启动标志 FLAG_ACTIVITY_NEW_TASK,该标志用于开辟新任务的活动栈。于是离开登录页面的跳转代码变成下面这样:
(完整代码见 chapter04\src\main\java\com\example\chapter04\LoginInputActivity.java)
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, LoginSuccessActivity.class);
// 设置启动标志:跳转到新页面时,栈中的原有实例都被清空,同时开辟新任务的活动栈
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);运行测试 App,登录成功进入首页之后,点击返回键果然没回到登录页面。
默认启动模式 standard
该模式可以被设定,不在 manifest 设定时,Activity 的默认模式就是 standard。在该模式下,启动的 Activity 会依照启动顺序被依次压入 Task 栈中:

栈顶复用模式 singleTop
在该模式下,如果栈顶 Activity 为我们要新建的 Activity(目标 Activity),那么就不会重复创建新的 Activity。

应用场景
适合开启渠道多、多应用开启调用的 Activity,通过这种设置可以避免已经创建过的 Activity 被重复创建,多数通过动态设置使用。
栈内复用模式 singleTask
与 singleTop 模式相似,只不过 singleTop 模式只是针对栈顶的元素,而 singleTask 模式下,如果 task 栈内存在目标 Activity 实例,则将 task 内的对应 Activity 实例之上的所有 Activity 弹出栈,并将对应 Activity 置于栈顶,获得焦点。

应用场景
程序主界面:我们肯定不希望主界面被创建多次,而且在主界面退出的时候退出整个 App 是最好的效果。
耗费系统资源的 Activity:对于那些及其耗费系统资源的 Activity,我们可以考虑将其设为 singleTask 模式,减少资源耗费。
全局唯一模式 singleInstance
在该模式下,我们会为目标 Activity 创建一个新的 Task 栈,将目标 Activity 放入新的 Task,并让目标 Activity 获得焦点。新的 Task 有且只有这一个 Activity 实例。如果已经创建过目标 Activity 实例,则不会创建新的 Task,而是将以前创建过的 Activity 唤醒。

看一个示例,Activity3 设置为 singleInstance,Activity1 和 Activity2 默认(standard),下图程序流程中,黄色的代表 Background 的 Task,蓝色的代表 Foreground 的 Task。返回时会先把 Foreground 的 Task 中的 Activity 弹出,直到 Task 销毁,然后才将 Background 的 Task 唤到前台,所以最后将 Activity3 销毁之后,会直接退出应用。

动态设置启动模式
在上述所有情况,都是我们在 Manifest 中通过 launchMode 属性设置的,这个被称为静态设置,动态设置是通过 Java 代码设置的。
通过 Intent 动态设置 Activity 启动模式
intent.setFlags();如果同时有动态和静态设置,那么动态的优先级更高。接下来我们来看一下如何动态的设置 Activity 启动模式。
FLAG_ACTIVITY_NEW_TASK
对应 Flag
Intent.FLAG_ACTIVITY_NEW_TASK此 Flag 跟 singleInstance 很相似,在给目标 Activity 设立此 Flag 后,会根据目标 Activity 的 affinity 进行匹配,如果已经存在与其 affinity 相同的 task,则将目标 Activity 压入此 Task。反之没有的话,则新建一个 task,新建的 task 的 affinity 值与目标 Activity 相同,然后将目标 Activity 压入此栈。
但它与 singleInstance 有不同的点,两点需要注意的地方:
- 新的 Task 没有说只能存放一个目标 Activity,只是说决定是否新建一个 Task,而 singleInstance 模式下新的 Task 只能防止一个目标 Activity。
- 在同一应用下,如果 Activity 都是默认的 affinity,那么此 Flag 无效,而 singleInstance 默认情况也会创建新的 Task。
FLAG_ACTIVITY_SINGLE_TOP
该模式比较简单,对应 Flag 如下:
Intent.FLAG_ACTIVITY_SINGLE_TOP此 Flag 与静态设置中的 singleTop 效果相同
FLAG_ACTIVITY_CLEAR_TOP
这个模式对应的 Flag 如下:
Intent.FLAG_ACTIVITY_CLEAR_TOP当设置此 Flag 时,目标 Activity 会检查 Task 中是否存在此实例,如果没有则添加压入栈。如果有,就将位于 Task 中的对应 Activity 其上的所有 Activity 弹出栈,此时有以下两种情况:
- 如果同时设置 FLAG_ACTIVITY_SINGLE_TOP,则直接使用站内的对应 Activity。
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);- 没有设置,则将栈内的对应 Activity 销毁重新创建。
按位或运算符
运算规则:0|0=0 0|1=1 1|0=0 1|1=1
总结:参加运算的两个对象只要有一个为 1,其值为 1。
例如:3|5 即 0000 0011 | 0000 0101 = 0000 0111,因此,3|5 的值得 7
在活动之间传递消息
本节介绍如何在两个活动之间传递各类消息,首先描述 Intent 的用途和组成部分,以及显式 Intent 和隐式 Intent 的区别;接着阐述结合 Intent 和 Bundle 向下一个活动页面发送数据,再在下一个页面中解析收到的请求数据;然后叙述从下一个活动页面返回应答数据给上一个页面,并由上一个页面解析返回的应答数据。
显式 Intent 和隐式 Intent
上一小节的 Java 代码,通过 Intent 对象设置活动的启动日志,这个 Intent 究竟是什么呢?Intent 的中文名是意图,意思是我想让你干什么,简单地说,就是传递消息。Intent 是各个组件之间信息沟通的桥梁,既能在 Activity 之间沟通,又能在 Activity 与 Service 之间沟通,也能在 Activity 与 Broadcast 之间沟通。总而言之,Intent 用于 Android 各组件之间的通信,它主要完成下列 3 部分工作:
- 表明本次通信请求从哪里来、到哪里去、要怎么走。
- 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
- 发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。
为了做好以上工作,就要给意图配上必须的装备,Intent 的组成部分见表 4-3。
| 元素名称 | 设置方法 | 说明与用途 |
|---|---|---|
| Component | setComponent | 组件,它指定意图的来源与目标 |
| Action | setAction | 动作,它指定意图的动作行为 |
| Data | setData | 即 Uri,它指定动作要操纵的数据路径 |
| Category | addCategory | 类别,它指定意图的操作类别 |
| Type | setType | 数据类型,它指定消息的数据类型 |
| Extras | putExtras | 扩展信息,它指定装载的包裹信息 |
| Flags | setFlags | 标志位,它指定活动的启动标志 |
指定意图对象的目标有两种表达方式,一种是显式 Intent,另一种是隐式 Intent。
- 显式 Intent,直接指定来源活动与目标活动,属于精确匹配
在构建一个意图对象时,需要指定两个参数,第一个参数表示跳转的来源页面,即“来源 Activity.this”;第二个参数表示待跳转的页面,即“目标 Activity.class”。具体的意图构建方式有如下 3 种:
- 在 Intent 的构造函数中指定,示例代码如下:
Intent intent = new Intent(this, ActNextActivity.class); // 创建一个目标确定的意图- 调用意图对象的 setClass 方法指定,示例代码如下:
Intent intent = new Intent(); // 创建一个新意图
intent.setClass(this, ActNextActivity.class); // 设置意图要跳转的目标活动- 调用意图对象的 setComponent 方法指定,示例代码如下:
Intent intent = new Intent(); // 创建一个新意图
// 创建包含目标活动在内的组件名称对象
ComponentName component = new ComponentName(this, ActNextActivity.class);
intent.setComponent(component); // 设置意图携带的组件信息- 隐式 Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配
通常 App 不希望向外部暴露活动名称,只给出一个事先定义好的标记串,这样大家约定俗成、按图索骥就好,隐式 Intent 边骑到了标记过滤作用。这个动作名称标记串,可以是自己定义的动作,也可以是已有的系统动作。常见系统动作的取值说明见表 4-4。
| Intent 类的系统动作常量名 | 系统动作的常量值 | 说明 |
|---|---|---|
| ACTION_MAIN | android.intent.action.MAIN | App 启动时的入口 |
| ACTION_VIEW | android.intent.action.VIEW | 向用户显示数据 |
| ACTION_SEND | android.intent.action.SEND | 分享内容 |
| ACTION_CALL | android.intent.action.CALL | 直接拨号 |
| ACTION_DIAL | android.intent.action.DIAL | 准备拨号 |
| ACTION_SENDTO | android.intent.action.SENDTO | 发送短信 |
| ACTION_ANSWER | android.intent.action.ANSWER | 接听电话 |
动作名称既可以通过 setAction 方法指定,也可以通过构造函数Intent(String action)直接生成意图对象。当然,由于动作是模糊匹配,因此有时需要更详细的路径,比如仅知道某人住在天通苑小区,并不能直接找到他家,还得说明他住在天通苑的哪一期、哪栋楼、哪一层、哪一个单元。Uri 和 Category 便是这样的路径与门类信息,Uri 数据可通过构造函数Intent(String action, Uri uri)在生成对象时一起指定,也可通过 setData 方法指定(setData 这个名字有歧义,实际相当于 setUri);Category 可通过 addCategory 方法指定,之所以用 add 而不用 set 方法,是因为一个意图允许设置多个 Category,方便一起过滤。
下面是一个调用系统拨号程序的代码例子,其中就用到了 Uri:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActionUriActivity.java)
String phoneNo = "12345";
Intent intent = new Intent(); // 创建一个新意图
intent.setAction(Intent.ACTION_DIAL); // 设置意图动作为准备拨号
Urine uri = Uri.parse("tel:" + phoneNo); // 声明一个拨号的 Uri
intent.setData(uri); // 设置意图前往的路径
startActivity(intent); // 启动意图通往的活动页面隐式 Intent 还用到了过滤器的概念,把不符合匹配条件的过滤掉,剩下符合条件的按照优先顺序调用。譬如创建一个 App 模块,AndroidManifest.xml 里的 intent-filter 就是配置文件中的过滤器。像最常见的首页活动 MainActivity,它的 activity 节点下面便设置了 action 和 category 的过滤条件。其中 android.intent.action.MAIN 表示 App 的入口动作,而 android.intent.category.LAUNCHER 表示在桌面上显示 App 图标,配置样例如下:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>向下一个 Activity 发送数据
上一小节提到,Intent 对象的 setData 方法只指定到达目标的路径,并非本次通信所携带的参数信息,真正的参数信息存放在 Extras 中。Intent 重载了很多种 putExtras 方法传递各种类型的参数,包括整型、双精度型、字符串等基本数据类型,甚至 Serializable 这样的序列化结构。只是调用 putExtra 方法显然不好管理,像送快递一样大小包裹随便扔,不但找起来不方便,丢了也难以知道。所以 Android 引入了 Bundle 概念,可以把 Bundle 理解为超市的寄包柜或快递收件柜,大小包裹由 Bundle 统一存取,方便又安全。
Bundle 内部用于存放消息的数据结构是 Map 映射,既可添加或删除元素,还可判断元素是否存在。开发者若要把 Bundle 数据全部打包好,只需调用一次意图对象的 putExtras 方法;若要把 Bundle 数据全部取出来,也只需调用一次意图对象的 getExtras 方法。Bundle 对象操作各类型数据的读写方法说明见表 4-5。
| 数据类型 | 读方法 | 写方法 |
|---|---|---|
| 整型数 | getInt | putInt |
| 浮点数 | getFloat | putFloat |
| 双精度数 | getDouble | putDouble |
| 布尔值 | getBoolean | putBoolean |
| 字符串 | getString | putString |
| 字符串数组 | getStringArray | putStringArray |
| 字符串列表 | getStringArrayList | putStringArrayList |
| 可序列化结构 | getSerializable | putSerializable |
接下来举个在活动之间传递数据的例子,首先在上一个活动使用包裹封装好数据,把包裹塞给意图对象,再调用 startActivity 方法跳到意图指定的目标活动。完整的活动跳转代码示例如下:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActSendActivity.java)
Intent intent = new Intent(this, ActReceiveActivity.class);
// 创建一个新包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", tv_send.getText().toString());
intent.putExtras(bundle);
startActivity(intent);然后再下一个活动中获取意图携带的快递包裹,从包裹取出各参数信息,并将传来的数据显示到文本视图。下面便是目标活动获取并展示包裹数据的代码例子:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActReceiveActivity.java)
TextView tv_receive = findViewById(R.id.tv_receive);
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
tv_receive.setText(desc);代码编写完毕,运行测试 App,打开上一个页面如图 4-9 所示。单机页面上的发送按钮跳到下一个页面如图 4-10 所示,根据展示文本可知正确获得了传来的数据。


向上一个 Activity 返回数据
数据传递经常是相互的,上一个页面不但把请求数据发送到下一个页面,有时候还要处理下一个页面的应答数据,所谓应答发生在下一个页面返回到上一个页面之际。如果只把请求数据发送到下一个页面,上一个页面调用 startActivity 方法即可;如果还要处理下一个页面的应答数据,此时就得分多步处理,详细步骤说明如下:
步骤一,上一个页面注册回调处理下一个页面的 response。
private static final String mRequest = "你睡了吗?来我家睡吧";
private ActivityResultLauncher<Intent> register;
private TextView tv_response;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_request);
TextView tv_request = findViewById(R.id.tv_request);
tv_request.setText("待发送的消息为:" + mRequest);
tv_response = findViewById(R.id.tv_response);
findViewById(R.id.btn_request).setOnClickListener(this);
register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {});
}步骤二,填充按钮的跳转动作逻辑,打包数据,使用 register.launch(intent) 进行跳转。
public void onClick(View v) {
Intent intent = new Intent(this, ActResponseActivity.class);
// 创建一个新包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", mRequest);
intent.putExtras(bundle);
register.launch(intent);
}步骤三,下一个页面接收并解析请求数据,进行相应处理。
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
// 把请求消息的详情显示在文本视图上
tv_request.setText(desc);步骤四,填充返回按钮的跳转动作逻辑,打包数据,返回上一个页面。
String mResponse = "我吃过了,还是你来我家吃";
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("response_time", DateUtil.getNowTime());
bundle.putString("response_content", mResponse);
intent.putExtras(bundle);
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
// 结束当前的活动页面
finish();步骤五,填充第一步的 callback。
register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result != null) {
Intent intent = result.getData();
if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
Bundle bundle = intent.getExtras();
String response_time = bundle.getString("response_time");
String response_content = bundle.getString("response_content");
String desc = String.format("收到返回消息:\n应答时间为%s\n应答内容为%s", response_time, response_content);
// 把返回消息的详情显示在文本视图上
tv_response.setText(desc);
}
}
});旧方式 Deprecated
步骤一,上一个页面打包好请求数据,调用 startActivityForResult 方法执行跳转动作,表示需要处理下一个页面的应答数据,该方法的第二个参数表示请求代码,它用于标识每个跳转的唯一性。跳转代码示例如下:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActRequestActivity.java)
String mRequest = "你吃饭了吗?来我家吃吧";
Intent intent = new Intent(this, ActResponseActivity.class);
// 创建一个新包裹
Bundle bundle = new Bundle();
bundle.putString("request_time", DateUtil.getNowTime());
bundle.putString("request_content", mRequest);
intent.putExtras(bundle);
startActivityForResult(intent, 0);步骤二,下一个页面接收并解析请求数据,进行相应处理。接受代码示例如下:
(完整代码见chapter04\src\main\java\com\example\chapter04\ActResponseActivity.java)
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
String request_time = bundle.getString("request_time");
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s", request_time, request_content);
// 把请求消息的详情显示在文本视图上
tv_request.setText(desc);步骤三,下一个页面在返回上一个页面时,打包应答数据并调用 setResult 方法返回数据包裹。setResult 方法的第一个参数标识应答代码(成功还是失败),第二个参数为携带包裹的意图对象。返回代码示例如下:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActResponseActivity.java)
String mResponse = "我吃过了,还是你来我家吃";
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("response_time", DateUtil.getNowTime());
bundle.putString("response_content", mResponse);
intent.putExtras(bundle);
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
// 结束当前的活动页面
finish();步骤四,上一个页面重写方法 onActivityResult,该方法的输入参数包含请求代码和结果代码,其中请求代码用于判断这次返回对应哪个跳转,接过代码用于判断下一个页面是否处理成功。如果下一个页面处理成功,再对返回数据解包操作,处理返回数据的代码示例如下:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ActRequestActivity.java)
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (intent != null && requestCode == 0 && resultCode == Activity.RESULT_OK) {
Bundle bundle = intent.getExtras();
String response_time = bundle.getString("response_time");
String response_content = bundle.getString("response_content");
String desc = String.format("收到返回消息:\n应答时间为%s\n应答内容为%s", response_time, response_content);
// 把返回消息的详情显示在文本视图上
tv_response.setText(desc);
}
}结合上述的活动消息交互步骤,运行测试 App 打开第一个活动页面如图 4-11 所示。

点击传送按钮跳转到第二个活动页面如图 4-12 所示,可见第二个页面收到了请求数据。然后点击第二个页面的返回按钮,回到第一个页面如图 4-13 所示,可见第一个页面成功收到了第二个页面的应答数据。


为活动补充附加信息
本节介绍如何在意图之外给活动添加额外的信息,首先可以把字符串参数放到字符串资源文件中,待 App 运行之时再从资源文件读取字符串值;接着还能在 AndroidManifest.xml 中给指定活动配置专门的元数据,App 运行时即可获取对应活动的元数据信息;然后利用元数据的 resource 属性配置更复杂的 XML 定义,从而为 App 注册在长按桌面之时弹出的快捷菜单。
利用资源文件配置字符串
利用 Bundle 固然能在页面跳转的时候传送数据,但这仅限于在代码中传递参数,如果要求临时修改某个参数的数值,就得去改 Java 代码。然而直接修改 Java 代码有两个弊端:
- 代码文件那么多,每个文件又有许多行代码,一下子还真不容找到修改的地方。
- 每次改动代码都得重新编译,让 Android Studio 编译的功夫也稍微费点时间。
有鉴于此,对于可能手工变动的参数,通常把参数名称与参数值的对应关系写入配置文件,由程序在运行时读取配置文件,这样只需修改配置文件就能改变对应数据了。res\values 目录下面的 strings.xml 就用来配置字符串形式的参数,打开该文件,发现里面已经存在名为 app_name 的字符串参数,它配置的是当前模块的应用名称。现在可于 app_name 下方补充一行参数配置,参数名称叫做“weather_str”,参数值则为“晴天”,具体的配置内容如下所示:
<string name="weather_str">晴天</string>接着打开活动页面的 Java 代码,调用 getString 方法即可根据“R.string.参数名称”获得指定参数的字符串值。获取代码示例如下:
(完整代码见 chapter04\src\main\java\com\example\chapter04\ReadStringActivity.java)
// 从strings.xml获取名叫weather_str的字符串值
String value = getString(R.string.weather_str);
tv_resource.setText("来自字符串资源:今天的天气是" + value);上面的 getString 方法来自于 Context 类,由于页面所在的活动类 AppCompatActivity 追根溯源来自 Context 这个抽象类,因此凡是活动页面代码都能直接调用 getString 方法。
然后在 onCreate 方法中调用 showStringResource 方法,运行测试 App,打开读取页面如图 4-14 所示,可见从资源文件成功读到了字符串。

利用元数据传递配置信息
尽管资源文件能够配置字符串参数,然而有时候为安全起见,某个参数要给某个活动专用,并不希望其他活动也能获取该参数,此时就不方便到处使用 getString 了。好在 Activity 提供了元数据(Metadata)的概念,元数据是一种描述其他数据的数据,它相当于描述固定活动的参数信息。打开 AndroidManifest.xml,在测试活动的 activity 节点内部添加 meta-data 标签,通过属性 name 指定 元数据的名称,通过属性 value 指定元数据的值。仍以天气为例,添加 meta-data 标签之后的 activity 节点如下所示:
<activity
android:name=".MetaDataActivity"
android:exported="true"
android:launchMode="standard">
<meta-data
android:name="weather"
android:value="晴天" />
</activity>元数据的 value 属性既可直接填字符串,也可引用 strings.xml 已定义的字符串资源,引用格式形如“@string/字符串的资源名称”。下面便是采用引用方式的 activity 节点配置:
<activity
android:name=".MetaDataActivity"
android:exported="true"
android:launchMode="standard">
<meta-data
android:name="weather"
android:value="@string/weather_str" />
</activity>配置好了 activity 节点的 meta-data 标签,再回到 Java 代码获取元数据信息,获取步骤分为下列 3 步:
- 调用 getPackageManager 方法获得当前应用的包管理器。
- 调用包管理器的 getActivityInfo 方法获得当前活动的信息对象。
- 活动信息对象的 metadata 是 Bundle 包裹类型,调用包裹对象的 getString 即可获得指定名称的参数值。
把上述 3 个步骤串起来,得到以下的元数据获取代码:
(完整代码见 chapter04\src\main\java\com\example\chapter04\MetaDataActivity.java)
// 获取应用包管理器
PackageManager pm = getPackageManager();
try {
// 从应用包管理器中获取当前的活动信息
ActivityInfo info = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
// 获取活动附加的元数据信息
Bundle bundle = info.metaData;
String weather = bundle.getString("weather");
tv_meta.setText("来自元数据信息:今天的天气是" + weather);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}重新运行 App 观察到的界面如图 4-15 所示,可见成功获得 AndroidManifest.xml 配置的元数据。

给应用页面注册快捷方式
元数据不单单能传递简单的字符串参数,还能传送更复杂的资源数据,从 Android 7.1 开始新增的快捷方式便用到了这点,譬如在手机桌面上长按支付宝图标,会弹出如图 4-16 所示的快捷菜单。

点击菜单项“Scan”,直接打开支付宝的扫码页面;点击菜单项“Pay/Collect”,直接打开支付宝的付款页面;点击菜单项“Collect”,直接打开支付宝的收款页面。如此不必打开支付宝首页,即可迅速跳转到常用的 App 页面,这便是所谓的快捷方式。
那么 Android 7.1 又是如何实现快捷方式的呢?那得再琢磨琢磨元数据了。原来元数据的 meta-data 标签除了前面说到的 name 属性和 value 属性,还拥有 resource 属性,该属性可指定一个 XML 文件,表示元数据想要的复杂信息保存于 XML 数据之中。借助元数据以及指定的 XML 配置,方可完成快捷方式功能,具体的实现过程说明如下:
首先打开 res/values 目录下的 strings.xml,在 resources 节点内部添加下述的 3 组(每组两个,共 6 个字)字符串配置,每组都代表一个菜单项,每组都代表一个菜单项,每组又分为长名称和短名称,平时优先展示长名称,当长名称放不下时才展示短名称。这 3 组 6 个字符串的配置定义示例如下:
<string name="first_short">first</string>
<string name="first_long">启停活动</string>
<string name="second_short">second</string>
<string name="second_long">来回跳转</string>
<string name="third_short">third</string>
<string name="third_long">登录返回</string>接着在 res 目录下创建名为 xml 的文件夹,并在该文件夹创建 shortcuts.xml,这个 XML 文件用来保存 3 组菜单项的快捷方式定义,文件内容如下所示:
(完整代码见chapter04\src\main\res\xml\shortcuts.xml)
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="first"
android:shortcutLongLabel="@string/first_long"
android:shortcutShortLabel="@string/first_short">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.ActStartActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="second"
android:shortcutLongLabel="@string/second_long"
android:shortcutShortLabel="@string/second_short">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.JumpFirstActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="third"
android:shortcutLongLabel="@string/third_long"
android:shortcutShortLabel="@string/third_short">
<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.LoginInputActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>由上述的 XML 例子中看到,每个 shortcut 节点都代表了一个菜单项,该节点的各属性说明如下:
- shortcutId:快捷方式的编号。
- enabled:是否启用快捷方式。true 表示启用,false 表示禁用。
- icon:快捷菜单左侧的图标。
- shortcutShortLabel:快捷菜单的短标签。
- shortcutLongLabel:快捷菜单的长标签。优先展开长标签的文本,长标签放不下时才展示短标签的文本。
以上的节点属性仅仅指明了每项菜单的基本规格,点击菜单项之后的跳转动作还要由 shortcut 内部的 intent 节点定义,该节点主要有 targetPackage 与 targetClass 两个属性需要修改,其中 targetPackage 属性固定为当前 App 的包名,而 targetClass 属性描述了菜单项对应的活动类完整路径。
然后打开 AndroidManifest.xml,找到 MainActivity 所在的 activity 节点,在该节点内部补充如下的元数据配置,其中 name 属性为 android.app.shortcuts,而 resource 属性为 @xml/shorcuts:
<meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"
/>这行元数据的作用,是告诉 App 首页有个快捷方式菜单,其资源内容参见位于 xml 目录下的 shortcuts.xml。完整的 activity 节点配置示例如下:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>然后把测试应用安装到手机上,回到桌面长按应用图标,此时图标下方弹出如图 4-17 所示的快捷菜单列表。

点击其中一个菜单项,果然跳到了配置的活动页面,证明元数据成功实现了类似支付宝的快捷方式。
小结
本章主要介绍了活动组件——Activity 的常见用法,包括:正确启停活动页面(Activity 的启动和结束、Activity 的生命周期、Activity 的启动模式)、在活动之间传递消息(显式 Intent 和隐式 Intent、向下一个 Activity 发送数据、向上一个 Activity 返回数据)、为活动补充附加信息(利用资源文件配置字符串、利用元数据传递配置信息、给应用页面注册快捷方式)。
通过本章的学习,我们应该能掌握以下 3 种开发技能:
- 理解活动的生命周期过程,并学会正确启动和结束活动。
- 理解意图的组成结构,并利用意图在活动之间传递消息。
- 理解元数据的概念,并通过元数据配置参数信息和注册快捷菜单。