第 4 章 活动Activity

本章介绍Android 4大组件之一Activity的基本概念和常见用法。主要包括如何正确地启动和停止活动页 面、如何在两个活动之间传递各类消息、如何在意图之外给活动添加额外的信息,等等。

4.1 启停活动页面

本节介绍如何正确地启动和停止活动页面,首先描述活动页面的启动方法与结束方法,用户看到的页面就是开发者塑造的活动;接着详细分析活动的完整生命周期,以及每个周期方法的发生场景和流转过程;然后描述活动的几种启动模式,以及如何在代码中通过启动标志控制活动的跳转行为。

4.1.1 Activity的启动和结束

在第 2 章的“2.4.3 跳到另一个页面”一节中,提到通过startActivity方法可以从当前页面跳到新页面,具体格式如“startActivity(new Intent(源页面.this, 目标页面.class));”。由于当时尚未介绍按钮控件,因此只好延迟 3 秒后才自动调用startActivity方法。现在有了按钮控件,就能利用按钮的点击事件去触发页面跳转,譬如以下代码便在重写后的点击方法onClick中执行页面跳转动作。

// 活动类直接实现点击监听器的接口View.OnClickListener
public class ActStartActivity extends AppCompatActivity implements
View.OnClickListener {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_act_start);// setOnClickListener来自于View,故而允许直接给View对象注册点击监听器findViewById(R.id.btn_act_next).setOnClickListener(this);}@Overridepublic 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,是为了在新页面中演示如何关闭页面。众所周知,若要从当前页面回到上一个页面,点击屏幕底部的返回键即可实现,但不是所有场景都使用返回键。比如页面左上角的箭头图标经常代表着返回动作,况且有时页面上会出现“完成”按钮,无论点击箭头图标还是点击完成按钮,都要求马上回到上一个页面。包含箭头图标与“完成”按钮的演示界面如图所示。

既然点击某个图标或者点击某个按钮均可能触发返回动作,就需要App支持在某个事件发生时主动返回上一页。回到上一个页面其实相当于关闭当前页面,因为最开始由A页面跳到B页面,一旦关闭了B页面,App应该展示哪个页面呢?当然是展示跳转之前的A页面了。在Java代码中,调用finish方法即可关闭当前页面,前述场景要求点击箭头图标或完成按钮都返回上一页面,则需给箭头图标和完成按钮分别注册点击监听器,然后在onClick方法中调用finish方法。下面便是添加了finish方法的新页面代码例子:

