概述

ActivityResultLauncher 是 Android Jetpack Activity 1.2.0 版本引入的新 API,用于替代传统的 startActivityForResult()onActivityResult() 方法。它提供了更加类型安全、简洁和易于测试的方式来处理 Activity 间的数据传递和结果回调。

为什么需要 ActivityResultLauncher?

传统方式的问题

  1. 内存泄漏风险onActivityResult() 方法可能在 Activity 销毁后仍被调用
  2. 代码分散:启动逻辑和结果处理逻辑分散在不同方法中
  3. 类型不安全:通过 Intent 传递数据缺乏类型检查
  4. 测试困难:难以进行单元测试
  5. 请求码管理:需要手动管理 requestCode,容易冲突

ActivityResultLauncher 的优势

  1. 类型安全:通过泛型提供编译时类型检查
  2. 生命周期感知:自动处理生命周期,避免内存泄漏
  3. 代码集中:启动和结果处理逻辑在同一位置
  4. 易于测试:支持单元测试
  5. 简化API:无需管理请求码

基本使用方法

1. 基础设置

首先确保项目中包含正确的依赖:

dependencies {
    implementation "androidx.activity:activity-ktx:1.7.2"
    implementation "androidx.fragment:fragment-ktx:1.6.1"
}

2. 注册 ActivityResultLauncher

在 Activity 或 Fragment 中注册 launcher:

class MainActivity : AppCompatActivity() {
    
    // 注册launcher - 必须在onCreate之前或onCreate中注册
    private val imagePickerLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri: Uri? ->
        // 处理结果
        uri?.let {
            handleSelectedImage(it)
        }
    }
    
    private val customActivityLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            val data = result.data?.getStringExtra("result")
            // 处理返回数据
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        setupViews()
    }
    
    private fun setupViews() {
        findViewById<Button>(R.id.btnPickImage).setOnClickListener {
            // 启动图片选择
            imagePickerLauncher.launch("image/*")
        }
        
        findViewById<Button>(R.id.btnStartActivity).setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            customActivityLauncher.launch(intent)
        }
    }
    
    private fun handleSelectedImage(uri: Uri) {
        // 处理选中的图片
        findViewById<ImageView>(R.id.imageView).setImageURI(uri)
    }
}

常用的 ActivityResultContract

1. 内置 Contract

class ExampleActivity : AppCompatActivity() {
    
    // 获取单个内容
    private val getContentLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> /* 处理结果 */ }
    
    // 获取多个内容
    private val getMultipleContentsLauncher = registerForActivityResult(
        ActivityResultContracts.GetMultipleContents()
    ) { uris -> /* 处理结果 */ }
    
    // 拍照
    private val takePictureLauncher = registerForActivityResult(
        ActivityResultContracts.TakePicture()
    ) { success -> /* 处理结果 */ }
    
    // 请求权限
    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted -> /* 处理权限结果 */ }
    
    // 请求多个权限
    private val requestMultiplePermissionsLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions -> /* 处理权限结果 */ }
    
    // 创建文档
    private val createDocumentLauncher = registerForActivityResult(
        ActivityResultContracts.CreateDocument("text/plain")
    ) { uri -> /* 处理结果 */ }
    
    // 打开文档
    private val openDocumentLauncher = registerForActivityResult(
        ActivityResultContracts.OpenDocument()
    ) { uri -> /* 处理结果 */ }
}

2. 自定义 Contract

class CustomContract : ActivityResultContract<String, String?>() {
    
    override fun createIntent(context: Context, input: String): Intent {
        return Intent(context, CustomActivity::class.java).apply {
            putExtra("input_data", input)
        }
    }
    
    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        return when (resultCode) {
            Activity.RESULT_OK -> intent?.getStringExtra("result_data")
            else -> null
        }
    }
}

// 使用自定义Contract
class MainActivity : AppCompatActivity() {
    private val customLauncher = registerForActivityResult(CustomContract()) { result ->
        result?.let {
            // 处理自定义结果
            Toast.makeText(this, "结果: $it", Toast.LENGTH_SHORT).show()
        }
    }
    
    private fun launchCustomActivity() {
        customLauncher.launch("输入数据")
    }
}

完整示例:文件选择器

这里是使用 ActivityResultLauncher 重构文件选择功能:

class ImprovedMainWebViewActivity : AppCompatActivity() {
    
