目录
1.显示Intent,直接指定来源活动与目标活动,属于精确匹配
2.隐式Intent,没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配
本文介绍如何在两个活动之间传递各类消息:首先描述Intent的用途和组成部分,以及显式Intent和隐式Intent的区别;然后阐述结合Intent和Bundle向下一个活动页面发送数据,再由下一个活动页面返回应答数据给上一个页面;最后叙述如何去使用新的registerForActivityResult方法简化活动交互过程。
显式Intent和隐式Intent
Intent的中文名是意图,意思是我想让你干什么,简单的说,就是传递消息。Intent是各个组件之间信息沟通的桥梁,既能在Activity之间沟通,又能在Activity与Service之间沟通,也能在Activity与Broadcast之间沟通。总而言之,Intent用于Android各组件之间发通信,它主要完成下列3部分工作:
- 标明本次通信请求从哪里来、到哪里去、要怎么走。
- 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
- 发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答数据内容。
元素名称 | 设置方法 | 说明与用途 |
---|---|---|
Component | setComponent | 组件,它指定意图的来源与目标 |
Action | setAction | 动作,它指定意图的动作行为 |
Data | setData | 即Uri,它指定动作要操纵的数据途径 |
Category | addCategory | 类别,它指定意图的操作类别 |
Type | setType | 数据类型,它指定消息的数据类型 |
Extras | putExtras | 拓展信息,它指定装载的包裹信息 |
Flags | setFlags | 标志位,它指定活动的启动标志 |
指定意图对象的目标有两种表达方式,一种是显式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便起到了标记过滤作用。这个动作名称标记串,可以是自己定义的动作,也可以是已有的系统动作。
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方法指定;Category可以通过addCategory方法指定,之所以用add而不用set方法,是因为一个意图允许设置多个Category,方便一起过滤。
下面是一个调用系统拨号程序的代码例子,其中就用到了Uri:
String phoneN = "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就是配置文件里的过滤器。像最常见的首页活动MainActivity,它的activity节点下面便设置了action和catrgory的过滤条件。其中android.intent.action.MAIN的入口动作,而android.intent.category.LAUNCHER表示在桌面上显示App图标。
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
普通的活动数据交互
Android引入了Bundle概念,可以把Bundle理解为超市的寄包柜或快递收件柜,大小包裹由Bundle统一存取,方便又安全。
Bundle内部用与存放消息的数据机构是Map映射,即可添加或删除元素,还可判断元素是否存在。开发者若要把Bundle数据全部打包好,只需要调用一次意图对象的putExtras方法;若要把Bundle数据全部取出来,也只需调用一次意图对象的putExtras方法。
数据类型 | 读方法 | 写方法 |
---|---|---|
整型数 | getInt | putInt |
浮点数 | getFloat | putFloat |
双精度数 | getDouble | putDouble |
布尔值 | getBoolean | putBoolean |
字符串 | getString | putString |
字符串数组 | getStringArray | putStringArray |
字符串列表 | getStringArrayList | putStringArrayList |
可序列化结构 | getSerializable | putSerializable |
接下来举个在活动之间传递数据的例子,首先在上一个活动使用包裹封装好数据,把包裹交给意图对象,再调用startActivity方法跳转到意图指定的目标活动。
@Override
public void onClick(View v){
if (v.getId()==R.id.btn_SendMessage1){
//创建一个意图对象,准备跳转到指定的活动页面
Intent intent = new Intent(this,MessageChangeReceiveType1.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); //转跳到意图指定的活动页面
}
}
然后再下个活动中获取意图携带的包裹,从包裹去除各参数信息,并将传来的数据显示到文本视图。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message_change_receive_type1);
//从布局文件中获取名为tv_receive的文本视图
TextView tv_receive = findViewById(R.id.tv_receive1);
//从上一个活动传来的意图中获取包裹
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); //把请求消息的详情显示再文本视图上
}
如果只把请求数据发送到下一个活动,上一个活动调用startActivity方法即可;如果还要处理下个活动的应答数据,此时就得分多步处理。
1.上一个活动打包好请求数据,调用startActivityForResult方法执行跳转动作,表示需要处理下一个活动的应答数据,该方法第二个参数表示请求代码,它用于标识每个跳转的唯一性。
@Override
public void onClick(View v){
if (v.getId()==R.id.btn_SendMessage2){
String request = "你吃饭了吗?来我家吃吧";
Intent intent = new Intent(this,MessageChangeReceiveType2.class);
Bundle bundle = new Bundle();
bundle.putString("request_time",DateUtil.getNowTime());
bundle.putString("request_content",request);
intent.putExtras(bundle);
//期望接收下一个活动的返回数据。第二个参数为本次请求代码
startActivityForResult(intent,0);
}
}
2.下一个活动接收并解析请求数据,进行相应处理。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message_change_receive_type2);
TextView tv_request=findViewById(R.id.tv_receive2);
findViewById(R.id.btn_ReceiveMessage2).setOnClickListener(this);
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);
}
3.下一个活动在返回上一个活动时,打包应答数据并调用setResult方法返回数据包裹。setResult方法的第一个参数表示应答代码(成功还是失败),第二个参数为携带包裹的意图对象。
@Override
public void onClick(View v){
if (v.getId()==R.id.btn_ReceiveMessage2){
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(); //结束当前的活动页面
}
}
4.上一个活动重写方法onActivityResult,该方法的参数包含请求代码和结果代码,其中请求代码用于判断这次返回对应哪个跳转,结果代码用于判断下一个活动是否处理成功。如果下一个活动处理成功,在对返回数据进行解包操作。
//从下一个活动携带参数返回当前活动时触发
@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();
TextView tv_answer = findViewById(R.id.tv_answer);
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_answer.setText(desc);
}
}
改进后的活动数据交互
从appcompat1.3.0开始,startActivityForResult方法被标记为已废弃,官方建议改用registerForActivityResult方法。具体的使用步骤说明如下:
1.先声明一个活动结果启动器对象ActivityResultLauncher
private ActivityResultLauncher mLauncher; // 声明一个活动结果启动器对象
2.调用registerForActivityResult方法去注册一个善后工作的活动结果启动器,并指定对活动返回数据的处理过程,也就是第一个参数传入ActivityResultContracts.StartActivityForResult对象,第二个参数填入onActivityResult要做的事情。
// 注册一个善后工作的活动结果启动器
mLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode()==RESULT_OK && result.getData()!=null) {
Bundle bundle = result.getData().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); // 把返回消息的详情显示在文本视图上
}
});
3.调用启动器对象的launch方法,传入封装了参数信息的意图对象,开始执行启动器的跳转与回调处理。
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_SendMessage3) {
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, MessageChangeReceiveType3.class);
Bundle bundle = new Bundle(); // 创建一个新包裹
// 往包裹存入名叫request_time的字符串
bundle.putString("request_time", DateUtil.getNowTime());
// 往包裹存入名叫request_content的字符串
bundle.putString("request_content", mRrequest);
intent.putExtras(bundle); // 把快递包裹塞给意图
mLauncher.launch(intent); // 活动结果启动器开动了
}
}