registerForActivityResult API 使用指南:从基础到实战

张开发
2026/5/7 10:40:58 15 分钟阅读
registerForActivityResult API 使用指南:从基础到实战
1. registerForActivityResult API 基础入门如果你是一名Android开发者肯定对startActivityForResult不陌生。这个经典的API让我们能够启动另一个Activity并接收返回结果。但随着Android开发的演进Google推出了更现代、更安全的替代方案——registerForActivityResult。这个API不仅解决了旧方法的一些痛点还带来了更清晰的代码结构和更好的类型安全。我第一次接触registerForActivityResult是在重构一个老项目时。当时项目中有大量分散在各处的startActivityForResult调用回调处理逻辑混乱不堪。迁移到新API后代码立刻变得整洁多了。registerForActivityResult的核心思想是将Activity结果的处理逻辑集中管理而不是像以前那样分散在onActivityResult中。要使用这个API首先需要确保你的项目依赖了正确版本的AndroidX库。最低要求是androidx.appcompat:appcompat:1.3.1或更高版本。如果你遇到api找不到的问题十有八九是依赖版本太旧了。更新依赖后这个API就会出现在你的代码补全列表中。2. 解决API找不到的常见问题在实际开发中很多开发者遇到的第一个拦路虎就是registerForActivityResult的api找不到。这个问题通常有几个常见原因我们来一一排查。首先检查你的build.gradle文件。确保在dependencies块中有如下依赖implementation androidx.appcompat:appcompat:1.3.1或者更高版本。我建议直接使用当前最新的稳定版因为后续版本可能修复了一些bug并添加了新功能。其次检查你的项目是否启用了AndroidX。在gradle.properties文件中应该有android.useAndroidXtrue android.enableJetifiertrue如果没有这两行就需要添加并同步项目。我曾经接手过一个老项目就是因为缺少这些配置导致新API无法使用。还有一个容易被忽视的问题是IDE缓存。有时候即使正确配置了依赖Android Studio仍然找不到新API。这时可以尝试点击File Invalidate Caches / Restart选择Invalidate and Restart等待IDE重启并重新索引项目3. ActivityResultLauncher的创建与使用理解了基础概念后我们来看看如何创建和使用ActivityResultLauncher。这是registerForActivityResult返回的对象负责启动目标Activity并处理返回结果。创建launcher的基本语法如下val launcher registerForActivityResult( ActivityResultContractInputType, OutputType(), callback )这里的InputType是你传递给目标Activity的参数类型OutputType是期望返回的结果类型。callback是一个lambda表达式用于处理返回结果。举个实际例子假设我们要启动一个Activity并传递字符串参数同时接收返回的字符串结果val messageLauncher registerForActivityResult( object : ActivityResultContractString, String() { override fun createIntent(context: Context, input: String): Intent { return Intent(thisMainActivity, DetailActivity::class.java).apply { putExtra(message, input) } } override fun parseResult(resultCode: Int, intent: Intent?): String { return if (resultCode Activity.RESULT_OK) { intent?.getStringExtra(result) ?: } else { } } } ) { result - // 处理返回结果 Toast.makeText(this, 收到返回: $result, Toast.LENGTH_SHORT).show() }使用时只需要调用messageLauncher.launch(你好这是传递的消息)4. 内置ActivityResultContracts详解Android为我们提供了一系列内置的ActivityResultContracts覆盖了大多数常见场景。使用这些预定义的Contract可以大大简化代码。StartActivityForResult是最通用的Contract相当于旧版的startActivityForResultval startForResult registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result - if (result.resultCode Activity.RESULT_OK) { val data result.data // 处理返回数据 } } // 启动Activity startForResult.launch(Intent(this, TargetActivity::class.java))权限请求是另一个常见场景。对于单个权限val requestPermission registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted - if (isGranted) { // 权限被授予 } else { // 权限被拒绝 } } // 请求权限 requestPermission.launch(Manifest.permission.CAMERA)对于多个权限val requestMultiplePermissions registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions - permissions.entries.forEach { val (permission, isGranted) it // 处理每个权限状态 } } // 请求多个权限 requestMultiplePermissions.launch(arrayOf( Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS ))媒体选择也是常见需求。选择单张图片val getContent registerForActivityResult( ActivityResultContracts.GetContent() ) { uri - // 处理选中的图片URI } getContent.launch(image/*)选择多张图片val getMultipleContents registerForActivityResult( ActivityResultContracts.OpenMultipleDocuments() ) { uris - uris.forEach { uri - // 处理每个选中的图片URI } } getMultipleContents.launch(arrayOf(image/*))5. 自定义ActivityResultContract实战虽然内置Contract已经覆盖了很多场景但有时我们需要自定义Contract来处理特定业务逻辑。自定义Contract需要实现两个关键方法createIntent和parseResult。假设我们要实现一个拍照并返回照片文件的Contractclass TakePhotoContract : ActivityResultContractUnit, File?() { override fun createIntent(context: Context, input: Unit): Intent { val photoFile createImageFile(context) val photoURI FileProvider.getUriForFile( context, ${context.packageName}.fileprovider, photoFile ) return Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply { putExtra(MediaStore.EXTRA_OUTPUT, photoURI) } } override fun parseResult(resultCode: Int, intent: Intent?): File? { return if (resultCode Activity.RESULT_OK) { latestPhotoFile } else { null } } companion object { private var latestPhotoFile: File? null private fun createImageFile(context: Context): File { val timeStamp SimpleDateFormat(yyyyMMdd_HHmmss, Locale.getDefault()).format(Date()) val storageDir context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) return File.createTempFile( JPEG_${timeStamp}_, .jpg, storageDir ).also { latestPhotoFile it } } } }使用这个自定义Contractval takePhotoLauncher registerForActivityResult(TakePhotoContract()) { file - file?.let { // 处理拍摄的照片文件 val bitmap BitmapFactory.decodeFile(it.absolutePath) imageView.setImageBitmap(bitmap) } } // 启动相机 takePhotoLauncher.launch(Unit)6. 生命周期与内存管理最佳实践registerForActivityResult的一个关键优势是它自动处理生命周期问题但使用时仍需注意一些细节。首先launcher的注册应该在Activity的onCreate或Fragment的onViewCreated中进行。这是因为registerForActivityResult需要在组件创建时就建立结果处理的绑定关系。如果在按钮点击事件中才注册可能会导致无法接收到返回结果。我曾经在一个项目中犯过这样的错误// 错误示范 fun onButtonClick() { val launcher registerForActivityResult(...) { ... } launcher.launch(...) }这样写的问题是每次点击按钮都会创建一个新的launcher而之前的launcher可能已经被销毁导致结果丢失。正确的做法是在类属性中声明launcherclass MainActivity : AppCompatActivity() { private val launcher registerForActivityResult(...) { ... } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... } fun onButtonClick() { launcher.launch(...) } }对于Fragment要注意在onDestroyView中取消注册避免内存泄漏class MyFragment : Fragment() { private var launcher: ActivityResultLauncherString? null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) launcher registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted - // 处理结果 } } override fun onDestroyView() { super.onDestroyView() launcher?.unregister() launcher null } }7. 复杂场景与常见问题解决在实际项目中我们经常会遇到一些复杂场景需要更巧妙地使用registerForActivityResult。场景一链式调用有时候我们需要按顺序启动多个Activity每个都依赖前一个的结果。这时可以使用嵌套的launcherval firstLauncher registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { firstResult - if (firstResult.resultCode RESULT_OK) { val secondLauncher registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { secondResult - // 处理第二个结果 } secondLauncher.launch(Intent(this, SecondActivity::class.java)) } } // 启动第一个Activity firstLauncher.launch(Intent(this, FirstActivity::class.java))场景二共享launcher有时多个操作可以使用同一个Contract但不同参数。例如选择不同类型的内容val contentPicker registerForActivityResult( ActivityResultContracts.GetContent() ) { uri - // 处理选中的内容 } // 选择图片 buttonPickImage.setOnClickListener { contentPicker.launch(image/*) } // 选择PDF buttonPickPdf.setOnClickListener { contentPicker.launch(application/pdf) }常见问题结果丢失有时候会发现回调没有被执行这通常有几个原因宿主Activity/Fragment在结果返回前被销毁在错误的生命周期方法中注册launcher使用了局部变量存储launcher导致被垃圾回收解决方法是确保在正确的生命周期方法中注册使用成员变量存储launcher处理好配置变更如屏幕旋转8. 性能优化与测试技巧随着项目中launcher数量的增加我们需要考虑性能和测试的问题。性能优化避免重复创建相同的launcher。对于可能多次使用的Contract应该在类初始化时就创建好。对于不常用的launcher可以在需要时创建用完后及时unregister。考虑使用懒加载private val lazyLauncher by lazy { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { ... } }测试技巧测试registerForActivityResult的行为需要模拟Activity的结果返回。使用AndroidX Test可以方便地做到这一点Test fun testActivityResult() { val scenario launchActivityMainActivity() // 模拟点击触发launcher onView(withId(R.id.button)).perform(click()) // 构建模拟结果 val resultData Intent().apply { putExtra(key, value) } val result ActivityResult(Activity.RESULT_OK, resultData) // 获取Activity的launcher并发送结果 scenario.onActivity { activity - activity.getLauncher().dispatchResult(result) } // 验证处理逻辑 onView(withId(R.id.resultView)).check(matches(withText(value))) }对于自定义Contract的测试应该分别测试createIntent和parseResult方法Test fun testCustomContractCreateIntent() { val contract MyCustomContract() val intent contract.createIntent(ApplicationProvider.getApplicationContext(), input) // 验证intent的内容 assertEquals(expected.action, intent.action) } Test fun testCustomContractParseResult() { val contract MyCustomContract() val resultIntent Intent().apply { putExtra(result_key, result_value) } val output contract.parseResult(Activity.RESULT_OK, resultIntent) // 验证解析结果 assertEquals(expected_output, output) }在实际项目中我发现合理组织launcher的创建和使用位置对代码可维护性有很大影响。通常我会在Activity/Fragment的顶部声明所有launcher就像声明其他成员变量一样。对于复杂的launcher逻辑可能会单独提取到一个辅助类中。

更多文章