// 活动类直接实现点击监听器的接口View.OnClickListener
public class ActFinishActivity extends AppCompatActivity implements
View.OnClickListener {@Overrideprotected void onCreate(Bundl![请添加图片描述](https://img-blog.csdnimg.cn/f3cc8c276fb64f22a863f9db995d2551.gif)
e savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_act_finish);// 给箭头图标注册点击监听器,ImageView由View类派生而来findViewById(R.id.iv_back).setOnClickListener(this);// 给完成按钮注册点击监听器,Button也由View类派生而来findViewById(R.id.btn_finish).setOnClickListener(this);}@Overridepublic void onClick(View v) { // 点击事件的处理方法if (v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {finish(); // 结束当前的活动页面}}
}

另外,所谓“打开页面”或“关闭页面”沿用了浏览网页的叫法,对于App而言,页面的真实名称是“活动”—Activity。打开某个页面其实是启动某个活动,所以有startActivity方法却无openActivity方法;关闭某个页面其实是结束某个活动,所以有finish方法却无close方法。

4.1.2 Activity的生命周期

App引入活动的概念而非传统的页面概念,这是有原因的,单从字面意思理解,页面更像是静态的,而活动更像是动态的。犹如花开花落那般,活动也有从含苞待放到盛开再到凋零的生命过程。每次创建新的活动页面,自动生成的Java代码都给出了onCreate方法,该方法用于执行活动创建的相关操作,包括加载XML布局、设置文本视图的初始文字、注册按钮控件的点击监听,等等。onCreate方法所代表的创建动作,正是一个活动最开始的行为,除了onCreate,活动还有其他几种生命周期行为,它们对应的方法说明如下:

  • onCreate:创建活动。此时会把页面布局加载进内存,进入了初始状态。

  • onStart:开启活动。此时会把活动页面显示在屏幕上,进入了就绪状态。

  • onResume:恢复活动。此时活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等。

  • onPause:暂停活动。此时活动页面进入暂停状态(也就是退回就绪状态),无法与用户正常交互。

  • onStop:停止活动。此时活动页面将不在屏幕上显示。

  • onDestroy:销毁活动。此时回收活动占用的系统资源,把页面从内存中清除掉。

  • onRestart:重启活动。处于停止状态的活动,若想重新开启的话,无须经历onCreate的重复创建过程,而是走onRestart的重启过程。

  • onNewIntent:重用已有的活动实例。

上述的生命周期方法,涉及复杂的App运行状态,更直观的活动状态切换过程如图所示。

4.1.3 Activity的启动模式

如果一个Activity已经启动过,并且存在当前应用的Activity任务栈中,启动模式为singleTask, singleInstance或singleTop(此时已在任务栈顶端),那么在此启动或回到这个Activity的时候,不会创建新的实例,也就是不会执行onCreate方法,而是执行onNewIntent方法。

上一小节提到,从第一个活动跳到第二个活动,接着结束第二个活动就能返回第一个活动,可是为什么不直接返回桌面呢?这要从Android的内核设计说起了,系统给每个正在运行的App都分配了活动栈,栈里面容纳着已经创建且尚未销毁的活动信息。鉴于栈是一种先进后出、后进先出的数据结构,故而后面入栈的活动总是先出栈,假设 3 个活动的入栈顺序为:活动A→活动B→活动C,则它们的出栈顺序将变为:活动C→活动B→活动A,可见活动C结束之后会返回活动B,而不是返回活动A或者别的地方。假定某个App分配到的活动栈大小为 3 ,该App先后打开两个活动,此时活动栈的变动情况如图所示。

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

结合图4-7与图4-8的入栈与出栈流程,即可验证结束活动之时的返回逻辑了。不过前述的出入栈情况仅是默认的标准模式,实际上Android允许在创建活动时指定该活动的启动模式,通过启动模式控制活动的出入栈行为。App提供了两种办法用于设置活动页面的启动模式,其一是修改AndroidManifest.xml,在指定的activity节点添加属性android:launchMode,表示本活动以哪个启动模式运行。其二是在代码中调用Intent对象的setFlags方法,表明后续打开的活动页面采用该启动标志。下面分别予以详细说明。

1 .在配置文件中指定启动模式

打开AndroidManifest.xml,给activity节点添加属性android:launchMode,属性值填入standard表示采取标准模式,当然不添加属性的话默认就是标准模式。具体的activity节点配置内容示例如下:

<activity android:name=".JumpFirstActivity" android:launchMode="standard" />

Image From 笔记-Android 开发从入门到实战

接下来举两个例子阐述启动模式的实际应用:在两个活动之间交替跳转、登录成功后不再返回登录页面,分别介绍如下。

1 .在两个活动之间交替跳转假设活动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内部的跳转代码就改成了下面这般:

JumpFirstActivity.java

// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, JumpSecondActivity.class);
// 当栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  // 设置启动标志
startActivity(intent);  // 跳转到意图对象指定的活动页面
当然活动B内部的跳转代码也要设置同样的启动标志:

JumpSecondActivity.java

// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, JumpFirstActivity.class);
// 当栈中存在待跳转的活动实例时,则重新创建该活动的实例,并清除原实例上方的所有实例
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  // 设置启动标志
startActivity(intent);  // 跳转到意图指定的活动页面

这下两个活动的跳转代码都设置了FLAG_ACTIVITY_CLEAR_TOP,运行测试App发现多次跳转之后,每个活动仅会返回一次而已。

2 .登录成功后不再返回登录页面很多App第一次打开都要求用户登录,登录成功再进入App首页,如果这时按下返回键,发现并没有回到上一个登录页面,而是直接退出App了,这又是什么缘故呢?原来用户登录成功后,App便记下用户的登录信息,接下来默认该用户是登录状态,自然不必重新输入用户名和密码了。既然默认用户已经登录,哪里还需要回到登录页面?不光登录页面,登录之前的其他页面包括获取验证码、找回密码等页面都不应回去,每次登录成功之后,整个App就焕然一新仿佛忘记了有登录页面这回事。

对于回不去的登录页面情况,可以设置启动标志FLAG_ACTIVITY_CLEAR_TASK,该标志会清空当前活动栈里的所有实例。不过全部清空之后,意味着当前栈没法用了,必须另外找个活动栈才行,也就是同时设置启动标志FLAG_ACTIVITY_NEW_TASK,该标志用于开辟新任务的活动栈。于是离开登录页面的跳转代码变成下面这样:

// 创建一个意图对象,准备跳到指定的活动页面
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 唤醒。

  • 动态设置启动模式
    在上述所有情况,都是我们在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=1 1|1=1
总结:参加运算的两个对象只要有一个为 1 ,其值为 1 。
例如:3|5即 0000 0011| 0000 0101 = 0000 0111,因此,3|5的值得 7

4.2 在活动之间传递消息

本节介绍如何在两个活动之间传递各类消息,首先描述Intent的用途和组成部分,以及显式Intent和隐式Intent的区别;接着阐述结合Intent和Bundle向下一个活动页面发送数据,再在下一个页面中解析收到的请求数据;然后叙述从下一个活动页面返回应答数据给上一个页面,并由上一个页面解析返回的应答数据。

4.2.1 显式Intent和隐式Intent

上一小节的Java代码,通过Intent对象设置活动的启动标志,这个Intent究竟是什么呢?Intent的中文名是意图,意思是我想让你干什么,简单地说,就是传递消息。Intent是各个组件之间信息沟通的桥梁,既能在Activity之间沟通,又能在Activity与Service之间沟通,也能在Activity与Broadcast之间沟通。总而言之,Intent用于Android各组件之间的通信,它主要完成下列 3 部分工作:

( 1 )标明本次通信请求从哪里来、到哪里去、要怎么走。

( 2 )发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。

( 3 )发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。

为了做好以上工作,就要给意图配上必需的装备,Intent的组成部分见表4-3。

指定意图对象的目标有两种表达方式,一种是显式Intent,另一种是隐式Intent。

1 .显式Intent,直接指定来源活动与目标活动,属于精确匹配在构建一个意图对象时,需要指定两个参数,第一个参数表示跳转的来源页面,即“来源Activity.this”;第二个参数表示待跳转的页面,即“目标Activity.class”。具体的意图构建方式有如下 3 种:

( 1 )在Intent的构造函数中指定,示例代码如下:

Intent intent = new Intent(this, ActNextActivity.class);  // 创建一个目标确定的意图

( 2 )调用意图对象的setClass方法指定,示例代码如下:

Intent intent = new Intent();  // 创建一个新意图
intent.setClass(this, ActNextActivity.class); // 设置意图要跳转的目标活动

( 3 )调用意图对象的setComponent方法指定,示例代码如下:

Intent intent = new Intent();  // 创建一个新意图
// 创建包含目标活动在内的组件名称对象
ComponentName component = new ComponentName(this, ActNextActivity.class);
intent.setComponent(component);  // 设置意图携带的组件信息

2 .隐式Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配通常App不希望向外部暴露活动名称,只给出一个事先定义好的标记串,这样大家约定俗成、按图索骥就好,隐式Intent便起到了标记过滤作用。这个动作名称标记串,可以是自己定义的动作,也可以是已有的系统动作。常见系统动作的取值说明见表4-4。

动作名称既可以通过setAction方法指定,也可以通过构造函数Intent(String action)直接生成意图对象。当然,由于动作是模糊匹配,因此有时需要更详细的路径,比如仅知道某人住在天通苑小区,并不能直接找到他家,还得说明他住在天通苑的哪一期、哪栋楼、哪一层、哪一个单元。Uri和Category便是这样的路径与门类信息,Uri数据可通过构造函数Intent(String action, Uri uri)在生成对象时一起指定,也可通过setData方法指定(setData这个名字有歧义,实际相当于setUri);Category可通过addCategory方法指定,之所以用add而不用set方法,是因为一个意图允许设置多个Category,方便一起过滤。

下面是一个调用系统拨号程序的代码例子,其中就用到了Uri:

String phoneNo = "12345";
Intent intent = new Intent();  // 创建一个新意图
intent.setAction(Intent.ACTION_DIAL);  // 设置意图动作为准备拨号
Uri uri = Uri.parse("tel:" + phoneNo);  // 声明一个拨号的Uri
intent.setData(uri);  // 设置意图前往的路径
startActivity(intent);  // 启动意图通往的活动页面
隐式Intent还用到了过滤器的概念,把不符合匹配条件的过滤掉,剩下符合条件的按照优先顺序调用。譬如创建一个App模块,AndroidManifest.xml里的intent-filter就是配置文件中的过滤器。像最常见的首页活动MainAcitivity,它的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>

4.2.2 向下一个Activity发送数据

上一小节提到,Intent对象的setData方法只指定到达目标的路径,并非本次通信所携带的参数信息,真正的参数信息存放在Extras中。Intent重载了很多种putExtra方法传递各种类型的参数,包括整型、双精度型、字符串等基本数据类型,甚至Serializable这样的序列化结构。只是调用putExtra方法显然不好管理,像送快递一样大小包裹随便扔,不但找起来不方便,丢了也难以知道。所以Android引入了Bundle概念,可以把Bundle理解为超市的寄包柜或快递收件柜,大小包裹由Bundle统一存取,方便又安全。

Bundle内部用于存放消息的数据结构是Map映射,既可添加或删除元素,还可判断元素是否存在。开发者若要把Bundle数据全部打包好,只需调用一次意图对象的putExtras方法;若要把Bundle数据全部取出来,也只需调用一次意图对象的getExtras方法。Bundle对象操作各类型数据的读写方法说明见表 。

接下来举个在活动之间传递数据的例子,首先在上一个活动使用包裹封装好数据,把包裹塞给意图对象,再调用startActivity方法跳到意图指定的目标活动。完整的活动跳转代码示例如下:

ActSendActivity.java

// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this,ActReceiveActivity.class);
Bundle bundle = new Bundle();  // 创建一个新包裹
// 往包裹存入名为request_time的字符串
bundle.putString("request_time", DateUtil.getNowTime());
// 往包裹存入名为request_content的字符串
bundle.putString("request_content", tv_send.getText().toString());
intent.putExtras(bundle);  // 把快递包裹塞给意图
startActivity(intent);  // 跳转到意图指定的活动页面

然后在下一个活动中获取意图携带的快递包裹,从包裹取出各参数信息,并将传来的数据显示到文本视图。下面便是目标活动获取并展示包裹数据的代码例子:

ActReceiveActivity.java

// 从布局文件中获取名为tv_receive的文本视图
TextView tv_receive = findViewById(R.id.tv_receive);
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
// 从包裹中取出名为request_time的字符串
String request_time = bundle.getString("request_time");
// 从包裹中取出名为request_content的字符串
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.2.3 向上一个Activity返回数据

数据传递经常是相互的,上一个页面不但把请求数据发送到下一个页面,有时候还要处理下一个页面的应答数据,所谓应答发生在下一个页面返回到上一个页面之际。如果只把请求数据发送到下一个页面,上一个页面调用startActivity方法即可;如果还要处理下一个页面的应答数据,此时就得分多步处理,详细步骤说明如下:

步骤一,上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作,表示需要处理下一个页面的应答数据,该方法的第二个参数表示请求代码,它用于标识每个跳转的唯一性。跳转代码示

String request = "你吃饭了吗?来我家吃吧";
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, ActResponseActivity.class);
Bundle bundle = new Bundle();  // 创建一个新包裹
// 往包裹存入名为request_time的字符串
bundle.putString("request_time", DateUtil.getNowTime());
// 往包裹存入名为request_content的字符串
bundle.putString("request_content", request);
intent.putExtras(bundle);  // 把快递包裹塞给意图
// 期望接收下个页面的返回数据。第二个参数为本次请求代码
startActivityForResult(intent, 0);

