第8章:Hilt与Fragment——Fragment中依赖注入的注意事项、Fragment与Activity共享依赖

Fragment,说实话,是Android依赖注入里最容易踩坑的地方。

为什么?因为Fragment的生命周期太复杂了。它不像Activity那样简单直接,Fragment有onAttach、onCreate、onCreateView、onViewCreated……这一串回调,而且还能被销毁重建。我早期用Dagger的时候,就在Fragment上翻过车——注入的依赖在Fragment重建后变成了null,查了半天才发现是注入时机不对。

这一章,我们就来聊聊Fragment里用Hilt的那些门道。

8.1 Fragment的注入时机

先看一个最基础的例子。用Hilt给Fragment注入依赖,你只需要加一个注解:

@AndroidEntryPoint
class UserProfileFragment : Fragment() {

    @Inject
    lateinit var userRepository: UserRepository

    override fun onAttach(context: Context) {
        super.onAttach(context)
        // 注意:此时userRepository还没有注入!
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 这里userRepository已经可用了
        Log.d("TAG", "userRepository = $userRepository")
    }
}

嗯,这里要注意:@Inject标注的字段,在super.onCreate()之后才真正注入完毕。如果你在onAttach里就急着用,那肯定拿到的是null。

⚠️ 避坑指南
我曾经在onAttach里直接调用注入的依赖,结果空指针异常。后来养成习惯:所有依赖都在onCreate之后使用,或者在onViewCreated里初始化UI。

8.2 Fragment与Activity共享依赖

实际项目中,Fragment经常需要访问Activity级别的依赖。比如,一个登录Activity里有多个Fragment,它们都需要共享同一个SessionManager

Hilt提供了很优雅的方式——@ActivityScoped

@Module
@InstallIn(ActivityComponent::class)
object AppModule {

    @Provides
    @ActivityScoped
    fun provideSessionManager(): SessionManager {
        return SessionManager()
    }
}

这样,Activity和它里面的所有Fragment,拿到的都是同一个SessionManager实例。

在Fragment里获取Activity级别的依赖,有两种方式:

  1. 直接注入——如果依赖是@ActivityScoped的,Fragment里直接@Inject就行。
  2. 通过Activity获取——用(requireActivity() as MainActivity).sessionManager,但我不推荐这么做,耦合太强。
💡 我的习惯
我个人倾向于用第一种方式。只要把依赖声明为@ActivityScoped,Fragment里直接注入,代码干净,测试也方便。

8.3 FragmentScope vs ActivityScope

你可能会问:那Fragment自己的作用域呢?

Hilt提供了@FragmentScoped。它和@ActivityScoped的区别,说白了就是生命周期不同:

作用域 生命周期 适用场景
@ActivityScoped Activity创建到销毁 整个Activity共享的依赖,如SessionManager
@FragmentScoped Fragment创建到销毁 仅当前Fragment需要的依赖,如列表适配器
@Singleton 应用全局 全局唯一的依赖,如Retrofit实例

举个例子:

@Module
@InstallIn(FragmentComponent::class)
object FragmentModule {

    @Provides
    @FragmentScoped
    fun provideUserAdapter(): UserAdapter {
        return UserAdapter()
    }
}

这样,每个Fragment都有自己的UserAdapter实例,互不干扰。

🔑 关键点
选择作用域时,问自己一个问题:这个依赖应该活多久?如果多个Fragment共享,用@ActivityScoped;如果每个Fragment独立,用@FragmentScoped。

8.4 避坑:Fragment重建与注入

Fragment有个很坑的地方——它会被系统重建。比如屏幕旋转时,Fragment会走onDestroy然后重新创建。这时候,Hilt会重新注入依赖。

但如果你在Fragment里保存了状态,比如在onSaveInstanceState里存了数据,重建后注入的依赖是新的实例,之前的状态就丢了。

怎么解决?

  • ViewModel保存状态,ViewModel本身是@HiltViewModel,可以注入依赖。
  • 或者,把需要持久化的数据存到SavedStateHandle里。
@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val userId: String
        get() = savedStateHandle.get("userId") ?: ""
}

这样,即使Fragment重建,ViewModel里的数据还在。

⚠️ 我曾经踩过的坑
有一次,我在Fragment里用@Inject注入了一个网络请求的依赖,然后在onSaveInstanceState里保存了请求结果。结果屏幕旋转后,Fragment重建,注入的是新的依赖实例,但保存的结果还在——这就导致新旧数据不一致。后来改用ViewModel,问题解决。

8.5 知识体系总览

下面这张图,帮你理清Fragment依赖注入的核心逻辑:

Fragment依赖注入核心逻辑 Fragment 注入时机:onCreate之后才可用 作用域选择:@ActivityScoped vs @FragmentScoped @ActivityScoped 多个Fragment共享同一个实例 @FragmentScoped 每个Fragment独立实例 ⚠️ 注意:Fragment重建时,注入的依赖会重新创建

8.6 实战建议

最后,给你几个我在项目中总结出来的建议:

  • 能用ViewModel就别直接注入Fragment——ViewModel的生命周期更可控,而且天然支持状态保存。
  • Fragment之间共享数据,用ActivityScoped + ViewModel——别用EventBus或者全局变量,那会让依赖关系变得一团糟。
  • 注入的依赖如果涉及UI,记得在onViewCreated里初始化——因为onCreate时视图还没创建,你拿不到View。
  • 测试时,用@ActivityScoped的依赖更容易mock——因为作用域明确,替换起来方便。
💡 一个小技巧
如果你不确定某个依赖应该用@ActivityScoped还是@FragmentScoped,先默认用@ActivityScoped。等发现多个Fragment需要独立实例时,再改成@FragmentScoped。这样改动成本最低。

好了,这一章的内容就到这里。Fragment的依赖注入,说白了就是把握好时机和范围。时机对了,依赖不会空;范围对了,数据不会乱。


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