本节介绍如何在两个活动之间传递各类消息:首先描述Intent的用途和组成部分,以及显示Intent和隐式Intent的区别;然后阐述结合Intent和Bundle向下一个活动页面发送数据,再由下一个活动页面返回应答数据给上一个页面;最后叙述如何使用新的registerForActivityResult方法简化活动交互过程。
2.2.1 显示 Intent 和隐式 Intent
上一小节的Java代码通过Intent对象设置活动的启动标志,这个Intent究竟是什么呢?Intent的中文名是意图,意思是我想让你干什么,简单的说,就是传递消息。Intent是各个组件之间信息沟通的桥梁,既能在Activity之间沟通,又能在Activity与Service之间沟通,也能在Activity与Broadcast之间沟通。总而言之,Intent用于Android各组件之间的通信,它主要完成下列3部分工作:
(1)标明本次通信请求从哪里来、到哪里去、要怎么走。
(2)发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
(3)发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容。
为了做好以上工作,就要给意图配上必需的装备,Intent的组成部分见表。
元素名称 | 设置方法 | 说明与用途 |
---|---|---|
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方法指定(setData这个名字有歧义,实际相当于setUri);Category可以通过addCategory方法指定,之所以用add而不用set方法,是因为一个意图允许设置多个Category,方便一起过滤。
下面是一个调用系统拨号程序的代码例子,其中就用到了Uri:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ActionUriActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="点击一下按钮将向号码12345发起请求"
android:textColor="#000000"
android:textSize="17sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_dial"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="跳到拨号页面"
android:textColor="@color/black"
android:textSize="17sp" />
<Button
android:id="@+id/btn_sms"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="跳到短信页面"
android:textColor="@color/black"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>
package com.example.chapter04;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class ActionUriActivity extends AppCompatActivity implements View
.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_action_uri);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
findViewById(R.id.btn_dial).setOnClickListener(this);
findViewById(R.id.btn_sms).setOnClickListener(this);
}
@Override
public void onClick(View v) {
String phoneNo = "12345";
if (v.getId()==R.id.btn_dial){// 点击了“跳到拨号页面”按钮
Intent intent = new Intent();// 创建一个新意图
intent.setAction(Intent.ACTION_DIAL);// 设置意图动作为准备拨号
Uri uri = Uri.parse("tel:"+phoneNo);// 声明一个拨号的Uri
intent.setData(uri);// 设置意图前往的路径
startActivity(intent);// 启动意图通往的活动页面
} else if (v.getId()==R.id.btn_sms) {// 点击了“跳到短信页面”按钮
Intent intent = new Intent();// 创建一个新意图
intent.setAction(Intent.ACTION_SENDTO);// 设置意图动作为发短信
Uri uri = Uri.parse("smsto:"+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"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
2.2.2 普通的活动数据交互
上一小节提到,Intent对象的setData方法只指定到达目标的路径,并非本次通信所携带的参数信息,真正的参数信息存放在Extras中。Intent重载了很多种putExtra方法传递各种类型的参数,包括整型、双精度型、字符串等基本数据类型,甚至Serializable这样的序列化结构。只是调用putExtra方法显然不好管理,像送快递一样大小包裹随便扔,不但找起来不方便,丢了也难以知道。所以Android引入了Bundle概念,可以把Bundle理解为超市的寄包柜或快递收件柜,大小包裹由Bundle统一存取,方便又安全。
Bundle内部用于存放消息的数据结构是Map映射,既可添加或删除元素,还可判断元素是否存在。开发者若要把Bundle数据全部打包好,只需调用一次意图对象的putExtras方法;若要把Bundle数据全部取出来,也只需调用一次意图对象的getExtras方法。Bundle对象操作各类型数据的读写方法说明见表。
数据类型 | 读方法 | 写方法 |
---|---|---|
整型数 | getInt | putInt |
浮点数 | getFloat | putFloat |
双精度数 | getDouble | putDouble |
布尔值 | getBoolean | putBoolean |
字符串 | getString | putString |
字符串数组 | getStringArray | putStringArray |
字符串列表 | getStringArrayList | putStringArrayList |
可序列化结构 | getSerializable | putSerializable |
接下来举个在活动之间传递数据的例子,首先在上一个活动使用包裹封装好数据,把包裹交给意图对象,再调用startActivity方法跳转到意图指定的目标活动。完整的活动跳转代码示例如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ActSendActivity">
<TextView
android:id="@+id/tv_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="今天的天气真不错"
android:textColor="#000000"
android:textSize="17sp" />
<Button
android:id="@+id/btn_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="发送以上文字"
android:textColor="#000000"
android:textSize="17sp" />
</LinearLayout>
package com.example.chapter04;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.chapter04.util.DateUtil;
public class ActSendActivity extends AppCompatActivity implements View
.OnClickListener {
private TextView tv_send;// 声明一个文本视图对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_act_send);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// 从布局文件中获取名叫tv_send的文本视图
tv_send = findViewById(R.id.tv_send);
findViewById(R.id.btn_send).setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v.getId()==R.id.btn_send){
// 创建一个意图对象,准备跳到指定的活动页面
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);// 跳转到意图指定的活动页面
}
}
}
然后在下一个活动中获取意图携带的包裹,从包裹取出各参数信息,并将传来的数据显示到文本视图。下面便是目标活动获取并展示包裹数据的代码例子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ActReceiveActivity">
<TextView
android:id="@+id/tv_receive"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:textColor="#000000"
android:textSize="17sp" />
<Button
android:id="@+id/btn_receive"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="知道了"
android:textColor="#000000"
android:textSize="17sp" />
</LinearLayout>
package com.example.chapter04;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class ActReceiveActivity extends AppCompatActivity implements View
.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_act_receive);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// 从布局文件中获取名叫tv_receive的文本视图
TextView tv_receive = findViewById(R.id.tv_receive);
findViewById(R.id.btn_receive).setOnClickListener(this);
// 从上一个页面传来的意图中获取快递包裹
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);
}
@Override
public void onClick(View v) {
if (v.getId()==R.id.btn_receive){
finish();// 结束当前的活动页面
}
}
}
代码编写完毕,运行测试App,打开上一个活动页面,如图所示。
单击页面上的 “发送以上文字” 按钮跳转到下一个活动页面,如图所示,根据展示文本可知正确获得了传来的数据。
数据传递经常是相互的,上一个活动不但把请求数据发送到下一个活动,有时候还要处理下一个活动的应答数据,所谓应答发生在下一个活动返回到上一个活动之际。如果只把请求数据发送到下一个活动,上一个活动调用startActivity方法即可;如果还要处理下一个活动的应答数据,此时就得分多步处理,详细步骤说明如下:
01 上一个活动打包好请求数据,调用startActivityForResult方法执行跳转动作,表示需要处理下一个活动的应答数据,该方法的第二个参数表示请求代码,它用于标识每个跳转的唯一性。跳转代码示例如下:
private String mRrequest = "你吃饭了吗?来我家吃吧";
// 创建一个意图对象,准备跳到指定的活动页面
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",mRrequest);
// 把快递包裹塞给意图
intent.putExtras(bundle);
// 期望接收下个页面的返回数据。第二个参数为本次请求代码
startActivityForResult(intent,0);
02 下一个活动接收并解析请求数据,进行相应处理。接收代码示例如下:
// 从上一个页面传来的意图中获取快递包裹
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);// 把请求消息的详情显示在文本视图上
03 下一个活动在返回上一个活动时,打包应答数据并调用setResult方法返回数据包裹。setResult方法的第一个参数表示应答代码(成功还是失败),第二个参数为携带包裹的意图对象。返回代码示例如下:
private String mRresponse = "我吃过了,还是你来我家吃";
// 创建一个新意图
Intent intent = new Intent();
// 创建一个新包裹
Bundle bundle = new Bundle();
// 往包裹存入名叫response_time的字符串
bundle.putString("response_time", DateUtil.getNowTime());
// 往包裹存入名叫response_content的字符串
bundle.putString("response_content",mRresponse);
// 把快递包裹塞给意图
intent.putExtras(bundle);
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK,intent);
finish();// 结束当前的活动页面
04 上一个活动重写方法onActivityResult,该方法的参数包含请求代码和结果代码,其中请求代码用于判断这次返回对应哪个跳转,结果代码用于判断下一个活动是否处理成功。如果下一个活动处理成功,再对返回数据进行解包操作,处理返回数据的代码示例如下:
// 从下一个页面携带参数返回当前页面时触发。其中requestCode为请求代码,
// resultCode为结果代码,intent为下一个页面返回的意图对象
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 意图非空,且请求代码为之前传的0,结果代码也为成功
if (data!=null&&requestCode==0&&resultCode== ActRequestActivity.RESULT_OK){
// 从返回的意图中获取快递包裹
Bundle bundle = data.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,打开的第一个活动页面如图所示。
点击 “传送请求数据” 按钮跳转到第二个活动页面,如图所示,
可见第二个活动页面收到了请求数据,然后点击第二个活动页面的 “返回应答数据” 按钮,回到第一个活动页面,如图所示,
可见第一个活动页面成功收到了第二个活动页面的应答数据。
2.2.3 改进后的活动数据交互
在两个活动之间传递消息,正常使用startActivityForResult搭配onActivityResult就好了,可是从appcompat1.3.0 开始,startActivityForResult方法被标记为已废弃,官方建议改用registerForActivityResult方法。具体的使用步骤说明如下:
01 先声名一个活动结果启动器对象ActivityResultLauncher,举例如下:
private ActivityResultLauncher mLauncher;// 声明一个活动结果启动器对象
02 调用regissterForActivityResult方法注册一个善后工作的活动结果启动器,并指定对活动返回数据的处理过程,也就是第一个参数传入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);// 把返回消息的详情显示在文本视图上
}
});
03 调用启动器对象的launch方法,传入封装了参数信息的意图对象,开始执行启动器的跳转与回调处理。代码如下:
// 创建一个意图对象,准备跳到指定的活动页面
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", mRrequest);
intent.putExtras(bundle); // 把快递包裹塞给意图
mLauncher.launch(intent); // 活动结果启动器开动了
完整代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RegisterResultActivity">
<TextView
android:id="@+id/tv_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:textColor="#000000"
android:textSize="17sp" />
<Button
android:id="@+id/btn_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="传送请求数据"
android:textColor="#000000"
android:textSize="17sp" />
<TextView
android:id="@+id/tv_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:textColor="#000000"
android:textSize="17sp" />
</LinearLayout>
package com.example.chapter04;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.chapter04.util.DateUtil;
public class RegisterResultActivity extends AppCompatActivity implements View.OnClickListener {
private String mRrequest = "你吃饭了吗?来我家吃吧";
private TextView tv_response; // 声明一个文本视图对象
private ActivityResultLauncher mLauncher;// 声明一个活动结果启动器对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_register_result);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
// 从布局文件中获取名叫tv_request的文本视图
TextView tv_request = findViewById(R.id.tv_request);
tv_request.setText("待发送的消息为:"+mRrequest);
// 从布局文件中获取名叫tv_response的文本视图
tv_response = findViewById(R.id.tv_response);
findViewById(R.id.btn_request).setOnClickListener(this);
// 注册一个善后工作的活动结果启动器
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);// 把返回消息的详情显示在文本视图上
}
});
}
@Override
public void onClick(View v) {
if (v.getId()==R.id.btn_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", mRrequest);
intent.putExtras(bundle); // 把快递包裹塞给意图
mLauncher.launch(intent); // 活动结果启动器开动了
}
}
}
以上的活动结果启动器使用步骤,看起来并未简化多少代码,不见得带来什么方便。不过在某些特殊场合,运用活动结果启动器倒是能收到奇效。比如到系统相册挑选某张图片,调用startActivityForResult方法的话,活动跳转代码是下面这样的:
findViewById(R.id.btn_choose_common).setOnClickListener(v -> {
// 创建一个内容获取动作的意图(准备跳到系统相册)
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
startActivityForResult(intent,CHOOSE_CODE);
});
然后重写onActivityResult方法,依次判断resultCode和requestCode,校验通过后再展示图片,回调代码示例如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode==RESULT_OK && requestCode==CHOOSE_CODE) { // 从相册回来
if (intent.getData() != null) {
Uri uri = intent.getData(); // 获得已选择照片的路径对象
// 根据指定图片的uri,获得自动缩小后的位图对象
Bitmap bitmap = BitmapUtil.getAutoZoomImage(this, uri);
iv_photo.setImageBitmap(bitmap); // 设置图像视图的位图对象
}
}
}
此时利用活动结果启动器加以改造,可在调用registerForActivityResult方法时,第一个参数传入ActivityResultContracts.GetContent对象,第二个参数填入图片展示过程。之后调用启动器的launch方法传入文件类型 “image/*” ,即可完成从相册挑选并展示图片的功能。详细代码示例如下:
// 注册一个善后工作的活动结果启动器,获取指定类型的内容
ActivityResultLauncher launcher = registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) {
// 根据指定图片的uri,获得自动缩小后的位图对象
Bitmap bitmap = BitmapUtil.getAutoZoomImage(this, uri);
iv_photo.setImageBitmap(bitmap); // 设置图像视图的位图对象
}
});
完成代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ChoosePhotoActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_choose_common"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="普通选择图片"
android:textColor="#000000"
android:textSize="17sp" />
<Button
android:id="@+id/btn_choose_register"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="注册相册结果"
android:textColor="#000000"
android:textSize="17sp" />
</LinearLayout>
<ImageView
android:id="@+id/iv_photo"
android:layout_width="match_parent"
android:layout_height="360dp" />
</LinearLayout>
package com.example.chapter04.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import android.graphics.Matrix;
import java.io.InputStream;
public class BitmapUtil {
private final static String TAG = "BitmapUtil";
// 获得旋转角度之后的位图对象
public static Bitmap getRotateBitmap(Bitmap bitmap, float rotateDegree) {
Matrix matrix = new Matrix(); // 创建操作图片用的矩阵对象
matrix.postRotate(rotateDegree); // 执行图片的旋转动作
// 创建并返回旋转后的位图对象
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
}
// 获得比例缩放之后的位图对象
public static Bitmap getScaleBitmap(Bitmap bitmap, double scaleRatio) {
int new_width = (int) (bitmap.getWidth() * scaleRatio);
int new_height = (int) (bitmap.getHeight() * scaleRatio);
// 创建并返回缩放后的位图对象
return Bitmap.createScaledBitmap(bitmap, new_width, new_height, false);
}
// 获得自动缩小后的位图对象
public static Bitmap getAutoZoomImage(Context ctx, Uri uri) {
Log.d(TAG, "getAutoZoomImage uri="+uri.toString());
Bitmap zoomBitmap = null;
// 打开指定uri获得输入流对象
try (InputStream is = ctx.getContentResolver().openInputStream(uri)) {
// 从输入流解码得到原始的位图对象
Bitmap originBitmap = BitmapFactory.decodeStream(is);
int ratio = originBitmap.getWidth()/2000+1;
// 获得比例缩放之后的位图对象
zoomBitmap = getScaleBitmap(originBitmap, 1.0/ratio);
} catch (Exception e) {
e.printStackTrace();
}
return zoomBitmap;
}
}package com.example.chapter04.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import android.graphics.Matrix;
import java.io.InputStream;
public class BitmapUtil {
private final static String TAG = "BitmapUtil";
// 获得旋转角度之后的位图对象
public static Bitmap getRotateBitmap(Bitmap bitmap, float rotateDegree) {
Matrix matrix = new Matrix(); // 创建操作图片用的矩阵对象
matrix.postRotate(rotateDegree); // 执行图片的旋转动作
// 创建并返回旋转后的位图对象
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
bitmap.getHeight(), matrix, false);
}
// 获得比例缩放之后的位图对象
public static Bitmap getScaleBitmap(Bitmap bitmap, double scaleRatio) {
int new_width = (int) (bitmap.getWidth() * scaleRatio);
int new_height = (int) (bitmap.getHeight() * scaleRatio);
// 创建并返回缩放后的位图对象
return Bitmap.createScaledBitmap(bitmap, new_width, new_height, false);
}
// 获得自动缩小后的位图对象
public static Bitmap getAutoZoomImage(Context ctx, Uri uri) {
Log.d(TAG, "getAutoZoomImage uri="+uri.toString());
Bitmap zoomBitmap = null;
// 打开指定uri获得输入流对象
try (InputStream is = ctx.getContentResolver().openInputStream(uri)) {
// 从输入流解码得到原始的位图对象
Bitmap originBitmap = BitmapFactory.decodeStream(is);
int ratio = originBitmap.getWidth()/2000+1;
// 获得比例缩放之后的位图对象
zoomBitmap = getScaleBitmap(originBitmap, 1.0/ratio);
} catch (Exception e) {
e.printStackTrace();
}
return zoomBitmap;
}
}
package com.example.chapter04;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.ImageView;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.chapter04.util.BitmapUtil;
public class ChoosePhotoActivity extends AppCompatActivity {
private final static String TAG = "ChoosePhotoActivity";
private int CHOOSE_CODE = 3;// 只在相册挑选图片的请求码
private ImageView iv_photo;// 声明一个图像视图对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_choose_photo);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
iv_photo = findViewById(R.id.iv_photo);
findViewById(R.id.btn_choose_common).setOnClickListener(v -> {
// 创建一个内容获取动作的意图(准备跳到系统相册)
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");
startActivityForResult(intent,CHOOSE_CODE);
});
// 注册一个善后工作的活动结果启动器,获取指定类型的内容
ActivityResultLauncher launcher = registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
if (uri != null) {
// 根据指定图片的uri,获得自动缩小后的位图对象
Bitmap bitmap = BitmapUtil.getAutoZoomImage(this, uri);
iv_photo.setImageBitmap(bitmap); // 设置图像视图的位图对象
}
});
// 点击按钮时触发活动结果启动器,传入待获取内容的文件类型
findViewById(R.id.btn_choose_register).setOnClickListener(v -> launcher.launch("image/*"));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode==RESULT_OK && requestCode==CHOOSE_CODE) { // 从相册回来
if (intent.getData() != null) {
Uri uri = intent.getData(); // 获得已选择照片的路径对象
// 根据指定图片的uri,获得自动缩小后的位图对象
Bitmap bitmap = BitmapUtil.getAutoZoomImage(this, uri);
iv_photo.setImageBitmap(bitmap); // 设置图像视图的位图对象
}
}
}
}