步骤二,下一个页面接收并解析请求数据,进行相应处理。接收代码示例如下:

(完整代码见chapter04\src\main\java\com\example\chapter04\ActResponseActivity.java)// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
// 从包裹中取出名为request_time的字符串
String request_time = bundle.getString("request_time");
// 从包裹中取出名为request_content的字符串
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方法的第一个参数表示应答代码(成功还是失败),第二个参数为携带包裹的意图对象。返回代码示例如下:

String response = "我吃过了,还是你来我家吃";
Intent intent = new Intent();  // 创建一个新意图
Bundle bundle = new Bundle();  // 创建一个新包裹
// 往包裹存入名为response_time的字符串
bundle.putString("response_time", DateUtil.getNowTime());
// 往包裹存入名为response_content的字符串
bundle.putString("response_content", response);
intent.putExtras(bundle);  // 把快递包裹塞给意图
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
finish();  // 结束当前的活动页面

步骤四,上一个页面重写方法onActivityResult,该方法的输入参数包含请求代码和结果代码,其中请求代码用于判断这次返回对应哪个跳转,结果代码用于判断下一个页面是否处理成功。如果下一个页面处理成功,再对返回数据解包操作,处理返回数据的代码示例如下:

// 从下一个页面携带参数返回当前页面时触发。其中requestCode为请求代码,
// resultCode为结果代码,intent为下一个页面返回的意图对象
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent)
{ // 接收返回数据super.onActivityResult(requestCode, resultCode, intent);// 意图非空,且请求代码为之前传的0,结果代码也为成功if (intent!=null && requestCode==0 && resultCode== Activity.RESULT_OK) {Bundle bundle = intent.getExtras(); // 从返回的意图中获取快递包裹// 从包裹中取出名叫response_time的字符串String response_time = bundle.getString("response_time");// 从包裹中取出名叫response_content的字符串String response_content = bundle.getString("response_content");String desc = String.format("收到返回消息:\n应答时间为:%s\n应答内容为:%s",response_time, response_content);tv_response.setText(desc); // 把返回消息的详情显示在文本视图上}
}

