ActivityResultLauncher:它是什么以及如何使用

作者: IT Sectr 发布日期: 2026-06-10 阅读时间: 9 分钟

ActivityResultLauncher 是 Android Activity Result API 的一个组件,在 androidx.activity 库的 Activity 1.2.0 版本中引入。它取代了自 Android SDK 创建以来就存在的过时方法 startActivityForResult 和 onActivityResult。根据 Android Developers (2024),新API消除了与Activity的紧密耦合和缺乏类型安全的问题。ActivityResultLauncher 提前注册并使用 Contract 对输入和输出数据进行严格类型化。

要点

  • ActivityResultLauncher — 用于从Activity获取结果的新API,取代过时的startActivityForResult
  • Contract — 定义特定场景输入和输出数据类型的对象
  • 注册 在调用launch之前通过registerForActivityResult完成
  • Callback — 在目标Activity完成后调用,返回结果
  • API可用 从Activity 1.2.0开始,适用于Activity、Fragment和Compose

什么是ActivityResultLauncher

ActivityResultLauncher 是来自 androidx.activity.result 包的一个类,提供了一种类型安全的机制来启动Activity并获取结果。Launcher 通过 registerForActivityResult 方法创建,该方法接受两个参数:Contract(描述输入和输出类型)和 ActivityResultCallback(结果处理器)。注册后,launcher 即可通过 launch 方法进行调用。

与旧API相比的关键区别在于注册和启动的分离。注册在初始化阶段(Activity.onCreate 或 Fragment.onCreate)完成,callback 与 launcher 绑定一次,并在结果返回时保证触发。这解决了 onActivityResult 以意外顺序或在已销毁的Activity上触发的问题。

ActivityResultLauncher 支持所有以前通过 onActivityResult 处理的场景:启动相机、图库、请求联系人、权限和自定义Activity。此外,API是可扩展的:开发人员可以为Activity之间数据交换的特定场景创建自定义Contract。

为什么Activity Result API取代了startActivityForResult

startActivityForResult 自 API Level 1(2008年)以来一直是 Android SDK 的一部分,并在超过12年的时间里一直是获取Activity结果的主要方式。然而,这种方法存在 Google 在 Activity Result API 中解决的根本性缺陷。让我们看看主要问题以及新API如何解决它们。

问题1:与Activity紧密耦合

startActivityForResult 方法通过 requestCode(一个传递给 onActivityResult 的任意整数)与 Activity 和 Fragment 关联。开发人员手动将代码与启动的操作匹配,这导致代码重用和继承时出现错误。ActivityResultLauncher 完全消除了 requestCode:callback 在注册阶段绑定到特定的 launcher,并且仅为其调用。

问题2:屏幕旋转时结果丢失

配置更改(屏幕旋转、语言更改)时,Activity 被重新创建,onActivityResult 可能无法触发——callback 丢失。Activity Result API 通过 SavedStateRegistry 自动保存和恢复 launcher 状态,确保即使在 Activity 重新创建后也能收到结果。

问题3:缺乏类型安全

旧API通过带有 Bundle 的 Intent 传递结果,其中的键和数据类型不受编译器检查。Activity Result API 使用 Contract——一个通用接口,定义输入数据类型(I)和结果类型(O)。类型不匹配错误在编译阶段被检测到,而不是在运行时。

特性startActivityForResultActivityResultLauncher
RequestCode需要手动管理自动,不需要
类型安全通用 Contract
旋转时保存丢失SavedStateRegistry
最低APIAPI Level 1Activity 1.2.0
在Compose中使用不支持rememberLauncherForActivityResult

Activity Result API的主要约定

Contract 是一个 ActivityResultContract<I, O> 接口,定义了如何启动 Activity 以及如何解释结果。Google 提供了一组内置约定,覆盖了开发人员的大部分需求。

StartIntentSenderForResult

StartIntentSenderForResult 是启动 IntentSender 的基本约定。用于系统场景,例如通过 Google Sign-In 进行身份验证或通过 Google Pay 进行支付。输入参数——PendingIntent,输出——带有代码和 Intent 的 ActivityResult。

RequestMultiplePermissions

RequestMultiplePermissions 是在 Android 6.0+ 中同时请求多个权限的约定。输入参数——包含权限名称的 String 数组,输出——包含每个请求结果的 Map<String, Boolean>。以前这需要在 onRequestPermissionsResult 中手动解析并匹配请求代码。

TakePicture 和 TakeVideo

TakePicture 是通过系统相机拍照的约定。输入——保存照片的 Uri,输出——Boolean(成功)。TakeVideo 类似地处理视频。这些约定取代了过时的 MediaStore.ACTION_IMAGE_CAPTURE,后者在不同设备上行为不稳定。

GetContent 和 OpenDocument

