第2章 常用组件 2.2 在活动之间传递消息

        本节介绍如何在两个活动之间传递各类消息:首先描述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的组成部分见表。

表2-3 Intent组成元素的列表说明
元素名称设置方法说明与用途
ComponentsetComponent组件,它指定意图的来源与目标
ActionsetAction动作,它指定意图的动作行为
DatasetData即Uri,它指定动作要操纵的数据路径
CategoryaddCategory类别,它指定意图的操作类别
TypesetType数据类型,它指定消息的数据类型
ExtrasputExtras扩展信息,它指定装载的包裹信息
FlagssetFlags标志位,它指定活动的启动标志

        指定意图对象的目标有两种表达方式,一种是显式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便起到了标记过滤作用。这个动作名称标记串,可以是自己定义的动作,也可以是已有的系统动作。常见系统动作的取值说明见表。

表2-4 常见系统动作的取值说明
Intent类的系统动作常量名系统动作的常量值说明
ACTION_MAINandroid.intent.action.MAINApp启动时的入口
ACTION_VIEWandroid.intent.action.VIEW向用户显示数据
ACTION_SENDandroid.intent.action.SEND分享内容
ACTION_CALLandroid.intent.action.CALL直接拨号
ACTION_DIALandroid.intent.action.DIAL准备拨号
ACTION_SENDTOandroid.intent.action.SENDTO发送短信
ACTION_ANSWERandroid.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对象操作各类型数据的读写方法说明见表。

表2-5 Bundle 对各类型数据的读写方法说明
数据类型读方法写方法
整型数getIntputInt
浮点数getFloatputFloat
双精度数getDoubleputDouble
布尔值getBooleanputBoolean
字符串getStringputString
字符串数组getStringArrayputStringArray
字符串列表getStringArrayListputStringArrayList
可序列化结构getSerializableputSerializable

        接下来举个在活动之间传递数据的例子,首先在上一个活动使用包裹封装好数据,把包裹交给意图对象,再调用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); // 设置图像视图的位图对象
            }
        }
    }

}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值