结合上述的活动消息交互步骤,运行测试App打开第一个活动页面如图所示。

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

注意:在androidx.appcompat:appcompat:1.3.0中startActivityForResult被标记过时的方法,官方建议使用registerForActivityResult

具体代码如下

public class ActRequestActivity extends AppCompatActivity implements View.OnClickListener {private static final String mRequest = "你睡了吗?来我家睡吧";private ActivityResultLauncher<Intent> register;private TextView tv_response;@Overrideprotected 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 -> {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);}}});}@Overridepublic 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);}
}

4.3 为活动补充附加信息

本节介绍如何在意图之外给活动添加额外的信息,首先可以把字符串参数放到字符串资源文件中,待App运行之时再从资源文件读取字符串值;接着还能在AndroidManifest.xml中给指定活动配置专门的元数据,App运行时即可获取对应活动的元数据信息;然后利用元数据的resource属性配置更复杂的XML定义,从而为App注册在长按桌面之时弹出的快捷菜单。

4.3.1 利用资源文件配置字符串

利用Bundle固然能在页面跳转的时候传送数据,但这仅限于在代码中传递参数,如果要求临时修改某个参数的数值,就得去改Java代码。然而直接修改Java代码有两个弊端:

( 1 )代码文件那么多,每个文件又有许多行代码,一下子还真不容易找到修改的地方。

