Android 中的 Activity 结果处理:registerForActivityResult 详解

1,550 阅读4分钟

在 Android 开发中,处理 Activity 结果是一个常见的需求。传统的 startActivityForResult 和 onActivityResult 方法在处理复杂场景时存在一些问题,比如代码分散、类型安全问题以及配置变更时的处理困难。为了解决这些问题,Android 引入了 registerForActivityResult API,提供了一种更简洁、更安全的方式来处理 Activity 结果。

一、为什么需要 registerForActivityResult?

传统的 Activity 结果处理方式有以下痛点:

  1. 代码分散:启动 Activity 的代码和处理结果的代码分散在不同的方法中,导致逻辑不连贯。

  2. 类型不安全:结果数据需要手动转换,容易出现类型转换异常。

  3. 生命周期管理复杂:在配置变更(如屏幕旋转)时需要额外处理以保留结果。

  4. 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 的基本流程如下:

  1. 定义或选择一个 ActivityResultContract
  2. 调用 registerForActivityResult 方法注册 Contract,并传入处理结果的回调
  3. 在需要的时候,通过返回的 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 中类似,但有几点需要注意:

  1. 直接在 Fragment 中调用 registerForActivityResult,而不是通过 requireActivity()

  2. 确保在 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,相比传统方式具有以下优势:

  1. 代码更简洁:启动 Activity 和处理结果的代码集中在一起

  2. 类型安全:使用泛型确保结果类型的正确性

  3. 生命周期感知:自动处理配置变更等生命周期事件

  4. 统一接口:在 Activity 和 Fragment 中使用方式一致

通过内置的各种 ActivityResultContract 和自定义 Contract 的能力,你可以轻松处理各种 Activity 结果场景,让代码更加清晰和易于维护。建议在新项目中完全采用这种方式处理 Activity 结果,并逐步在现有项目中替换旧的处理方式。