安卓 onActivityResult 废弃,registerForActivityResult 使用详解

文章目录

  • onActivityResult 存在的问题
  • registerForActivityResult 有哪些改进
  • registerForActivityResult 实战
    • registerForActivityResult 自定义使用
    • registerForActivityResult 开箱即用
      • StartActivityForResult
      • GetContent
  • 后记
    • 注意事项
    • 附录

  安卓的兼容性是出了名的低,原因就在于它经常喜欢出一个版本就换一个 API。终于,连 Activity 之间数据回传的方法 onActivityResult 也废弃了。安卓官方给出的解决方案是使用 registerForActivityResult 来代替 onActivityResult

  registerForActivityResult 的使用流程比原先的 onActivityResult 要复杂很多,但理解了之后,发现这种新的方式确实更优雅。

  为了能让读者理解,这里先回顾原来 onActivityResult 的使用方式,然后再来对比讲解 registerForActivityResult

onActivityResult 存在的问题

  为了便于说明,这里假设 活动 A 调用了 活动 B,然后活动 B 生命结束,将结果回传至活动 A。

  在这个过程中,A 需要在调用时向 B 传递一个 请求码,这样在 B 返回时就会自动携带那个请求码。同时 B 需要提供一个 返回码,来代表 B 中返回的具体数据 Intent 作一个分类。

  • 活动 A
