文章图片
这个方法相信所有做过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之间交换数据 【Android精华教程|Activity Result API详解,是时候放弃startActivityForResult了】如果想要在两个Activity之间交换数据,我们先回顾一下传统的写法:
class FirstActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
val firstButton = findViewById
class SecondActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val secondButton = findViewById
如此一来,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 = https://www.it610.com/article/result.data?.getStringExtra("data")
// Handle data from SecondActivity
}
}override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_first)
val firstButton = findViewById
注意这里的代码变更。我们完全移除了对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
我们只需关注代码变更的部分。
由于这次是请求运行时权限,因此不能再使用刚才的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:
每个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
/**
* An {@link ActivityResultContract} to
* {@link MediaStore#ACTION_IMAGE_CAPTURE take small a picture} preview, returning it as a
* {@link Bitmap}.
* * 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 {
...
}
public abstract class ActivityResultContract {public abstract @NonNull Intent createIntent(@NonNull Context context, I input);
public abstract O parseResult(int resultCode, @Nullable Intent intent);
...
}
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
最后一个小问题 到这里,我们基本就将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
这里我们分别在两处调用了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(R.id.first_button)
val secondButton = findViewById(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这种魔术数字也能对多个任务进行区分。
一切都更加简单和清晰。