Activity Result API详解,是时候放弃startActivityForResult了
本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。
如果你将项目中的appcompat库升级到1.3.0或更高的版本,你会发现startActivityForResult()方法已经被废弃了。
这个方法相信所有做过Android的开发者都用过,它主要是用于在两个Activity之间交换数据的。
那么为什么这个如此常用的方法会被废弃呢?官方给出的说法是,现在更加建议使用Activity Result API来实现在两个Activity之间交换数据的功能。
我个人的观点是,startActivityForResult()方法并没有什么致命的问题,只是Activity Result API在易用性和接口统一性方面都做得更好。既然有更好的API,那么就不再建议去使用过去老旧的API,所以才把startActivityForResult()方法标为了废弃。
其实除了startActivityForResult()方法之外,还有像requestPermissions()方法也被标为了废弃。看起来它们两者之间好像并没有什么关联,但是到了Activity Result API中,它们就被归属到了统一的API模板当中。因此,我们可以使用非常类似的代码去实现在两个Activity之间交换数据,以及请求运行时权限的功能。
另外,Activity Result API的用法非常简单,一学就会。相信你看完本篇文章之后,就可以将自己项目中所有相关的代码都升级成Activity Result API的用法。
那么我们开始吧。
在两个Activity之间交换数据
如果想要在两个Activity之间交换数据,我们先回顾一下传统的写法:
class FirstActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_first)val firstButton = findViewById<Button>(R.id.first_button)firstButton.setOnClickListener {val intent = Intent(this, SecondActivity::class.java)startActivityForResult(intent, 1)}}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)when (requestCode) {1 -> {if (resultCode == RESULT_OK) {val data = data?.getStringExtra("data")// Handle data from SecondActivity}}}}}
这里调用了startActivityForResult()方法去向SecondActivity请求数据,然后在onActivityResult()方法中去解析SecondActivity返回的结果。
那么SecondActivity中的代码是什么样的呢?这里我们就简单模拟一下,随便返回一个数据即可:
class SecondActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_second)val secondButton = findViewById<Button>(R.id.second_button)secondButton.setOnClickListener {val intent = Intent()intent.putExtra("data", "data from SecondActivity")setResult(RESULT_OK, intent)finish()}}}
如此一来,FirstActivity向SecondActivity请求数据的功能就通了,是不是感觉也挺简单的?所以我刚才说了,startActivityForResult()方法并没有什么致命的问题。
那么接下来我们学习一下如何使用Activity Result API来实现同样的功能。
首先,SecondActivity中的代码是不需要修改的。这部分代码并没有被废弃,Activity Result API也与它无关。
FirstActivity中的代码,我们需要使用Activity Result API来替代startActivityForResult()的写法,如下所示:
class FirstActivity : AppCompatActivity() {private val requestDataLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->if (result.resultCode == RESULT_OK) {val data = result.data?.getStringExtra("data")// Handle data from SecondActivity}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_first)val firstButton = findViewById<Button>(R.id.first_button)firstButton.setOnClickListener {val intent = Intent(this, SecondActivity::class.java)requestDataLauncher.launch(intent)}}}
注意这里的代码变更。我们完全移除了对onActivityResult()方法的重写,而是调用registerForActivityResult()方法来注册一个对Activity结果的监听。
registerForActivityResult()方法接收两个参数,第一个参数是一种Contract类型,由于我们是希望从另外一个Activity中请求数据,因此这里使用了StartActivityForResult这种Contract。第二个参数是一个Lambda表达式,当有结果返回时则会回调到这里,然后我们在这里获取并处理数据即可。
registerForActivityResult()方法的返回值是一个ActivityResultLauncher对象,这个对象当中有一个launch()方法可以用于去启用Intent。这样我们就不需要再调用startActivityForResult()方法了,而是直接调用launch()方法,并把Intent传入即可。
这两种写法到底孰优孰劣呢?我个人感觉还是Activity Result API的写法更简单一点,不过总体优势并没有那么大。Activity Result API真正的优势在于我们接下来要讲的内容。
请求运行时权限
除了startActivityForResult()方法之外,requestPermissions()方法也被废弃了。至于理由都是一样的,推荐使用Activity Result API。
那么要如何使用Activity Result API来请求运行时权限呢?不要惊讶,它将会出奇得简单:
class FirstActivity : AppCompatActivity() {private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->if (granted) {// User allow the permission.} else {// User deny the permission.}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_first)val firstButton = findViewById<Button>(R.id.first_button)firstButton.setOnClickListener {requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)}}}
我们只需关注代码变更的部分。
由于这次是请求运行时权限,因此不能再使用刚才的StartActivityForResult来作为Contract了,而是要使用RequestPermission这种Contract。
另外由于指定了不同的Contract类似,Lambda表达式的参数也会发生变化。现在Lambda表达式会传入一个布尔型的参数,用于告诉我们用户是否允许了我们请求的权限。
最后,launch()方法的参数也发生了变化,现在只需传入要请求的权限名即可。
有没有发现,这两段代码的模板出奇得一致。我们使用了两段差不多的代码,实现了之前几乎并没有太大联系的两个功能。这就是Activity Result API的好处,它将一些API的接口统一化,使得我们在实现特定功能的时候能够变得非常简单。
内置Contract
刚才我们体验了StartActivityForResult和RequestPermission这两种Contract,分别用于在两个Activity之间交换数据,以及请求运行时权限。它们都是Activity Result API中内置的Contract。
那么除此之外,我们还有哪些内置Contract可以使用呢?
下面是我列出的appcompat 1.3.0版本所支持的所有内置Contract,以后还可能会继续增加新的Contract:
StartActivityForResult()
StartIntentSenderForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
GetContent()
GetMultipleContents()
OpenDocument()
OpenMultipleDocuments()
OpenDocumentTree()
CreateDocument()
每个Contract的命名已经明确表示它们的作用是什么了,也就是说,当我们要实现以上Contract所包含的功能时,都不需要再自己手动费力去写了,Activity Result API已经帮我们支持好了。
比如,我想要调用手机摄像头去拍摄一张图片,并且得到这张图片的Bitmap对象,那么就可以使用TakePicturePreview这个Contract。
实现代码如下:
class FirstActivity : AppCompatActivity() {private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->// bitmap from camera}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_first)val firstButton = findViewById<Button>(R.id.first_button)firstButton.setOnClickListener {takePictureLauncher.launch(null)}}}
代码非常简单,就是换了一下Contract类型,然后Lambda表达式的参数会变成bitmap对象。另外由于TakePicturePreview这个Contract不需要输入参数,所以我们调用launch()方法的时候直接传入null就可以了。
看到这里,可能有些读者朋友会比较好奇。我怎么知道每种Contract要求什么输入参数,以及Lambda表达式中返回的参数是什么呢?
这个很简单,只需要看一下这个Contract的源码即可。比如TakePicturePreview的源码如下:
/*** An {@link ActivityResultContract} to* {@link MediaStore#ACTION_IMAGE_CAPTURE take small a picture} preview, returning it as a* {@link Bitmap}.* <p>* This can be extended to override {@link #createIntent} if you wish to pass additional* extras to the Intent created by {@code super.createIntent()}.*/
public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap> {...
}
我们暂时不用关心TakePicturePreview内部的具体实现,只要看一下它在继承父类时指定的泛型类型即可。其中第一个参数就是要求的输入参数,而第二个参数就是Lambda表达式返回的输出参数。
只要掌握这个小技巧,每种Contract你就都能轻松运用自如了。那么我就不再多做演示,剩下这些Contract的用法等待你自己去探索。
自定义Contract
除了以上内置Contract之外,我们确实也可以定义自己的Contract类型。
虽然我觉得这个必要性并不是很强,因为内置Contract已经可以帮助我们应对绝大多数场景了。
不过,自定义Contract并不是一件复杂的事情。相反,它非常简单,所以这里还是简略提一下吧。
刚才我们大概看到了TakePicturePreview的源码实现,它必须继承自ActivityResultContract类,并通过泛型来指定当前Conract类型的输入参数和输出参数。
ActivityResultContract是一个抽象类,它的内部定义了两个抽象方法,如下所示:
public abstract class ActivityResultContract<I, O> {public abstract @NonNull Intent createIntent(@NonNull Context context, I input);public abstract O parseResult(int resultCode, @Nullable Intent intent);...
}
也就是说,任何一个继承自ActivityResultContract的Contract,都需要重写createIntent()和parseResult()这两个方法。
而这两个方法的作用也非常明显。createIntent()就是用于创建一个Intent,后续会使用这个Intent来发起动作,比如启动另外一个Activity去获取数据,或者打开相机去拍照等等。而parseResult()则是用于解析响应的结果,并把解析出来的结果作为输出参数返回到Lambda表达式当中。
每一个内置的Contract都是使用的这种规则来封装的自己的逻辑。
那么我们要自定义一个什么样的Contract来进行演示呢?
我想了一下,刚才在编写两个Activity之间交换数据的时候,我们需要显示地启动SecondActivity,并手动将SecondActivity返回的数据从Intent中解析出来,这就稍微有些麻烦。而借助自定义Contract就可以对此处进行优化。
新建一个叫做GetDataFromSecondActivity的Contract,代码如下所示:
class GetDataFromSecondActivity : ActivityResultContract<Void, String?>() {override fun createIntent(context: Context, input: Void?): Intent {return Intent(context, SecondActivity::class.java)}override fun parseResult(resultCode: Int, intent: Intent?): String? {if (resultCode == Activity.RESULT_OK) {if (intent != null) {return intent.getStringExtra("data")}}return null}}
我们通过泛型指定,这个Contract的输入参数是Void,输出参数是一个字符串。
然后在createIntent()方法中,我们手动创建了一个Intent,并将它的用途设置为打开SecondActivity。
最后在parseResult()方法中,我们对SecondActivity返回的结果进行解析,并将解析出来的字符串作为输出参数返回。
这样一个自定义的Contract就完成了,而我们使用这个Contract再去实现最开始的在两个Activity之间交换数据的功能,就会变得更加简单:
class FirstActivity : AppCompatActivity() {private val getDataLauncher = registerForActivityResult(GetDataFromSecondActivity()) { data ->// Handle data from SecondActivity}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_first)val firstButton = findViewById<Button>(R.id.first_button)firstButton.setOnClickListener {getDataLauncher.launch(null)}}}
可以看到,借助GetDataFromSecondActivity这个Contract,我们不需要再显式地声明去启动SecondActivity,launch()方法直接传入null即可。另外,我们也不需要再去手动解析SecondActivity返回的数据,lambda表达式上的参数就是解析出来的结果了。
最后一个小问题
到这里,我们基本就将Activity Result API的所有内容都学完了。
在本篇文章的最后,我想再回答一个小问题。因为我自己当初在使用Activity Result API的时候产生过这样的疑惑,所以我猜或许也会有朋友有同样的问题,那么在这里就顺手解答了。
现在你已经知道,Activity Result API是可以完全取代startActivityForResult()方法的。但是我们在调用startActivityForResult()方法时,除了传入Intent之外,还需要再传入一个requestCode,用于在多个任务之间进行区分。比如如下代码:
class FirstActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_first)val firstButton = findViewById<Button>(R.id.first_button)val secondButton = findViewById<Button>(R.id.second_button)firstButton.setOnClickListener {val intent = Intent(Intent.ACTION_VIEW)startActivityForResult(intent, 1)}secondButton.setOnClickListener {val intent = Intent(Intent.ACTION_DIAL)startActivityForResult(intent, 2)}}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)when (requestCode) {1 -> {// Handle result for ACTION_VIEW}2 -> {// Handle result for ACTION_DIAL}}}}
这里我们分别在两处调用了startActivityForResult()方法,它们各自用于处理不同的任务,因此需要给它们设置不同的requestCode。
在onActivityResult()方法当中,我们为了区分这个结果是来自之前的哪个任务的,所以要在这里再对requestCode进行判断。
这是以前使用startActivityForResult()方法时的传统写法。
而Activity Result API是没有地方让你传入requestCode的。
我在刚接触Activity Result API的时候受思维惯性的影响被这个问题困扰了一下,没有地方传入requestCode该怎么办呢?
后来思维转过来弯之后发现,原来Activity Result API根本就不需要requestCode这种东西,我们可以使用如下写法来实现和刚才完全一样的功能:
class FirstActivity : AppCompatActivity() {private val actionViewLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->// Handle result for ACTION_VIEW}private val actionDialLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->// Handle result for ACTION_DIAL}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_first)val firstButton = findViewById<Button>(R.id.first_button)val secondButton = findViewById<Button>(R.id.second_button)firstButton.setOnClickListener {val intent = Intent(Intent.ACTION_VIEW)actionViewLauncher.launch(intent)}secondButton.setOnClickListener {val intent = Intent(Intent.ACTION_DIAL)actionDialLauncher.launch(intent)}}}
由此也可以看出,Activity Result API的设计更加合理,不需要借助requestCode这种魔术数字也能对多个任务进行区分。
一切都更加简单和清晰。
如果想要学习Kotlin和最新的Android知识,可以参考我的新书 《第一行代码 第3版》,点击此处查看详情。
关注我的技术公众号,每天都有优质技术文章推送。
微信扫一扫下方二维码即可关注:
Activity Result API详解,是时候放弃startActivityForResult了相关推荐
- Android MVVM框架搭建(十)Hilt、ViewBinding、Activity Result API
Android MVVM框架搭建(十)Hilt.ViewBinding.Activity Result API 前言 正文 一.依赖 二.Hilt使用 1. Hilt 应用类 2. ViewModel ...
- 【java8新特性】——Stream API详解(二)
一.简介 java8新添加了一个特性:流Stream.Stream让开发者能够以一种声明的方式处理数据源(集合.数组等),它专注于对数据源进行各种高效的聚合操作(aggregate operation ...
- ext核心API详解
http://hi.baidu.com/j2me/profile 1 EXT核心API详解(一)-Ext 1 EXT核心API详解(二)-Array/Date/Function/Number/Stri ...
- Hotspot JNIEnv API详解(二)
目录 一.字符串操作 1.常见的编码格式 2.乱码问题根源 3.字符串API 4.jni_NewString和jni_NewStringUTF源码解析 二.数组操作 三.Monitor操作 四.NIO ...
- H5的新特性及API详解(很惊人)
H5的新特性及API详解(很惊人) 2017-01-20 17:00 4057人阅读 评论(0) 收藏 举报 分类: h5(11) js函数(64) js技巧(15) 版权声明:本文为博主原创 ...
- 【Unity编程】Unity中关于四元数的API详解
Unity中关于四元数的API详解 Quaternion类 Quaternion(四元数)用于计算Unity旋转.它们计算紧凑高效,不受万向节锁的困扰,并且可以很方便快速地进行球面插值. Unity内 ...
- 百度PaddleOCR及云平台OCR API详解及示例
百度PaddleOCR及云平台OCR API详解及示例 目录 百度PaddleOCR及云平台OCR API详解及示例 使用百度开源的PaddleOCR 多个开源代码库比较
- Java 8 Stream API详解--转
原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java 8引入 ...
- 【小白学PyTorch】扩展之Tensorflow2.0 | 21 Keras的API详解(下)池化、Normalization
<<小白学PyTorch>> 扩展之Tensorflow2.0 | 21 Keras的API详解(上)卷积.激活.初始化.正则 扩展之Tensorflow2.0 | 20 TF ...
最新文章
- [jobdu]调整数组顺序使奇数位于偶数前面
- mysql 多个unique key_[MySQL]MySQL 中通过使用UNIQUE KEY 来控制字段值不重复的问题.
- Windows消息机制学习笔记(二)—— 窗口与线程
- “新型肺炎患者同乘查询系统”上线,超2千万用户使用
- UGUI自定义组件之Image根据Text大小自动调整
- gem install mysql2的时候出现的错误
- Android开发中,怎样调用摄像机拍照以及怎样从本地图库中选取照片
- 第46章 	DCMI—OV5640摄像头—零死角玩转STM32-F429系列
- Racket GUI,使用message%显示图片
- 菜鸟教程,css小白入门
- 图象恢复——(逆滤波,维纳滤波)
- 全部资源,都在这里了
- 【opencv】鱼眼图像畸变校正——透视变换
- 每日小技巧——教你用一行Python代码去除照片背景
- 达内微软mta证书有用吗_达内与微软达成战略合作 合作培养青少年科技素质
- 计算机本科应届生年薪 30w40w 真的很普遍吗?
- 赠与今年的大学毕业生-----------胡适
- 逃出你的肖申克(一):一定要亲身经历了之后才能明白?
- 群晖DSM7 使用Zerotier实现无公网IP外网访问
- 组态王曲线控件读取access_组态王历史趋势曲线控件.doc