放弃 startActivityForResult,Activity Result API 优雅使用
Activity Result API 是 androidx 中的一个新 api,旨在替代原有的 startActivityForResult
方法,用于在两个 Activity 或 Fragment 交换数据、获取返回结果。
过去如果 Activity A 想获取 Activity B 的结果时,需要调用 startActivityForResult
启动 Activity 并重写 onActivityResult
方法获取返回值,这种写法让数据返回的处理逻辑和 Activity 强耦合在一起,代码复用不方便,可读性也很低。
//1.启动Activity
startActivityForResult(intent, 111)
//2.接收返回值:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 111) {
when (resultCode) {
1 -> { ... }
2 -> { ... }
}
}
}
目前 startActivityForResult
方式已被标为废弃方法,Google 推荐使用 Activity Result API
来进行 Activity 或 Fragment 数据交换,这一改变主要是为了简化代码结构,提高代码的重用性和可维护性。以下是对Activity Result API的详细介绍:
Activity Result API的核心组件
-
ActivityResultContract<I, O>
:协议,定义了数据类型、如何传递数据和处理返回数据。ActivityResultContract 是一个抽象类,可以继承它来创建自己的协议。泛型 I 是输入类型,O 是输出类型,如果不需要任何输出可以应用 Unit。 -
ActivityResultCallback<O>
:返回结果监听,通过该回调获取返回结果。 -
registerForActivityResult
,启动器注册方法,定义在 ComponentActivity 和 Fragment 中,注册协议并返回启动器。该方法需要注意调用位置,调用过晚会抛出异常:
- Activity:
LifecycleOwners must call register before they are STARTED.
- Fragment:
Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate())."
- Activity:
-
ActivityResultLauncher
:启动器,调用它的launch
方法来启动页面跳转,作用相当于原来的 startActivity()。
ActivityResultContract的类型
系统提供了几种常用的 ActivityResultContract 类型,如无特殊需求可以直接使用:
-
StartActivityForResult
:通用 Contract,内部未做任何转换。输入 Intent,输出 ActivityResult,是最常用的。class StartActivityForResult : ActivityResultContract<Intent, ActivityResult>() { override fun createIntent(context: Context, input: Intent):Intent = input override fun parseResult(resultCode: Int,intent: Intent?):ActivityResult = ActivityResult(resultCode, intent) }
-
RequestPermission
:请求单个权限。输入类型 String,具体权限;输出类型 Boolean,权限申请结果。 -
RequestMultiplePermissions
:请求一组权限。输入类型 Array,权限组;输出类型 Map<String, Boolean> 权限申请结果。 -
TakePicture
: 调用 MediaStore.ACTION_IMAGE_CAPTURE 拍照,输入类型 Uri,照片存储地址;输出类型 Boolean 拍照结果。 -
TakePicturePreview
:调用 MediaStore.ACTION_IMAGE_CAPTURE 拍照,输入类型 Void? ,输出类型 Bitmap,图片。 -
TakeVideo
:调用 MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频,输入类型 Uri,视频存储地址;输出类型 Bitmap?,缩略图。 -
PickContact
:选择联系人。 -
GetContent
:获取用户内容,输入类型 String,过滤的mime 类型(如image/*);输出类型 Uri,内容位置。 -
CreateDocument
:创建文档,输入类型 String,新文件的建议名称;输出类型 Uri。 -
OpenDocument
:选择单个文档,输入类型 Array,过滤的mime类型;输出类型 Uri。 -
OpenMultipleDocuments
:选择多个文档,输入类型 Array, 过滤的mime类型;输出类型 List。 -
OpenDocumentTree
:选择一个目录,输入类型 Uri?,可选的初始目录;输出类型 Uri?。
使用步骤
举个例子,FirstActivity -> SecondActivity,需要 FirstActivity 将数据传递给 SecondActivity,SecondActivity 处理完数据后将数据回传给 FirstActivity。
-
FirstActivity 中需要 调用
registerForActivityResult
方法注册启动器,参数一是启动协议,这里直接用内置的StartActivityForResult
;参数二是数据回调监听,在此处理数据返回逻辑。 -
使用启动器的
launch
方法进行页面跳转,Activity A 中代码如下:class FirstActivity : AppCompatActivity() { lateinit var button: Button //1.注册启动器 private val launcher: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult(), object: ActivityResultCallback<ActivityResult> { override fun onActivityResult(result: ActivityResult) { //3.处理SecondActivity返回数据 if (result.resultCode == RESULT_OK) { val content = result.data?.getStringExtra("RESULT_CONTENT") Log.d("baize_", "FirstActivity get result content:${content}}") } } }) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.first_activity) button = findViewById(R.id.button) button.setOnClickListener { //2.启动Activity B,并传递数据 val intent = Intent(this, SecondActivity::class.java).apply { putExtra("INPUT_CONTENT", "custom data...") } launcher.launch(intent) } } }
-
SecondActivity 中按传统方式接收和返回数据:
class SecondActivity : AppCompatActivity() { lateinit var button: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) button = findViewById(R.id.button) //1.获取FirstActivity传递来的数据 val content = intent.getStringExtra("INPUT_CONTENT") Log.d("baize_", "SecondActivity get input content:${content}}") button.setOnClickListener { //2.设置返回数据 val intent = Intent().apply { putExtra("RESULT_CONTENT", editText.text.toString()) } setResult(RESULT_OK, intent) finish() } } }
自定义协议
继续上面的例子,如果不希望每次跳转都书写同样的 Intent 解析代码,就可以自定义启动器将模板代码封装到里面:
自定义 Contract:
class CustomContract: ActivityResultContract<String, String?>() {
override fun createIntent(context: Context, input: String): Intent {
val intent = Intent(context, SecondActivity::class.java)
intent.putExtra("input", input)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
return if (resultCode == RESULT_OK) { intent?.getStringExtra("RESULT_CONTENT") } else null
}
}
使用时:
//1.注册启动器
private val launcher: ActivityResultLauncher<String> = registerForActivityResult(CustomContract(), object: ActivityResultCallback<String?> {
override fun onActivityResult(result: String?) {
//3.直接处理返回值
Log.d("baize_", "activity 1 get result content:${result}}")
}
})
//2.启动页面,直接传入String值
launcher.launch("custom data...")
最后效果:
总结
使用 Activity Result API
相比传统的 startActivityForResult()
和 onActivityResult()
方法,具有以下优点:
- 代码更加简洁:通过将结果处理逻辑与 Activity 的生命周期解耦,使得代码更加简洁易读。
- 提高重用性:自定义的 ActivityResultContract 可以在多个地方重复使用,提高代码的重用性。
- 易于维护:减少了 onActivityResult 方法的嵌套和耦合,使得代码更加易于维护。