第一篇:从一个 Dagger 报错开始:手把手带你搭建 Hilt 依赖注入的护城河

摘要

在现代 Android 开发中,Hilt 已经成为了 Google 官方推荐的依赖注入(DI)标准方案。然而,初学者在上手时,往往会被编译器抛出的长串 [Dagger/MissingBinding] 错误搞得晕头转向。本文将复盘一个真实的 Hilt 报错案例,深入剖析 Dagger/Hilt 的绑定原理,并带你一步步搭建一个规范的 DI 模块。


一、 事故现场:那串让人头秃的报错

在构建我们的 Clean MVI Compose 项目时,点击运行,Android Studio 并没有给出丝滑的安装进度条,而是无情地抛出了一段日志:

错误: [Dagger/MissingBinding] com.example.cleanmvicompose.data.user.remote.UserApiService cannot be provided without an @Provides-annotated method.

text 复制代码
UserApiService is injected at
    [...].UserRepositoryImpl(apiService)
UserRepositoryImpl is injected at
    [...].RepositoryModule.provideUserRepository(impl)
[...]

翻译成大白话: Hilt 编译器在帮我们组装"零件"时,发现它知道 UserRepositoryImpl 需要一个 UserApiService,但它翻遍了整个"说明书",也没找到哪里能买到或者生产这个 UserApiService


二、 根源剖析:消失的 @Provides

我们检查了当时的 NetworkModule.kt 代码:

kotlin 复制代码
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    // 这里看起来没问题啊?
    fun providerApiService(): UserApiService {
        return UserApiService()
    }
}

问题点: Dagger/Hilt 是一个**编译时(Compile-time)**框架。它不靠反射,而是靠扫描注解来生成代码。 在上面的代码中,虽然我们写了创建实例的方法,但没有给它贴上 @Provides 标签。对于 Hilt 来说,这个方法只是一个普通的、透明的方法,它看不见,自然也就无法将其加入依赖池。


三、 解决方案:补齐依赖链条

修复方法非常简单,但背后有两个核心注解需要理解:

kotlin 复制代码
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides    // 1. 核心修复:告诉 Hilt 这是一个"供应站"
    @Singleton   // 2. 作用域:告诉 Hilt 这个东西全局只需要一个
    fun provideUserApiService(): UserApiService {
        return UserApiService()
    }
}

为什么这里要加 @Singleton

由于我们的 NetworkModule 被安装在了 SingletonComponent 中,这就意味着这个模块的生命周期与整个 App 同步。如果不加 @Singleton,每次有地方需要 UserApiService 时,Hilt 都会重新调用一次构造函数。对于网络请求服务来说,单例化是节省内存和保持连接池复用的最佳实践。


四、 深度进阶:Hilt 模块的设计规范

在实战中,我们通常将 DI 模块按照功能进行分类,而不是全部堆在一起:

  1. NetworkModule:负责网络请求相关的依赖(Retrofit, OkHttp, Service)。
  2. DatabaseModule:负责本地存储相关的依赖(Room, DataStore)。
  3. RepositoryModule:负责将接口抽象与具体实现绑定。

接口与实现的绑定:@Binds vs @Provides

如果你的依赖项是一个接口,且其实现类可以通过 @Inject 构造函数注入,建议使用 @Binds(性能更高,代码更简洁):

kotlin 复制代码
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

    @Binds
    @Singleton
    abstract fun bindUserRepository(
        impl: UserRepositoryImpl
    ): UserRepository
}

五、 总结与反思

通过这次报错,我们应该建立起一套 Hilt 排查思维模型:

  1. 查来源 :提供者(Module)里写 @Provides 了吗?
  2. 查去处 :接收者(Constructor)写 @Inject 了吗?
  3. 查范围@InstallIn 的组件范围覆盖了使用处吗?

依赖注入不是为了增加代码量,而是为了实现"解耦"。 UI 层不再关心 UserApiService 是如何创建的,它只需要伸手去"要"。这种"伸手即得"的优雅,正是我们构建健壮架构的基石。


源码地址

下篇预告: 解决了依赖注入的问题,代码依然会乱作一团?下一篇我们将聊聊 Clean Architecture:如何通过分包策略,为你的项目穿上"防弹衣"。

相关推荐
咋吃都不胖lyh1 小时前
短期记忆和长期记忆都存 MySQL
android·java·开发语言
自进化Agent智能体1 小时前
Hermes Trajectory日志工程:让每一次执行都成为进化数据
架构
starsky762382 小时前
基于 Spring AI 构建具备记忆与情绪的多角色 Agent 系统
人工智能·spring·架构
2601_957879332 小时前
基于LBS位置服务与跨域OpenAPI的同城矩阵系统:边缘裂变与数据网关架构实践
线性代数·矩阵·架构
Doris_20233 小时前
eslint
前端·架构·前端框架
Legend NO243 小时前
非结构化数据治理全解:从合规痛点、中台架构到 AI 智能化分类落地
大数据·人工智能·架构
无忧智库3 小时前
破局“伪智慧”陷阱:数智园区全生命周期集成平台的架构重构与价值跃迁(PPT)
重构·架构
杊页3 小时前
系列三:组件化与模块化进阶 | 第8篇 组件化与模块化核心实战区别:大型项目架构的必由之路
android·android jetpack