在 Android 开发中,处理 Activity 结果是一个常见的需求。传统的 startActivityForResult 和 onActivityResult 方法在处理复杂场景时存在一些问题,比如代码分散、类型安全问题以及配置变更时的处理困难。为了解决这些问题,Android 引入了 registerForActivityResult API,提供了一种更简洁、更安全的方式来处理 Activity 结果。
一、为什么需要 registerForActivityResult?
传统的 Activity 结果处理方式有以下痛点:
-
代码分散:启动 Activity 的代码和处理结果的代码分散在不同的方法中,导致逻辑不连贯。
-
类型不安全:结果数据需要手动转换,容易出现类型转换异常。
-
生命周期管理复杂:在配置变更(如屏幕旋转)时需要额外处理以保留结果。
-
Fragment 与 Activity 不一致:Fragment 可以直接获取结果,但需要注意调用方式。
registerForActivityResult 解决了这些问题,提供了类型安全、生命周期感知的 Activity 结果处理方式。
二、基本概念
1. ActivityResultContract
ActivityResultContract 是一个抽象类,定义了如何启动 Activity 以及如何解析返回的结果。Android 提供了许多内置的 Contract,覆盖了常见的用例:
RequestPermission:请求单个权限RequestMultiplePermissions:请求多个权限StartActivityForResult:通用的启动 Activity 并获取结果TakePicture:拍摄照片PickContact:选择联系人GetContent:从内容提供者获取内容(如选择图片)
2. ActivityResultLauncher
ActivityResultLauncher 是通过 registerForActivityResult 方法注册 Contract 后返回的对象,用于启动相应的 Activity 并接收结果。
3. 注册与调用流程
使用 registerForActivityResult 的基本流程如下:
- 定义或选择一个
ActivityResultContract - 调用
registerForActivityResult方法注册 Contract,并传入处理结果的回调 - 在需要的时候,通过返回的
ActivityResultLauncher启动 Activity
三、Kotlin 实现示例
下面通过完整的 Kotlin 代码示例,展示 registerForActivityResult 的各种用法。
1. 请求单个权限
kotlin
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
// 注册权限请求回调
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// 权限已授予,执行相应操作
Toast.makeText(this, "相机权限已授予", Toast.LENGTH_SHORT).show()
// 可以启动相机等操作
} else {
// 权限被拒绝,处理拒绝逻辑
Toast.makeText(this, "相机权限被拒绝", Toast.LENGTH_SHORT).show()
// 可以显示一个对话框解释为什么需要此权限
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 检查权限状态
val cameraButton = findViewById<Button>(R.id.camera_button)
cameraButton.setOnClickListener {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
) {
// 已经有权限,直接执行操作
Toast.makeText(this, "已经有相机权限", Toast.LENGTH_SHORT).show()
} else {
// 没有权限,请求权限
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
}
2. 请求多个权限
kotlin
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
class PermissionsActivity : AppCompatActivity() {
// 注册多个权限请求回调
private val requestMultiplePermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val granted = permissions.entries.all {
it.value == true
}
if (granted) {
Toast.makeText(this, "所有权限已授予", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "部分权限被拒绝", Toast.LENGTH_SHORT).show()
// 可以检查具体哪些权限被拒绝
permissions.entries.forEach {
if (!it.value) {
println("权限 ${it.key} 被拒绝")
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_permissions)
val permissionsButton = findViewById<Button>(R.id.permissions_button)
permissionsButton.setOnClickListener {
// 检查并请求多个权限
val permissionsToRequest = mutableListOf<String>()
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
permissionsToRequest.add(Manifest.permission.CAMERA)
}
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
permissionsToRequest.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
if (permissionsToRequest.isNotEmpty()) {
requestMultiplePermissions.launch(permissionsToRequest.toTypedArray())
} else {
Toast.makeText(this, "已经有所有请求的权限", Toast.LENGTH_SHORT).show()
}
}
}
}
3. 启动 Activity 获取结果
kotlin
// MainActivity.kt
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var resultTextView: TextView
// 注册 Activity 结果回调
private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val data: Intent? = result.data
val resultString = data?.getStringExtra("result_key") ?: "无结果"
resultTextView.text = "返回结果: $resultString"
} else {
resultTextView.text = "操作取消"
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
resultTextView = findViewById(R.id.result_text_view)
val startButton = findViewById<Button>(R.id.start_activity_button)
startButton.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
// 可以传递数据到 SecondActivity
intent.putExtra("request_key", "来自 MainActivity 的数据")
startForResult.launch(intent)
}
}
}
// SecondActivity.kt
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val editText = findViewById<EditText>(R.id.result_edit_text)
val returnButton = findViewById<Button>(R.id.return_result_button)
// 获取从 MainActivity 传递的数据
val requestData = intent.getStringExtra("request_key")
if (!requestData.isNullOrEmpty()) {
editText.setText(requestData)
}
returnButton.setOnClickListener {
val result = editText.text.toString()
val intent = Intent()
intent.putExtra("result_key", result)
setResult(RESULT_OK, intent)
finish()
}
}
}
4. 拍摄照片
kotlin
import android.content.ContentValues
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Button
import android.widget.ImageView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import java.text.SimpleDateFormat
import java.util.*
class CameraActivity : AppCompatActivity() {
private lateinit var imageView: ImageView
private var currentPhotoUri: Uri? = null
// 注册拍摄照片回调
private val takePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
// 照片拍摄成功,更新 UI
currentPhotoUri?.let { uri ->
imageView.setImageURI(uri)
}
} else {
// 照片拍摄失败
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
imageView = findViewById(R.id.photo_image_view)
val cameraButton = findViewById<Button>(R.id.camera_button)
cameraButton.setOnClickListener {
// 创建照片文件的 URI
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "IMG_${SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())}.jpg")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
}
currentPhotoUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
currentPhotoUri?.let { uri ->
takePicture.launch(uri)
}
}
}
}
5. 从相册选择图片
kotlin
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
class GalleryActivity : AppCompatActivity() {
private lateinit var imageView: ImageView
// 注册选择图片回调
private val pickImage = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
// 更新 UI 显示选择的图片
imageView.setImageURI(uri)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
imageView = findViewById(R.id.selected_image_view)
val galleryButton = findViewById<Button>(R.id.gallery_button)
galleryButton.setOnClickListener {
// 指定 MIME 类型为 image/*,允许选择任何图片类型
pickImage.launch("image/*")
}
}
}
6. 自定义 ActivityResultContract
kotlin
// 自定义 Contract 示例:获取用户输入的文本
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContract
import androidx.appcompat.app.AppCompatActivity
// 自定义 Contract
class GetTextContract : ActivityResultContract<String, String?>() {
override fun createIntent(context: Context, input: String): Intent {
// 创建启动 Activity 的 Intent,并传递初始文本
return Intent(context, TextInputActivity::class.java).apply {
putExtra("initial_text", input)
}
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
// 解析返回的结果
return if (resultCode == AppCompatActivity.RESULT_OK && intent != null) {
intent.getStringExtra("result_text")
} else {
null
}
}
}
// 文本输入 Activity
class TextInputActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_text_input)
val editText = findViewById<android.widget.EditText>(R.id.text_edit_text)
val saveButton = findViewById<android.widget.Button>(R.id.save_button)
// 获取初始文本
val initialText = intent.getStringExtra("initial_text") ?: ""
editText.setText(initialText)
saveButton.setOnClickListener {
val result = editText.text.toString()
val intent = Intent()
intent.putExtra("result_text", result)
setResult(RESULT_OK, intent)
finish()
}
}
}
// 在主 Activity 中使用自定义 Contract
class CustomContractActivity : AppCompatActivity() {
private lateinit var resultTextView: android.widget.TextView
// 注册自定义 Contract
private val getTextLauncher = registerForActivityResult(GetTextContract()) { result ->
if (result != null) {
resultTextView.text = "输入的文本: $result"
} else {
resultTextView.text = "未输入文本"
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_contract)
resultTextView = findViewById(R.id.result_text)
val launchButton = findViewById<android.widget.Button>(R.id.launch_button)
launchButton.setOnClickListener {
// 启动自定义 Activity,并传递初始文本
getTextLauncher.launch("请输入文本...")
}
}
}
四、在 Fragment 中使用
在 Fragment 中使用 registerForActivityResult 与在 Activity 中类似,但有几点需要注意:
-
直接在 Fragment 中调用
registerForActivityResult,而不是通过requireActivity() -
确保在 Fragment 的生命周期开始前注册(如
onCreate或onViewCreated中)
kotlin
class MyFragment : Fragment() {
// 注册权限请求回调
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
// 权限已授予
} else {
// 权限被拒绝
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.request_permission_button).setOnClickListener {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
五、处理配置变更
registerForActivityResult 会自动处理配置变更(如屏幕旋转),无需额外操作。当 Activity 因配置变更重建时,注册的 launcher 会保持不变,结果仍然会正确传递给回调。
六、总结
registerForActivityResult 是 Android 提供的现代 Activity 结果处理 API,相比传统方式具有以下优势:
-
代码更简洁:启动 Activity 和处理结果的代码集中在一起
-
类型安全:使用泛型确保结果类型的正确性
-
生命周期感知:自动处理配置变更等生命周期事件
-
统一接口:在 Activity 和 Fragment 中使用方式一致
通过内置的各种 ActivityResultContract 和自定义 Contract 的能力,你可以轻松处理各种 Activity 结果场景,让代码更加清晰和易于维护。建议在新项目中完全采用这种方式处理 Activity 结果,并逐步在现有项目中替换旧的处理方式。