( 2 )每次改动代码都得重新编译,让Android Studio编译的功夫也稍微费点时间。

有鉴于此,对于可能手工变动的参数,通常把参数名称与参数值的对应关系写入配置文件,由程序在运行时读取配置文件,这样只需修改配置文件就能改变对应数据了。res\values目录下面的strings.xml就用来配置字符串形式的参数,打开该文件,发现里面已经存在名为app_name的字符串参数,它配置的是当前模块的应用名称。现在可于app_name下方补充一行参数配置,参数名称叫作“weather_str”,参数值则为“晴天”,具体的配置内容如下所示:

<string name="weather_str">晴天</string>

接着打开活动页面的Java代码,调用getString方法即可根据“R.string.参数名称”获得指定参数的字符串值。获取代码示例如下:

// 显示字符串资源
private void showStringResource() {String value = getString(R.string.weather_str); // 从strings.xml获取名叫
weather_str的字符串值atv_resource.setText("来自字符串资源:今天的天气是"+value); // 在文本视图上显示文字
}

上面的getString方法来自于Context类,由于页面所在的活动类AppCompatActivity追根溯源来自Context这个抽象类,因此凡是活动页面代码都能直接调用getString方法。然后在onCreate方法中调用showStringResource方法,运行测试App,打开读取页面如图所示,可见从资源文件成功读到了字符串。