GetContent 是通过系统选择器选择内容的约定。输入——MIME类型(例如 image/*),输出——所选文件的 Uri。OpenDocument 的区别在于支持多选和按文档类型过滤。两个约定都通过 SAF(Storage Access Framework)工作。

CreateDocument 和 OpenDocumentTree

CreateDocument 是通过系统对话框创建新文档的约定。用户选择名称和文件夹,系统返回用于写入的 Uri。OpenDocumentTree 提供对整个目录的访问——用户选择一个文件夹,应用程序获取用于读取和写入其中所有文件的 tree-uri。

在Activity和Fragment中使用

在经典Android中使用ActivityResultLauncher的基本模式包括两个步骤:在初始化阶段通过 registerForActivityResult 注册,以及响应用户操作调用 launch。让我们看一个从图库中选择图像的典型示例。

在Activity中注册和启动

在 Activity 的 onCreate 中注册launcher——这确保在任何可能的调用之前 callback 已准备就绪。切勿在启动前立即注册 launcher——这违反了 API 约定,并可能导致 Activity 重新创建时结果丢失。

kotlin
class MainActivity : AppCompatActivity() {
    private val pickImageLauncher =
        registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
            uri?.let { binding.imageView.setImageURI(it) }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pickImageLauncher.launch("image/*")
    }
}

在Fragment中使用

Fragment中,注册在 onCreate、onAttach 或 onCreateView 中的初始化时完成。FragmentActivity 通过父级 Activity 传递 launcher,因此结果在 Fragment 内部处理,而不是在 Activity 中。与 onActivityResult 相比,这改进了封装,在 onActivityResult 中,所有 Fragment 的所有结果都集中在一个 Activity 方法中。

kotlin
class ProfileFragment : Fragment() {
    private val cameraLauncher =
        registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
            if (success) { updateProfilePhoto() }
        }

    fun takePhoto(photoUri: Uri) {
        cameraLauncher.launch(photoUri)
    }
}

Jetpack Compose中的ActivityResultLauncher

Jetpack Compose 为 Activity Result API 提供了特殊的 composable 功能——rememberLauncherForActivityResult。与经典方法不同,在 Compose 中,launcher 是通过 remember 绑定到 composable 生命周期的对象创建的。这使得可以在无需直接访问 Activity 或 Fragment 的情况下,以完全声明式的方式使用 Activity Result API。

rememberLauncherForActivityResult

rememberLauncherForActivityResult 接受 Contract 和 callback,返回 ActivityResultLauncher。launcher 在重组期间保留,并在退出组合时自动清理。调用 launch 作为对事件的响应发生——例如按钮点击或状态更改。

kotlin
@Composable
fun PhotoPicker() {
    val context = LocalContext.current
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri -> handleImage(uri) }

    Button(onClick = { launcher.launch("image/*") }) {
        Text("选择照片")
    }
}

在Compose中处理权限

在 Compose 中请求权限也通过 rememberLauncherForActivityResult 使用 RequestPermission 或 RequestMultiplePermissions 约定完成。Google 建议使用 accompanist-permissions,但其底层也使用 Activity Result API。为了方便控制权限状态,可以将状态存储在 remember 或 ViewModel 中。

常见错误和最佳实践

Activity Result API 消除了旧方法的许多问题,但错误使用可能导致新类型的错误。让我们看看最常见的问题以及如何避免它们。

错误:在 lambda 或协程内部注册

launcher 的注册必须在组件初始化时完成——在 Activity 的 onCreate 或 Fragment 的初始化器中。如果在 lambda、callback 或协程内部注册 launcher,Activity 重新创建时注册可能会重复执行,旧 launcher 将失去与结果的联系。

错误:使用相同键注册多个 launcher

每个 launcher 获得一个唯一的键来保存状态。如果在同一组件中使用相同的 Contract 注册两个 launcher,SavedStateRegistry 可能会覆盖其中一个的状态。Android Studio 通过 UnnecessaryRegisterForActivityResult lint 规则对此发出警告,但最好手动控制唯一性。

最佳实践:始终处理 null 结果

用户可以取消操作——按下系统返回键、最小化应用程序或切换到其他应用程序。在这种情况下,callback 将收到 null 或带有 RESULT_CANCELED 的 ActivityResult。使用前始终检查结果是否为 null,以避免 NullPointerException。

最佳实践:为可重用逻辑创建自定义 Contract

如果您的应用程序经常启动类似场景——例如选择联系人并返回姓名和电话——请创建自己的 Contract。这提高了代码的可读性,并允许集中更改启动和结果处理的逻辑。

kotlin
class PickContactContract : ActivityResultContract<Void, ContactData?>() {
    override fun createIntent(context: Context, input: Void?) =
        Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE)

    override fun parseResult(resultCode: Int, intent: Intent?) =
        intent?.data?.let { queryContact(it) }
}

常见问题

可以在 ViewModel 中使用 ActivityResultLauncher 吗?

不能 — ActivityResultLauncher 需要 Activity 或 Fragment 上下文进行注册。ViewModel 仅用于存储状态,launcher 在 Activity 或 Fragment 中创建,并将结果传递给 ViewModel。

Activity Result API 需要的最低 SDK 是什么?

Activity Result API 从 activity-ktx 1.2.0 库开始可用。最低 SDK — API Level 14(Android 4.0),但大多数约定仅在 API Level 19+ 上工作。

如果在收到结果之前调用 launch 两次会发生什么?

重复调用 launch 在第一个操作完成之前将被忽略。Activity Result API 不支持并行启动——在新调用之前等待第一个操作的 callback。

在旧代码中用什么替换 onActivityResult?

迁移通过将 startActivityForResult 调用替换为 registerForActivityResult 并使用相应的 Contract 来完成。删除 onActivityResult 并在 launcher 的 callback 中处理结果。Google 在 Android Developers 文档中提供了迁移指南。

ActivityResultLauncher 是否与 ML Kit 或 Barcode Scanner 等库一起工作?

可以,许多库支持通过 ActivityResultContracts 进行集成。例如,ML Kit Barcode Scanner 使用 StartIntentSenderForResult 来启动扫描器。请查看具体库的文档。

总结

  • ActivityResultLauncher — 取代过时 startActivityForResult 的现代类型安全 API
  • Contract 定义输入和输出数据的类型,消除手动匹配 requestCode
  • 注册 在初始化阶段完成,结果通过 callback 保证交付
  • 内置约定 涵盖相机、图库、权限、文档和联系人
  • Jetpack Compose 使用 rememberLauncherForActivityResult 与 API 协作
  • 自定义 Contract 允许在组件之间重用启动逻辑
  • API 保存状态 在配置更改时通过 SavedStateRegistry

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读