ActivityResultContracts 替代 startActivityForResult,迁移成本
ActivityResultContracts 替代 startActivityForResult,迁移成本
compileSdk 升到 31 之后,Android Studio 给所有 startActivityForResult 调用划上了删除线。Google 在 AndroidX Activity 1.2.0 里引入了 ActivityResultContracts,然后在后续版本里正式弃用了旧 API。看起来只是换种写法,但我把项目里一个核心流程改掉之后,线上崩溃监控立刻报了 IllegalStateException: LifecycleOwner is destroyed。这个 crash 在旧代码里根本不可能出现,因为它和 Activity 的启动机制完全不同。
旧机制与新机制的本质差异
startActivityForResult 的模型很简单。你在任何地方拿到一个 Activity 引用,调一行代码,带一个自己分配的 requestCode,然后回到统一入口 onActivityResult 里做路由。这个模型的缺陷众所周知:requestCode 容易冲突,回调逻辑全部挤在一个方法里,Activity 和 Fragment 的回调分发存在优先级陷阱。但这些缺陷至少是可预测的。
ActivityResultContracts 的底层是 ActivityResultRegistry。每个 ComponentActivity 内部维护了一个 registry,在 onCreate 时通过 LifecycleObserver 绑定到 Activity 的生命周期上。当你调用 registerForActivityResult,实际上是在向这个 registry 登记一个带唯一 key 的回调。key 是内部自动生成的,结果数据通过 Activity 的 onActivityResult 统一回收,再由 registry 根据 key 分发给对应的 lambda。
这个设计的第一个坑在于注册时机。旧 API 允许你在任何时刻调用 startActivityForResult,哪怕是在 onDestroy 前夕,最多就是收不到回调。新 API 要求你必须在 LifecycleOwner 到达 CREATED 状态时完成注册,通常在 onCreate 里初始化。如果你在 onResume 里尝试 registerForActivityResult,会直接抛出一个 IllegalStateException,告诉你不能在 STARTED 之后注册。这意味着所有原本延迟初始化的逻辑都要被推翻。
val launcher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK) {
handleData(result.data)
}
}这段代码必须放在初始化阶段,而不能在用户点击按钮后再创建。对于那种只在特定条件下才需要启动外部 Activity 的页面,你必须预先注册好 launcher,即使最终可能永远不会用到它。这改变了原先按需初始化的编程习惯。
Fragment 的重建与 Launcher 悬空
真正让我头疼的是 Fragment。androidx.fragment:1.3.0 开始,Fragment 直接实现了 ActivityResultCaller 接口,看起来是为 Fragment 量身定做的。但 Fragment 的生命周期比 Activity 复杂得多,尤其是 viewLifecycle 和 fragment lifecycle 分离之后。
我在一个列表页里这样写:在 onViewCreated 中注册 launcher,把 launcher 实例保存到 Fragment 的成员变量里。用户点击 Item,触发网络请求,请求完成后根据服务端返回的某个字段决定是否跳转到一个配置页面。旧代码里,网络回调中直接 startActivityForResult,没有任何问题。改成新 API 后,网络回调里调用 launcher.launch(intent)。
复现路径很清晰:用户点击 Item,触发请求,在请求返回前快速按返回键关闭当前页面。Fragment 走到 onDestroyView,但 Fragment 实例本身因为被返回栈保留还没走到 onDestroy。这时候网络请求返回,回调触发 launcher.launch(),崩溃。
java.lang.IllegalStateException: LifecycleOwner is destroyed
at androidx.activity.ActivityResultRegistry.register(ActivityResultRegistry.java:143)问题在于 launcher 绑定的是 Fragment 自身的 lifecycle,而不是 viewLifecycle。当 Fragment 从返回栈重新显示时,它的 lifecycle 会重新进入 CREATED 状态,但旧的 launcher 是否仍然有效取决于内部 registry