ActivityResultAPI详解,是时候放弃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之间交换数据,我们先回顾⼀下传统的写法:
override fun onCreate(savedInstanceState: Bundle?) {
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?) {
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?) {
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()的写法,如下所⽰:
private val requestDataLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (sultCode == RESULT_OK) {
val data = result.data?.getStringExtra("data")
// Handle data from SecondActivity
}
}
override fun onCreate(savedInstanceState: Bundle?) {
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真正的优势在于我们接下来要讲的内容。
register for
/  请求运⾏时权限  /
除了startActivityForResult()⽅法之外,requestPermissions()⽅法也被废弃了。⾄于理由都是⼀样的,推荐使⽤Activity Result API。
那么要如何使⽤Activity Result API来请求运⾏时权限呢?不要惊讶,它将会出奇得简单:
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
// User allow the permission.
} else {
// User deny the permission.
}
}
override fun onCreate(savedInstanceState: Bundle?) {
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.TakePicturePrevi
ew()) { bitmap ->
// bitmap from camera
}
override fun onCreate(savedInstanceState: Bundle?) {
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 {@ateIntent()}.
*/
public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap> {
...
}
我们暂时不⽤关⼼TakePicturePreview内部的具体实现,只要看⼀下它在继承⽗类时指定的泛型类型即可。其中第⼀个参数就是要求的输⼊参数,⽽第⼆个参数就是Lambda表达式返回的输出参数。
只要掌握这个⼩技巧,每种Contract你就都能轻松运⽤⾃如了。那么我就不再多做演⽰,剩下这些Contract的⽤法等待你⾃⼰去探索。
/  ⾃定义Contract  /
除了以上内置Contract之外,我们确实也可以定义⾃⼰的Contract类型。
虽然我觉得这个必要性并不是很强,因为内置Contract已经可以帮助我们应对绝⼤多数场景了。
不过,⾃定义Contract并不是⼀件复杂的事情。相反,它⾮常简单,所以这⾥还是简略提⼀下吧。
刚才我们⼤概看到了TakePicturePreview的源码实现,它必须继承⾃ActivityResultContract类,并通过泛型来指定当前Conract类型的输⼊参数和输出参数。
ActivityResultContract是⼀个抽象类,它的内部定义了两个抽象⽅法,如下所⽰:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。