    // 注册文件选择器
    private val filePickerLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        uri?.let { handleSelectedFile(it) }
    }
    
    // 注册文档选择器(推荐用于文件选择)
    private val documentPickerLauncher = registerForActivityResult(
        ActivityResultContracts.OpenDocument()
    ) { uri ->
        uri?.let { handleSelectedFile(it) }
    }
    
    // 注册多文件选择器
    private val multipleDocumentsLauncher = registerForActivityResult(
        ActivityResultContracts.OpenMultipleDocuments()
    ) { uris ->
        uris.forEach { handleSelectedFile(it) }
    }
    
    // 注册存储权限请求
    private val storagePermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val allGranted = permissions.values.all { it }
        if (allGranted) {
            openFileSelector()
        } else {
            Toast.makeText(this, "需要存储权限才能选择文件", Toast.LENGTH_SHORT).show()
        }
    }
    
    private var currentFileType = 3 // 默认所有文件
    private var currentScene = 10   // 默认场景
    
    fun takeFiles(scene: Int, type: Int = 3) {
        LogUtils.d("=== takeFiles 使用 ActivityResultLauncher ===")
        LogUtils.d("scene: $scene, type: $type")
        
        currentScene = scene
        currentFileType = type
        
        // 检查权限
        if (hasStoragePermission()) {
            openFileSelector()
        } else {
            requestStoragePermission()
        }
    }
    
    private fun hasStoragePermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            this, 
            Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED
    }
    
    private fun requestStoragePermission() {
        storagePermissionLauncher.launch(
            arrayOf(
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
        )
    }
    
    private fun openFileSelector() {
        val mimeType = when (currentFileType) {
            3 -> "*/*"
            4 -> "video/*"
            5 -> "application/pdf"
            6 -> "application/vnd.ms-excel"
            7 -> "application/msword"
            else -> "*/*"
        }
        
        try {
            // 使用 OpenDocument,更适合文件选择
            documentPickerLauncher.launch(arrayOf(mimeType))
        } catch (e: Exception) {
            LogUtils.e("文件选择器启动失败", e)
            // 降级到基础的 GetContent
            try {
                filePickerLauncher.launch(mimeType)
            } catch (e2: Exception) {
                LogUtils.e("降级方案也失败", e2)
                Toast.makeText(this, "无法打开文件选择器", Toast.LENGTH_SHORT).show()
            }
        }
    }
    
    private fun handleSelectedFile(uri: Uri) {
        // 文件处理逻辑...
        LogUtils.d("处理选中的文件: $uri")
        // 其他处理逻辑保持不变
    }
}

重要注意事项

1. 注册时机

class WrongExample : AppCompatActivity() {
    private lateinit var launcher: ActivityResultLauncher<String>
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ❌ 错误:在条件语句中注册
        if (someCondition) {
            launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { }
        }
        
        // ❌ 错误:在点击事件中注册
        button.setOnClickListener {
            launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { }
        }
    }
}

class CorrectExample : AppCompatActivity() {
    // ✅ 正确:在类级别声明和注册
    private val launcher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> 
        // 处理结果
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // ✅ 正确:在onCreate中注册也可以
        val anotherLauncher = registerForActivityResult(
            ActivityResultContracts.GetContent()
        ) { uri -> 
            // 处理结果
        }
    }
}

2. 生命周期管理

class LifecycleExample : AppCompatActivity() {
    private val launcher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        // ✅ 这里会自动检查生命周期状态
        // 只有在STARTED状态之后才会执行
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            handleResult(uri)
        }
    }
    
    private fun handleResult(uri: Uri?) {
        // 处理结果时确保Activity仍然活跃
        if (!isFinishing && !isDestroyed) {
            // 安全地更新UI
        }
    }
}

3. Fragment 中的使用

class ExampleFragment : Fragment() {
    
    // ✅ 正确:在Fragment中注册
    private val launcher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        // 处理结果
        if (isAdded && context != null) {
            handleResult(uri)
        }
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        view.findViewById<Button>(R.id.button).setOnClickListener {
            launcher.launch("image/*")
        }
    }
}

4. 内存泄漏防护

class MemoryLeakSafeExample : AppCompatActivity() {
    
    private val launcher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        // 使用弱引用避免内存泄漏
        weakReference.get()?.let { activity ->
            if (!activity.isFinishing) {
                activity.handleResult(uri)
            }
        }
    }
    
    private val weakReference = WeakReference(this)
    
    private fun handleResult(uri: Uri?) {
        // 处理结果
    }
}

最佳实践

1. 错误处理

class ErrorHandlingExample : AppCompatActivity() {
    
    private val launcher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        try {
            if (uri != null) {
                handleFile(uri)
            } else {
                // 用户取消选择
                Toast.makeText(this, "未选择文件", Toast.LENGTH_SHORT).show()
            }
        } catch (e: Exception) {
            LogUtils.e("处理文件失败", e)
            Toast.makeText(this, "文件处理失败: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    }
    
    private fun launchFilePicker() {
        try {
            launcher.launch("*/*")
        } catch (e: ActivityNotFoundException) {
            Toast.makeText(this, "设备不支持文件选择", Toast.LENGTH_SHORT).show()
        } catch (e: Exception) {
            LogUtils.e("启动文件选择器失败", e)
            Toast.makeText(this, "无法打开文件选择器", Toast.LENGTH_SHORT).show()
        }
    }
}

2. 链式权限请求

class PermissionChainExample : AppCompatActivity() {
    
    private val cameraPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            // 权限获得,请求存储权限
            storagePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
        } else {
            Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show()
        }
    }
    