4.3.2 利用元数据传递配置信息

尽管资源文件能够配置字符串参数,然而有时候为安全起见,某个参数要给某个活动专用,并不希望其他活动也能获取该参数,此时就不方便到处使用getString了。好在Activity提供了元数据(Metadata)的概念,元数据是一种描述其他数据的数据,它相当于描述固定活动的参数信息。打开AndroidManifest.xml,在测试活动的activity节点内部添加meta-data标签,通过属性name指定元数据的名称,通过属性value指定元数据的值。仍以天气为例,添加meta-data标签之后的activity节点如下所示:

<activity android:name=".MetaDataActivity"><meta-data android:name="weather" android:value="晴天" />
</activity>
元数据的value属性既可直接填字符串,也可引用strings.xml已定义的字符串资源,引用格式形如“@string/字符串的资源名称”。下面便是采取引用方式的activity节点配置:<activity android:name=".MetaDataActivity"> <meta-dataandroid:name="weather"android:value="@string/weather_str" />
</activity>

配置好了activity节点的meta-data标签,再回到Java代码获取元数据信息,获取步骤分为下列 3 步:

  • 调用getPackageManager方法获得当前应用的包管理器。

  • 调用包管理器的getActivityInfo方法获得当前活动的信息对象。

  • 活动信息对象的metaData是Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值。

把上述 3 个步骤串起来,得到以下的元数据获取代码:

