Android-ActivityResultAPI
Android-ActivityResultAPI
1. 推出目的
如果你将项目中的appcompat库升级到1.3.0或更高的版本,你会发现startActivityForResult()方法已经被废弃了。
现在更加建议使用Activity Result API来实现在两个Activity之间交换数据的功能。
2. 传统写法 - 在两个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()方法并没有什么致命的问题。
3. 新写法 - 在两个Activity之间交换数据
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传入即可。
4. 使用 Activity Result API 进行权限处理
ex: 申请读SDcard的权限:
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()方法的参数也发生了变化,现在只需传入要请求的权限名即可。
5. 其它内置 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的用法等待你自己去探索。
6. 为什么不需要 requestCode?
现在你已经知道,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这种魔术数字也能对多个任务进行区分。
参考链接:
Activity Result API详解,是时候放弃startActivityForResult了
<完>