    private val storagePermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            // 所有权限都获得,启动相机
            openCamera()
        } else {
            Toast.makeText(this, "需要存储权限", Toast.LENGTH_SHORT).show()
        }
    }
    
    private val cameraLauncher = registerForActivityResult(
        ActivityResultContracts.TakePicture()
    ) { success ->
        if (success) {
            Toast.makeText(this, "拍照成功", Toast.LENGTH_SHORT).show()
        }
    }
    
    private fun requestPermissionsAndTakePhoto() {
        cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
    }
    
    private fun openCamera() {
        val photoFile = createImageFile()
        val photoURI = FileProvider.getUriForFile(
            this,
            "${applicationContext.packageName}.provider",
            photoFile
        )
        cameraLauncher.launch(photoURI)
    }
}

3. 测试支持

class TestableActivity : AppCompatActivity() {
    
    // 使用接口来支持测试
    interface FilePicker {
        fun pickFile(callback: (Uri?) -> Unit)
    }
    
    class RealFilePicker(private val activity: AppCompatActivity) : FilePicker {
        private val launcher = activity.registerForActivityResult(
            ActivityResultContracts.GetContent()
        ) { uri ->
            callback?.invoke(uri)
        }
        
        private var callback: ((Uri?) -> Unit)? = null
        
        override fun pickFile(callback: (Uri?) -> Unit) {
            this.callback = callback
            launcher.launch("*/*")
        }
    }
    
    private val filePicker: FilePicker = RealFilePicker(this)
    
    fun selectFile() {
        filePicker.pickFile { uri ->
            handleSelectedFile(uri)
        }
    }
    
    private fun handleSelectedFile(uri: Uri?) {
        // 处理文件
    }
}

// 测试类
class TestableActivityTest {
    
    class MockFilePicker : TestableActivity.FilePicker {
        override fun pickFile(callback: (Uri?) -> Unit) {
            // 模拟文件选择
            callback.invoke(mockUri)
        }
    }
    
    // 测试方法...
}

性能优化建议

1. 避免重复注册

// ❌ 错误:每次都创建新的launcher
class BadExample : AppCompatActivity() {
    fun pickImage() {
        val launcher = registerForActivityResult(
            ActivityResultContracts.GetContent()
        ) { uri -> /* 处理 */ }
        launcher.launch("image/*")
    }
}

// ✅ 正确:复用launcher
class GoodExample : AppCompatActivity() {
    private val imageLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> /* 处理 */ }
    
    fun pickImage() {
        imageLauncher.launch("image/*")
    }
}

2. 合理选择Contract类型

class OptimizedExample : AppCompatActivity() {
    
    // 针对不同场景使用不同的Contract
    private val imageLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()  // 快速图片选择
    ) { uri -> handleImage(uri) }
    
    private val documentLauncher = registerForActivityResult(
        ActivityResultContracts.OpenDocument()  // 文档选择,支持持久化权限
    ) { uri -> handleDocument(uri) }
    
    private val multipleLauncher = registerForActivityResult(
        ActivityResultContracts.GetMultipleContents()  // 多文件选择
    ) { uris -> handleMultipleFiles(uris) }
    
    fun chooseFile(type: String) {
        when (type) {
            "image" -> imageLauncher.launch("image/*")
            "document" -> documentLauncher.launch(arrayOf("application/pdf"))
            "multiple" -> multipleLauncher.launch("*/*")
        }
    }
}

迁移指南

从 startActivityForResult 迁移

// 旧代码
class OldWay : AppCompatActivity() {
    companion object {
        const val REQUEST_IMAGE = 1001
        const val REQUEST_DOCUMENT = 1002
    }
    
    fun pickImage() {
        val intent = Intent(Intent.ACTION_GET_CONTENT)
        intent.type = "image/*"
        startActivityForResult(intent, REQUEST_IMAGE)
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
            REQUEST_IMAGE -> {
                if (resultCode == RESULT_OK) {
                    val uri = data?.data
                    handleImage(uri)
                }
            }
            REQUEST_DOCUMENT -> {
                if (resultCode == RESULT_OK) {
                    val uri = data?.data
                    handleDocument(uri)
                }
            }
        }
    }
}

// 新代码
class NewWay : AppCompatActivity() {
    
    private val imageLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> handleImage(uri) }
    
    private val documentLauncher = registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> handleDocument(uri) }
    
    fun pickImage() {
        imageLauncher.launch("image/*")
    }
    
    fun pickDocument() {
        documentLauncher.launch("application/pdf")
    }
}

总结

ActivityResultLauncher 是 Android 现代化开发的重要组成部分,它提供了:

  1. 更好的类型安全性
  2. 自动的生命周期管理
  3. 简化的API设计
  4. 更好的测试支持
  5. 避免内存泄漏

在新项目中建议直接使用 ActivityResultLauncher,在现有项目中建议逐步迁移。遵循本文提到的最佳实践,可以写出更加健壮和可维护的代码。

关键记住点

  • 必须在 onCreate 之前或onCreate中注册
  • 回调会自动处理生命周期
  • 选择合适的 Contract 类型
  • 做好错误处理
  • 支持单元测试

ActivityResultLauncher 让 Android 开发变得更加现代化和安全,是每个 Android 开发者都应该掌握的重要工具。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