startActivityForResult问题
startActivityForResult(Intent(this, SecondActivity::class.java), REQ_CODE)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQ_CODE) {
if (resultCode == Activity.RESULT_OK) {
data?.let {
val value: String? = data.getStringExtra("hello")
log(value!!)
}
}
}
}
startActivityForResult()
和 onActivityResult()
导致代码嵌套较多、耦合度高、难以维护等问题。
Google可能意识到该问题,推荐使用Activity Results API。
为什么强烈建议使用Activity Results API
官方解释
虽然所有 API 级别的 Activity
类均提供底层 startActivityForResult()
和 onActivityResult()
API,但我们强烈建议您使用 AndroidX Activity和 Fragment中引入的 Activity Result API。
Activity Result API 提供了用于注册结果、启动结果以及在系统分派结果后对其进行处理的组件。
启动一个 activity(无论是本应用中的 activity 还是其他应用中的 activity)不一定是单向操作,也可以启动另一个 activity 并接收返回的结果。
在启动 activity 以获取结果时,可能会出现您的进程和 activity 因内存不足而被销毁的情况;如果是使用相机等内存密集型操作,几乎可以确定会出现这种情况。
因此,Activity Result API 会将结果回调从您之前启动另一个 activity 的代码位置分离开来。由于在重新创建进程和 activity 时需要使用结果回调,因此每次创建 activity 时都必须无条件注册回调,即使启动另一个 activity 的逻辑仅基于用户输入内容或其他业务逻辑也是如此。
通俗解释
常见的场景是调用系统相机、调用相册获取照片、调用通讯录、获取部分特殊权限等,传统方式通常是通过 Intent 携带数据,然后使用 startActivityForResult 方法来启动下一个 Activity,然后通过 onActivityResult 来接收返回的数据。
传统方式的问题在于:
1、在启动 activity 以获取结果时,可能会出现进程和 activity 因内存不足而被销毁的情况。
2、onActivityResult 回调方法嵌套耦合严重,逻辑混乱导致难以维护。
Activity Result API使用
主要有两种使用方式:
1、调用系统内置ActivityResultContract
2、调用自定义ActivityResultContract
方式一,系统内置ActivityResultContract使用
Android系统内置了常用Contract,部分列举如下
StartActivityForResult():通用Contract
RequestMultiplePermissions():申请一组权限
RequestPermission():申请单个权限
TakePicturePreview():拍照,返回Bitmap
TakePicture():拍照,保存指定Uri地址,返回true表示保存成功
TakeVideo():拍视频,保存指定Uri地址,返回一张缩略图
PickContact():从通讯录获取联系人
CreateDocument():选择一个文档,返回Uri
OpenDocumentTree():选择一个目录,返回Uri
OpenMultipleDocuments(),选择多个文档,返回多个Uri
GetContent():选择一条内容,返回Uri
StartActivityFoResult
启动一个Activity并返回数据。
注册协议
private val activityLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val data = it.data
data?.apply {
val name = getStringExtra("name")
val age = getIntExtra("age", 0)
val address = getStringExtra("address")
textView.text = "name:$name age:$age address:$address"
}
}
}
启动
activityLauncher.launch(
Intent(this, ThirdActivity::class.java).apply {
putExtra("name", "小花")
putExtra("age", 38)
putExtra("address", "guangzhou")
}
)
RequestPermission
申请单个权限。
注册协议
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
Toast.makeText(mContext, "权限申请-成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(mContext, "权限申请-失败", Toast.LENGTH_SHORT).show()
}
}
启动
permissionLauncher.launch(Manifest.permission.CAMERA)
RequestMultiplePermissions
申请多个权限。
注册协议
private val multiPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
it ->
it.forEach {
if (it.value) {
Toast.makeText(mContext, "${it.key} 申请权限-成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(mContext, "${it.key} 申请权限-失败", Toast.LENGTH_SHORT).show()
}
}
}
启动
multiPermissionLauncher.launch(
arrayOf<String>(
Manifest.permission.CAMERA,
Manifest.permission.CALL_PHONE,
Manifest.permission.ACCESS_FINE_LOCATION
)
)
TakePicturePreview
拍照。
注册协议
private val takePicturePreviewLauncher =
registerForActivityResult(ActivityResultContracts.TakePicturePreview()) {
Toast.makeText(mContext, "Bitmap大小:${it.byteCount}", Toast.LENGTH_SHORT).show()
}
启动
takePicturePreviewLauncher.launch(null)
方式二,自定义ActivityResultContract使用
Activity Results API有三个重要的类:
ActivityResultContract
:协议,这是一个抽象类,定义如何传递数据和如何接收数据,
ActivityResultLauncher
:启动器,相当于以前的startActivityForResult()
ActivityResultCallback
:结果回调,相当于以前的onActivityResult()
官方说明:
位于 ComponentActivity
或 Fragment
中时,Activity Result API 会提供 registerForActivityResult()
API,用于注册结果回调。
registerForActivityResult()
接受 ActivityResultContract
和 ActivityResultCallback
作为参数,并返回 ActivityResultLauncher
,供您用来启动另一个 activity。
ActivityResultContract
定义生成结果所需的输入类型以及结果的输出类型。这些 API 可为拍照和请求权限等基本 intent 操作提供默认协定。您还可以创建自己的自定义协定。
ActivityResultCallback
是单一方法接口,带有 onActivityResult()
方法,可接受 ActivityResultContract
中定义的输出类型的对象。
创建待启动Activity
class SecondActivity : AppCompatActivity() {
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
textView = findViewById(R.id.textView)
val name = intent.getStringExtra("name")
textView.text = name
}
fun finishClick(view: View) {
setResult(Activity.RESULT_OK, Intent().apply {
putExtra("result", "hello ActivityResult")
})
finish()
}
}
定义协议
class MyContract : ActivityResultContract<String, String>() {
override fun createIntent(context: Context, input: String?): Intent {
return Intent(context, SecondActivity::class.java).apply {
putExtra("name", input)
}
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
val result = intent?.getStringExtra("result")
return if (resultCode == Activity.RESULT_OK) {
result
} else {
null
}
}
}
注册协议
private val launcher = registerForActivityResult(MyContract()) {
textView.text = it
}
跳转
launcher.launch("hello world")
结果
textView.text被设置成"hello ActivityResult"