第20章:Koin与Fragment:fragmentScope、fragment注入的注意事项

Fragment,说实话,是Android依赖注入里最容易出幺蛾子的地方。

为什么?因为Fragment的生命周期太复杂了。它依附于Activity,又可能被销毁重建,还经常在FragmentManager里进进出出。我见过太多项目,Koin配置得挺好,一到Fragment就崩了。嗯,今天我们就来聊聊怎么搞定它。

20.1 FragmentScope:到底要不要用?

先说说scope。Koin提供了fragmentScope,说白了就是让某个实例的生命周期跟Fragment绑定。Fragment创建,它创建;Fragment销毁,它销毁。

我个人习惯是:能不scope就不scope。为什么?因为Fragment的scope管理起来很麻烦,尤其是嵌套Fragment、ViewPager+Fragment这种场景,scope的生命周期边界容易搞混。

但有些场景确实需要。比如一个Fragment里有个网络请求的ViewModel,你希望它只在当前Fragment存活期间存在。这时候scope就派上用场了。

核心原则:只有当你明确需要「Fragment级别的单例」时,才使用fragmentScope。否则,用single()或factory()就够了。

20.2 注入Fragment的正确姿势

Fragment的注入,其实就两种方式:

  • 构造注入:通过构造函数传参。这是最推荐的方式。
  • 属性注入:在Fragment的onCreateonViewCreated里调用inject()

先看构造注入的例子:

class MyFragment(private val repository: MyRepository) : Fragment() {
    // 直接使用repository,不需要inject()
}

然后在Koin模块里注册:

val myModule = module {
    fragment { MyFragment(get()) }
}

这样写的好处是:依赖关系一目了然,测试也方便。我在项目中遇到过一个坑——用属性注入时,Fragment被重建后,属性还没注入就被调用了,直接空指针。换成构造注入后,再也没出过这个问题。

但构造注入有个限制:Fragment必须有无参构造函数才能被系统重建。你可能会问:「那构造注入后,系统重建怎么办?」

答案是:Koin会自动处理。它会在Fragment重建时重新注入依赖。前提是你得在Fragment的onCreate里调用inject()——嗯,这又绕回属性注入了。

我的建议:如果Fragment不需要被系统重建(比如你控制了返回栈),用构造注入。如果需要重建,用属性注入,但一定要在onCreate里调用inject(),别放在onViewCreated里。

20.3 避坑指南:我曾经踩过的坑

讲几个我真实遇到过的坑,你大概率也会碰到。

坑1:Fragment重建后,scope里的实例还是旧的

我曾经在一个项目里用fragmentScope管理一个网络请求的ViewModel。Fragment被销毁重建后,scope里的ViewModel还是旧的,导致数据错乱。

原因是什么?因为fragmentScope的生命周期跟Fragment的onDestroy绑定。如果Fragment只是被重建(比如旋转屏幕),onDestroy不会调用,scope就不会被清除。

解决方案:在onDestroyView里手动关闭scope,或者在onCreate里重新创建scope。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 每次创建时重新获取scope
    val scope = getKoin().createScope("myScope", named("fragment"))
    // 注入依赖
    inject()
}

坑2:嵌套Fragment的scope冲突

父Fragment和子Fragment都用fragmentScope,结果scope的ID冲突了。Koin默认用Fragment的类名作为scope的ID,如果两个Fragment类名一样(比如都是ListFragment),就会出问题。

解决办法:给scope指定唯一的ID。

val scope = getKoin().createScope(
    "fragment_${this.javaClass.simpleName}_${this.id}",
    named("fragment")
)

坑3:在DialogFragment里注入

DialogFragment的生命周期跟普通Fragment不太一样。它的onCreate可能在show()之后才调用。如果你在onCreate里注入,可能拿不到正确的scope。

我建议:DialogFragment里直接用factory()注入,别用scope。省心。

20.4 知识体系图

下面这张图总结了Fragment注入的核心逻辑和注意事项:

Fragment注入核心逻辑 Fragment注入 构造注入(推荐) 属性注入(备选) 依赖关系清晰,测试方便 系统重建时需配合inject() 需在onCreate中调用inject() 避免在onViewCreated中注入 注意:scope生命周期、嵌套Fragment、DialogFragment

20.5 最佳实践总结

说了这么多,最后给你一个可以直接用的清单:

场景 推荐方式 注意事项
普通Fragment(不重建) 构造注入 确保Koin模块里用fragment { }注册
普通Fragment(可能重建) 属性注入(onCreate中) 配合inject(),不要用by inject()
需要Fragment级别单例 fragmentScope 手动管理scope的创建和销毁
嵌套Fragment 构造注入 + 唯一scope ID 避免scope ID冲突
DialogFragment factory()注入 不要用scope,生命周期特殊

最后提醒一句:Fragment的注入,核心就是搞清楚「谁创建、谁销毁」。别把scope的生命周期搞混了,否则你会看到各种奇怪的NPE和内存泄漏。我当年在一个项目里排查了三天,最后发现是scope没关——嗯,从那以后我对scope就特别谨慎。


公众号:蓝海资料掘金营,微信deep3321