// 显示配置的元数据
private void showMetaData() { try {PackageManager pm = getPackageManager(); // 获取应用包管理器 // 从应用包管理器中获取当前的活动信息ActivityInfo act = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);Bundle bundle = act.metaData; // 获取活动附加的元数据信息String value = bundle.getString("weather"); // 从包裹中取出名叫weather的字符串tv_meta.setText("来自元数据信息:今天的天气是"+value); // 在文本视图上显示文字 } catch (Exception e) {e.printStackTrace(); }
}

然后在onCreate方法中调用showMetaData方法,重新运行App观察到的界面如图所示,可见成功获得AndroidManifest.xml配置的元数据。

4.3.3 给应用页面注册快捷方式

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

点击菜单项“扫一扫”,直接打开支付宝的扫码页面;点击菜单项“付钱”,直接打开支付宝的付款页面;点击菜单项“收钱”,直接打开支付宝的收款页面。如此不必打开支付宝首页,即可迅速跳转到常用的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 组菜单项的快捷方式定义,文件内容如下所示:

<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"><shortcutandroid:shortcutId="first"android:enabled="true"android:icon="@mipmap/ic_launcher"android:shortcutShortLabel="@string/first_short"android:shortcutLongLabel="@string/first_long"><!-- targetClass指定了点击该项菜单后要打开哪个活动页面 --><intentandroid:action="android.intent.action.VIEW"android:targetPackage="com.example.chapter04"android:targetClass="com.example.chapter04.ActStartActivity" /><categories android:name="android.shortcut.conversation"/></shortcut><shortcutandroid:shortcutId="second"android:enabled="true"android:icon="@mipmap/ic_launcher"android:shortcutShortLabel="@string/second_short"android:shortcutLongLabel="@string/second_long"><!-- targetClass指定了点击该项菜单后要打开哪个活动页面 --><intentandroid:action="android.intent.action.VIEW"android:targetPackage="com.example.chapter04"android:targetClass="com.example.chapter04.JumpFirstActivity" /><categories android:name="android.shortcut.conversation"/></shortcut><shortcutandroid:shortcutId="third"android:enabled="true"android:icon="@mipmap/ic_launcher"android:shortcutShortLabel="@string/third_short"android:shortcutLongLabel="@string/third_long"><!-- targetClass指定了点击该项菜单后要打开哪个活动页面 --><intentandroid:action="android.intent.action.VIEW"android:targetPackage="com.example.chapter04"android:targetClass="com.example.chapter04.LoginInputActivity" /><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/shortcuts:

<meta-data android:name="android.app.shortcuts"android:resource="@xml/shortcuts" />

这行元数据的作用,是告诉App首页有个快捷方式菜单,其资源内容参见位于xml目录下的shortcuts.xml。完整的activity节点配置示例如下:

<activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><!-- 指定快捷方式。在桌面上长按应用图标,就会弹出@xml/shortcuts所述的快捷菜单 --><meta-dataandroid:name="android.app.shortcuts"android:resource="@xml/shortcuts" />
</activity>

然后把测试应用安装到手机上,回到桌面长按应用图标,此时图标下方弹出如图所示的快捷菜单列表。

Image From 笔记-Android 开发从入门到实战

点击其中一个菜单项,果然跳到了配置的活动页面,证明元数据成功实现了类似支付宝的快捷方式。

4.4 小结

本章主要介绍了活动组件—Activity的常见用法,包括:正确启停活动页面(Activity的启动和结束、Activity的生命周期、Activity的启动模式)、在活动之间传递消息(显式Intent和隐式Intent、向下一个Activity发送数据、向上一个Activity返回数据)、为活动补充附加信息(利用资源文件配置字符串、利用元数据传递配置信息、给应用页面注册快捷方式)。

通过本章的学习,我们应该能掌握以下 3 种开发技能:

( 1 )理解活动的生命周期过程,并学会正确启动和结束活动。

( 2 )理解意图的组成结构,并利用意图在活动之间传递消息。

( 3 )理解元数据的概念,并通过元数据配置参数信息和注册快捷菜单。

4.5 课后练习题(选看)

一、填空题

1 .打开一个新页面,新页面的生命周期方法依次为onCreate→ → 。

2 .关闭现有的页面,现有页面的生命周期方法依次为onPause→ → 。

3 .Intent意图对象的__ _ _ 方法用于指定意图的动作行为。

4 .上一个页面要在 _ __ _方法中处理下一个页面返回的数据。

5 .在AndroidManifest.xml的activity节点添加 __ 标签,表示给该活动设置元数据信息。

二、判断题(正确打√,错误打×)

1 .活动页面处于就绪状态时,允许用户在界面上输入文字。(  )

2 .设置了启动标志Intent.FLAG_ACTIVITY_SINGLE_TOP之后,当栈顶为待跳转的活动实例之时,会重

用栈顶的实例。(  )

3 .隐式Intent直接指定来源活动与目标活动,它属于精确匹配。(  )

4 .调用startActivity方法也能获得下个页面返回的意图数据。(  )

5 .在桌面长按应用图标,会弹出该应用的快捷方式菜单(如果有配置的话)。(  )

三、选择题

1 .在当前页面调用(  )方法会回到上一个页面。

A.finish

B.goback

C.return

D.close

2 .从A页面跳到B页面,再从B页面返回A页面,此时A页面会先执行(  )方法。

A.onCreate

B.onRestart

C.onResume

D.onStart

3 .栈是一种(  )的数据结构。

A.先进先出

B.先进后出

C.后进先出

D.后进后出

4 .Bundle内部用于存放消息的数据结构是(  )。

A.Deque

B.List

C.Map

D.Set

5 .(  )不是meta-data标签拥有的属性。

A.id

B.name

C.resource

D.value

四、简答题

请简要描述意图Intent主要完成哪几项工作。

五、动手练习

请上机实验下列 3 项练习:

1 .创建两个活动页面,分别模拟注册页面和完成页面,先从注册页面跳到完成页面,但是在完成页面按返回键,不能回到注册页面(因为注册成功之后无需重新注册)。

2 .创建两个活动页面,从A页面携带请求数据跳到B页面,B页面应当展示A页面传来的信息;然后B页面向A页面返回应答数据,A页面也要展示B页面返回的信息。

3 .实现类似支付宝的快捷方式,也就是在手机桌面上长按App图标,会弹出快捷方式的菜单列表,点击某项菜单便打开对应的活动页面。

2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第四章:活动Activity相关推荐

  1. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第六章:数据存储

    第 6 章 数据存储 本章介绍Android 4种存储方式的用法,包括共享参数SharedPreferences.数据库SQLite.存储卡文 件.App的全局内存,另外介绍Android重要组件-应 ...

  2. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第五章:中级控件

    第 5 章 中级控件 本章介绍App开发常见的几类中级控件的用法,主要包括:如何定制几种简单的图形.如何使用几种选择按钮.如何高效地输入文本.如何利用对话框获取交互信息等,然后结合本章所学的知识,演示 ...

  3. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第三章:简单控件

    第 3 章 简单控件 本章介绍了App开发常见的几类简单控件的用法,主要包括:显示文字的文本视图.容纳视图的常用布局.响应点击的按钮控件.显示图片的图像视图等.然后结合本章所学的知识,演示了一个实战项 ...

  4. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第二章:Android App 开发基础

    第 2 章 Android App开发基础 本章介绍基于Android系统的App开发常识,包括以下几个方面:App开发与其他软件开发有什么不一 样,App工程是怎样的组织结构又是怎样配置的,App开 ...

  5. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第一章:Android开发环境搭建

    第 1 章 Android开发环境搭建 本章介绍了如何在个人电脑上搭建Android开发环境,主要包括:Android开发的发展历史是怎样的.Android Studio的开发环境是如何搭建的.如何创 ...

  6. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战二:简易登录+找回密码

    在移动互联网时代,用户是每家IT企业最宝贵的资源,对于App而言,吸引用户注册并登录是万分紧要之事,因为用户登录之后才有机会产生商品交易.登录校验通常是用户名+密码组合,可是每天总有部分用户忘记密码, ...

  7. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战三:购物车

    6.5 实战项目:购物车(还未补全图片) 购物车的应用面很广,凡是电商App都可以看到它的身影,之所以选择购物车作为本章的实战项目,除了它使用广泛的特点,更因为它用到了多种存储方式.现在就让我们开启电 ...

  8. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第八章:高级控件

    本章介绍了App开发常用的一些高级控件用法,主要包括:如何使用下拉框及其适配器.如何使用列表 类视图及其适配器.如何使用翻页类视图及其适配器.如何使用碎片及其适配器等.然后结合本章所学 的知识,演示了 ...

  9. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战一:简易计算器

    1.界面设计 Windows计算器,它主要由上半部分的计算结果与下半部分的计算按钮两块区域组成,据此可创建一个界面相似的计算器App,同样由计算结果和计算按钮两部分组成,如图所示. 按照计算器App的 ...

最新文章

  1. java多递归调用_java – 递归调用方法
  2. python开发移动应用_什么是移动应用开发的最佳编程语言?(一)Python?c++?
  3. Facebook推出Pythia 开源 可用于图像及语言分析
  4. java jps都卡死,java长时间运行后,jps失效
  5. 完成端口与高性能服务器程序开发
  6. Kafka 居然还会丢消息?
  7. 乘着创新的浪潮:协同运输机器人编队的基于模型的开发实施方案
  8. afm原子力分析软件_AFM数据处理软件|原子力显微镜配套数据处理软件 nanoscope analysis1.8 官方版 - 极光站...
  9. xshell5和xftp5安装教程
  10. 尼克新书《人工智能简史》全新升级 全方位解读AI历史和未来
  11. QGIS编译---QGIS3.10.6 + Qt5.11.2 + VS2015 ---32位版本
  12. 工业交换机的单模多模是什么意思?
  13. secureCRT上传CentOS镜像
  14. 下载并安装 Metricbeat
  15. 数据分析-C端与B端数据分析的异同
  16. 自搭ngrok服务器
  17. MYSQL环境搭建(windows)
  18. [Tarjan] Tarjan详细介绍(顺手写版本)
  19. mysql必知必会的数据_MySQL必知必会--汇 总 数 据
  20. 海康威视网络摄像头开发流程(一)------ 萤石开放平台的账号申请

热门文章

  1. 停车场微信支付系统 本地服务器,一种基于微信支付的停车自动收费系统
  2. APS系统哪家好(上)
  3. 【听译大师说话】python 创始人访谈录 1: 关于 django 和 ruby
  4. 面试被问到期望工资,怎样机智地表露自己想要的薪酬待遇?
  5. 论文阅读:Instance-aware Semantic Segmentation via Multi-task Network Cascades
  6. 你以为有钱人不快乐?他们都是在骗你的
  7. 四信塔式起重机监控系统应用方案
  8. 饮水机电路-工作剖析
  9. 正版ARM版Win11系统(intel版Win10/11系统)仅支持本站正版pd虚拟机
  10. 《父母爱情》的观后感作文4500字