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"