Intent intent = new Intent(AActivity.this, BActivity.class);
startActivityForResult(intent, requestCode);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == SOME_REQUEST_CODE && resultCode == Activity.RESULT_OK) {if (data != null) {String value = data.getStringExtra("key");// 在这里处理传递过来的信息}}
}
  • 活动 B
Intent resultIntent = new Intent();
resultIntent.putExtra("key", value);
setResult(Activity.RESULT_OK, resultIntent);

  可以看出,在这个过程中,请求码 才是必要的参数,因为活动 A 有可能在不同情况下启动不同的活动,所以需要标记究竟是哪个活动返回到 A 的。而 返回码 只是一种安卓额外提供的对回传数据的分类,这个参数实际上不是必要的,因为回传数据内部自己就可以自行分类。另外,此值固定设置为 int 类型也非常不合理。

  不仅如此,onActivityResult 直接作为 Activity 的重载方法,耦合度过大。onActivityResult 会直接接管所有活动的回传业务,这是非常不合适的。正确的设计方案应该是让每一个返回至活动 A 的活动单独在一个方法中处理回传业务,这样不同的活动之间就可以非侵入解耦,而不是将它们集中在一起统一管理。

  可以看出,onActivityResult 的设计其实确实是有很多问题的,只不过这个 API 过于古老,所以广泛用了很久。

registerForActivityResult 有哪些改进

  没有任何开发者喜欢官方一出一个新版本,就照官方的指示更换一次 API。这是没有技术含量的事情。如果一个新设计没有重大突破,它也没有替代老古董的必要。registerForActivityResult 相比于 onActivityResult 做了很多设计上的改进。尽管使用流程变得更复杂,但实际上好用很多。

  registerForActivityResult 使用了 责任链模式。责任链模式在很多通信框架中都有广泛使用,如 Netty 等。安卓 Activity 的回传过程如果有更复杂的业务需求,其中就涉及对数据的编码与解码。registerForActivityResult 就支持对回传数据的解码及解码之后的业务处理。这一点,它和 onActivityResult 不同,onActivityResult 是把返回码写死为 int 类型,而 registerForActivityResult 虽然也是把返回码写死为 int 类型,但它由于提供了一个 编解码器,因此支持将回传数据转化为任意的类型。

  此外,registerForActivityResult 会直接与要启动的 Activity 相绑定,这意味着,对 registerForActivityResult 来说,它不需要提供 请求码 来标记不同的 Activity。因为 registerForActivityResult 并不是 Activtity 重载方法,它支持多实例,因此可以让不同活动的回调代码相互隔离,而不是像 onActivityResult 集中在一起统一管理。

registerForActivityResult 实战

  纸上得来终觉浅,没有实战的讲解没有任何意义。这里结合具体代码来详细介绍 registerForActivityResult 的使用。

registerForActivityResult 自定义使用

  1. registerForActivityResult 是 Activtity 的一个方法,它可以生成一个 ActivityResultLauncher<Intent> 对象,该对象的 launch 方法可以启动一次 Activtity 调用流程。该对象可以启动一个 Activtity 并与之绑定,这样,将这个启动的 Activtity 回传时,接收回传数据的代码就无需使用 请求码 了。

    可以向 launch 提供一个实参来表示本次 Activtity 调用流程的输入。这个输入可以是任意类型,也可以为 null。

    // 这里的 this 指向的是一个 Activity
    ActivityResultLauncher<Intent> activityLauncher = this.registerForActivityResult(...);
    activityLauncher.launch(input);
    
  2. 然后,当需要接收处理回传数据时,可以在 registerForActivityResult(...) 中提供回调来处理。

    其中,registerForActivityResult 接受两个参数,第一个就是笔者前面提到的 编解码器,第二个就是解码之后的业务处理。

    (但是,contract 在英语的意思并不是解码与解码。虽然这里就实际使用来说,意译成 编解码器 会更加望文生义一些,但直译成 协议 可能更符合翻译界的 信达雅 规则。因此,本文后面将使用 协议 一词,但实际上这里的 contract 是一种 编解码器。)

    @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(@NonNull ActivityResultContract<I, O> contract,@NonNull ActivityResultCallback<O> callback) {return registerForActivityResult(contract, mActivityResultRegistry, callback);
    }
    

    可以看出,上面的形参 contract 就是 协议,它有两个泛型形参,第一个代表输入,第二个代表输出。输入就是前面向 launch 方法提供的输入。而输出则会向形参 callback 传递。

    而形参 callback 就是解码之后的业务处理,它有一个泛型形参,代表从 contract 解码之后的可操作数据。

  3. 自定义一个 协议 ActivityResultContract<I, O>,它有两个抽象方法,其中,方法 createIntent 会在启动一个新 Activity 之前被调用,它代表对输入数据的编码。因此,它的返回值被固定为 Intent,代表需要启动的 Activity。

    方法 parseResult 则会在启动的那个 Activity 结束,返回到原 Activity 时调用,它代表对输出数据的解码。因此,它也有一个为 Intent 类型的形参。而另一个形参 resultCode,则是启动的那个 Activity 在结束前设置的,这一点与原先使用 onActivityResult 时是一样的。

    可以看出,整个过程不需要 请求码,因为 ActivityResultLauncher 已经与启动的 Activity 进行了绑定,所以无需使用 请求码 将不同的 Activity 分开。

    abstract class ActivityResultContract<I, O> {abstract fun createIntent(context: Context, input: I): Intentabstract fun parseResult(resultCode: Int, intent: Intent?): O// ...省略其它内容...
    }
    

    下面假设前面向 launch 方法提供的输入为 Integer 类型,而输出设置为 String 类型。这里,先用方法 createIntent 启动了一个新 Activity,然后在方法 parseResult 中对回传数据进行解码处理。

    import android.content.Context;
    import android.content.Intent;import androidx.activity.result.contract.ActivityResultContract;public class ActivityResultHandler extends ActivityResultContract<Integer, String> {@NonNull@Overridepublic Intent createIntent(Context context, Integer input) {// 设置要启动的 ActivityIntent intent = new Intent(context, BActivity.class);intent.putExtra("INPUT_VALUE", input);return intent;}@NonNull@Overridepublic String parseResult(int resultCode, Intent intent) {// 处理回传数据解码业务if (resultCode == RESULT_OK && intent != null) {return intent.getStringExtra("MESSAGE");}return null;}
    }
    
  4. 自定义解码之后的业务处理。它有一个名称看起来很熟悉的方法 onActivityResult,它的形参正是上面 协议 中输出的内容。

    public interface ActivityResultCallback<O> {void onActivityResult(@SuppressLint("UnknownNullness") O result);
    }
    
  5. 这样,被启动的那个 Activity 就可以使用 setResult 传递回传数据了。这个步骤与原先使用 onActivityResult 时是一样的。

    Intent data = new Intent();
    data.putExtra("MESSAGE", "...回传数据...");
    setResult(RESULT_OK, data);
    finish();
    
  6. 上面的代码也可以组合起来成一次函数调用。

    this.registerForActivityResult(new ActivityResultContract<Integer, String>() {@NonNull@Overridepublic Intent createIntent(Context context, Integer input) {// 设置要启动的 ActivityIntent intent = new Intent(context, BActivity.class);intent.putExtra("INPUT_VALUE", input);return intent;}@NonNull@Overridepublic String parseResult(int resultCode, Intent intent) {// 处理回传数据解码业务if (resultCode == RESULT_OK && intent != null) {return intent.getStringExtra("MESSAGE");}return null;}},result -> {// 处理回传数据业务}).launch(0); // 传入输入参数 input 值
    

registerForActivityResult 开箱即用

  虽然上面的流程对于笔者这种技术栈较广的老将来说,理解起来不是很有难度,但对有些新手来说,却有点不太友好。为此,安卓官方在 ActivityResultContracts 下提供了一些开箱即用的 协议,它使得 registerForActivityResult 用起来就像原先的 onActivityResult 一样。

StartActivityForResult

  StartActivityForResult 是最普通的 协议,它使得 registerForActivityResult 用起来就像原先的 onActivityResult 一样。

ActivityResultLauncher<Intent> activityLauncher = this.registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {// 处理回传数据业务});
Intent intent = new Intent(AActivity.this, BActivity.class);
activityLauncher.launch(intent);

GetContent

  GetContent 可以用于与安卓系统自带应用之间的交互。例如,下面的代码演示了打开系统相册并选择图片的方法。

ActivityResultLauncher<Intent> activityLauncher = this.registerForActivityResult(new ActivityResultContracts.GetContent(),uri -> {// 处理图片回传});
activityLauncher.launch("image/*");

后记

注意事项

  • 当 Activity 返回时,registerForActivityResult 的回调 callback 会先于 Activity 的方法 onResume 被调用。这与原先被废弃的 onActivityResult 是一样的。

  • registerForActivityResult 需要在生命周期方法 onCreate 及之前调用。也就是说,registerForActivityResult 方法只能在类字段和 onCreate 方法中使用。不能随意调用 registerForActivityResult 来设置 Activity 在返回之后的行为,否则会导致如下报错。

    java.lang.IllegalStateException: LifecycleOwner XXX is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.at androidx.activity.result.ActivityResultRegistry.register(ActivityResultRegistry.java:123)at androidx.activity.ComponentActivity.registerForActivityResult(ComponentActivity.java:833)at androidx.activity.ComponentActivity.registerForActivityResult(ComponentActivity.java:842)
    

附录

  • 安卓官方 registerForActivityResult 使用说明:https://developer.android.google.cn/training/basics/intents/result?hl=zh-cn

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/614977.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言中的位运算详解

在C语言中&#xff0c;位运算符用于对二进制位进行操作&#xff0c;包括左移、右移、按位与、按位或、按位异或和按位取反等操作。本文将详细介绍C语言中的位运算符&#xff0c;包括运算规则和具体的例子。 1. 位运算符概述 C语言提供了一些位运算符&#xff0c;用于直接操作…

iOS rootless无根越狱解决方案

据游戏工委数据统计&#xff0c;2023年国内游戏市场实际销售收入与用户规模双双创下新高&#xff0c;游戏普遍采用多端并发方式&#xff0c;成为收入增长的主因之一。 中国市场实际销售收入及增长率丨数据来源&#xff1a;游戏工委 多端互通既是机遇&#xff0c;也是挑战。从游…

python实例100第16例:使用datetime输出指定格式的日期

题目&#xff1a;输出指定格式的日期。 程序分析&#xff1a;使用 datetime 模块。 #!/usr/bin/python # -*- coding: UTF-8 -*-import datetimeif __name__ __main__:# 输出今日日期&#xff0c;格式为 dd/mm/yyyy。更多选项可以查看 strftime() 方法print(datetime.date.t…

【AI大模型应用开发】1.0 Prompt Engineering(提示词工程)- 典型构成、原则与技巧,代码中加入Prompt

从这篇文章开始&#xff0c;我们就正式开始学习AI大模型应用开发的相关知识了。首先是提示词工程&#xff08;Prompt Engineering&#xff09;。 文章目录 0. 什么是提示词&#xff08;Prompt&#xff09;1. 为什么Prompt会起作用 - 大模型工作原理2. Prompt的典型构成、原则与…

【5】商密测评密码辅助工具

0X01 前言 最近在学了下商密测评&#xff0c;研究了下技术层面的测评&#xff0c;感觉找工具不方便&#xff0c;就顺手自己造了个辅助工具&#xff0c;都是自己遇到需要用的。 0x02 工具功能介绍 不爱打字&#xff0c;直接上图。后续根据技术测评层面需要继续完善和增加功能。…

基于VSG控制的MMC并网逆变器MATLAB仿真模型

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介 根据传统同步发电机的运行特性设计了MMC-VSG功频控制器和励磁控制器&#xff0c; 实现了MMC-VSG逆变器对高压电网电压和频率的支撑。该模型包含MMC变流器模块&#xff0c;环流抑制模块&#xff0c;…

HackTheBox-Keeper

OpenVPN连接 连接上HackTheBox&#xff01; 同时找到这个靶机&#xff0c;进行join&#xff01;分配的靶机的地址位10.10.11.227&#xff01; 信息收集 nmap -sT --min-rate 10000 -p- 10.10.11.227 开放端口为22和80端口 服务版本和操作系统信息探测&#xff1a; nmap -s…

Milvus Cloud与携程的向量探索大公开

【User Tech】2024 我们来啦&#xff01; 今年&#xff0c;【User Tech】将更加专注于为社区用户提供技术功能解读、热点答疑&#xff0c;聚焦更丰富、更多样化的行业或使用场景的用户案例。我们期待通过分享更多关于 Milvus Cloud 的实战经验&#xff0c;为大家在 AI、大模型、…

ASM磁盘组配置共享存储:裸设备(60)_块设备(12)_(99)方式

--1.块设备方式配置共享存储 存储信息&#xff1a; 20个1TB的LUN&#xff0c;/dev/mapper/mpath1--mpath20 修改/etc/udev/rules.d/12-dm-permissions.rules添加内容 vi /etc/udev/rules.d/12-dm-permissions.rules ENV{DM_NAME}"mpath1", OWNER:"grid",…

YOLOv8算法改进【NO.98】改进损失函数为最新提出的Shape-IoU

前 言 YOLO算法改进系列出到这&#xff0c;很多朋友问改进如何选择是最佳的&#xff0c;下面我就根据个人多年的写作发文章以及指导发文章的经验来看&#xff0c;按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通&#xff1a; 第一…

1-08运算符和表达式

一、概述 在C语言中&#xff0c;表达式和语句是构成程序的基本元素。本节和下一章节我们就围绕它们展开讲一讲其中的C语言基础语法。 首先&#xff0c;让我们区分这两个概念&#xff1a; 语句(statement)&#xff0c;语句是代码中的一个完整的&#xff0c;可以执行的步骤。 …

微信小程序内嵌h5 分享子页面点击进入后是主页面解决办法

<web-view src{{src}}></web-view>src: https://XXXXXX,/*** 生命周期函数--监听页面加载 */ onLoad(options) {this.srcFun(options) },srcFun(options){//当有子页面id时 更改内嵌页链接if (options.urlPathNew) { let urlhttps://XXX/caseOrder?classicId${opt…

李沐之经典卷积神经网络

目录 1. LeNet 2. 代码实现 1. LeNet 输入是32*32图片&#xff0c;放到一个5*5的卷积层里面&#xff0c;卷积层的输出通道数是6&#xff0c;高宽都是28&#xff08;32-5128&#xff09;。再经过2*2的池化层&#xff0c;把28*28变成14*14&#xff08;28-22&#xff09;/214&am…

《Vue2 进阶知识》动态挂载组件之Vue.extend + vm.$mount

前言 目前工作还是以 Vue2 为主&#xff0c;今早有人提问 如何动态挂载组件&#xff1f; 话说很久很久以前就实现过&#xff0c;今天再详细的整理一下此问题&#xff01; 开始 动态组件如下&#xff0c;是个简单的例子&#xff1a; 但请注意这里给了个 id"test2"…

vue 组件 import make sure to provide the “name“ option.

百度了好多结果&#xff0c;都过时了&#xff0c;例如&#xff1a; 模块引入是否加{} 再比如&#xff1a; 对于递归组件&#xff0c;请确保提供“name”选项。 出现该错误情况之一&#xff1a; 错误由未正确引入组件或子组件引起&#xff0c;如element-ui中form表单组件未引…

PostgreSQL之SEMI-JOIN半连接

什么是Semi-Join半连接 Semi-Join半连接&#xff0c;当外表在内表中找到匹配的记录之后&#xff0c;Semi-Join会返回外表中的记录。但即使在内表中找到多条匹配的记录&#xff0c;外表也只会返回已经存在于外表中的记录。而对于子查询&#xff0c;外表的每个符合条件的元组都要…

GitLab 502 Whoops, GitLab is taking too much time to respond. 解决

1、先通过gitlab-ctl restart进行重启&#xff0c;2分钟后看是否可以正常访问&#xff0c;为什么要2分钟&#xff0c;因为gitlab启动会有很多配套的服务启动&#xff0c;包括postgresql等 2、如果上面不行&#xff0c;再看gitlab日志&#xff0c;通过gitlab-ctl tail命令查看&…

【Arduino】编程语言:定时函数、数学函数、字符函数(功能、语法格式、参数说明、返回值) | 软件开发环境:安装步骤介绍(EXE安装版、ZIP安装版)

你的负担将变成礼物,你受的苦将照亮你的路。———泰戈尔 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌿[2] 2023年城市之星领跑者TOP1(哈尔滨)🌿 🌟[3] 2022年度博客之星人工智能领域TOP4🌟 🏅[4] 阿里云社区…

再发一波微信红包封面,免费!免费!免费!

我是90后程序员&#xff0c;大家都叫我小码哥&#xff0c;从事互联网近10余年了&#xff0c;一直想在互联网上分享自己的管理经验和技术经验&#xff0c;同时也想找一些志同道合的朋友&#xff0c;一起聊聊如何从互联网中快速的成长起来&#xff0c;无论是通过技术、互联网风口…

谈谈曲线与曲面

目录 1、非参数曲线与曲面 2、方程式曲线与曲面 3、参数曲线与曲面 3.1平面参数曲线 3.2空间参数曲线 3.3参数曲面 1、非参数曲线与曲面 非参数曲线曲面是一种与参数曲线曲面相对的概念。在非参数方法中&#xff0c;曲线或曲面不是通过参数方程来定义的&#xff0c;而是通…