2. Hilt编译错误概览:常见分类与日志解读技巧
说到Hilt编译错误,很多朋友第一反应就是「妈呀,又红一片」。说实话,当年我刚接手第一个Dagger+Hilt项目时,看到那几十行堆栈差点想把电脑砸了。但你仔细捋一捋就会发现——Hilt的错误信息其实特别「话痨」,它会把能说的全告诉你,只是你不一定看得懂。这章我们就聊明白:常见的错误分哪几类,以及怎么从那一堆日志里快速抓到关键点。
2.1 常见编译错误分类
我个人习惯把Hilt编译错误分成四大类。为什么是四类?因为在项目实践中,90%以上的报错都能归到这几类里。你想想看,Hilt本质上是在编译期帮你生成一批Dagger组件代码,所以错误几乎都发生在「绑定链路」上。
| 分类 | 典型表现 | 根因 |
|---|---|---|
| 依赖循环错误 | Circular dependency detected | A依赖B,B又依赖A(直接或间接) |
| 组件/入口未绑定 | … cannot be provided without an @Provides-annotated method | 某个类型没在模块里声明,也没用@Inject构造函数 |
| 模块配置错误 | @Module 缺少 @InstallIn、或作用域不匹配 | 模块没挂到组件上,或者用错了组件类型 |
| 作用域冲突 | @Singleton cannot be applied to … which is scoped with @ActivityScoped | 同一个类型被两个不同作用域同时绑定 |
2.2 依赖循环错误
这种错误最让人头疼。原因很直接:A要创建,得先要B;B要创建,又得先要A。编译器直接死锁。我在项目中遇到过两次,都是因为偷懒把两个模块互相注入了。解决方案其实也不复杂——引入@LazyInject或者Provider。
// 错误示例(循环依赖)
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
fun provideApi(logger: Logger): Api = ApiImpl(logger)
}
@Module
@InstallIn(SingletonComponent::class)
object LoggerModule {
@Provides
fun provideLogger(api: Api): Logger = LoggerImpl(api)
}
// 修正方案:使用 Lazy 或 Provider 打破循环
@Provides
fun provideLogger(apiProvider: Provider<Api>): Logger = LoggerImpl(apiProvider.get())
Circular dependency时,第一时间别去翻整个依赖图。先看报错里最后几行required by字段,它直接告诉你是哪两个类型在「打架」。
2.3 组件/入口未绑定错误
这个错误特别常见,尤其当你在Activity或Fragment里用@AndroidEntryPoint,却忘了给某个ViewModel加注解。报错信息会很长,但关键就一句话:… cannot be provided without an @Provides-annotated method or @Inject constructor。说白了,Hilt找不到你要的这个东西怎么造出来。
- 场景一:你写了
@HiltViewModel但ViewModel里依赖了一个没有提供者的仓库类。 - 场景二:你在Activity里注入了一个
String,但没给String写@Provides方法。 - 场景三:你用了
@Binds但忘了把实现类注册到模块里。
// 典型报错日志片段
error: [Dagger/MissingBinding] com.example.Logger cannot be provided without an @Provides-annotated method.
public abstract static class ApplicationC implements HiltWrapper_MyApplication {
^
// 重点看:缺少 Logger 的提供者
@InstallIn,编译错误可能会扯到另一个无关的类上。我建议你每次加新依赖后,都先跑一次./gradlew assembleDebug,不要等到最后才编译。
2.4 模块配置错误
这个分类比较杂,但核心就两类:一是模块没挂到组件上(缺@InstallIn),二是挂错了组件作用域。例如你把一个@Singleton的模块装到了ActivityComponent下,编译会直接报错说作用域不兼容。
我印象很深的是刚迁移Hilt时,把@InstallIn(ServiceComponent::class)写成了@InstallIn(ActivityComponent::class),结果Service里死活拿不到那个依赖。后来才发现是模块装错了位置。
2.5 作用域冲突错误
当你同时用@Singleton和@ActivityScoped给同一个类型提供绑定,编译器就会报:… is already scoped with @Singleton。这是因为Hilt不允许一个类型有多个不同作用域。
// 错误:两个模块都提供同一个接口,但作用域不同
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides @Singleton
fun provideUserService(): UserService = UserServiceImpl()
}
@Module
@InstallIn(ActivityComponent::class)
object ActivityModule {
@Provides @ActivityScoped
fun provideUserService(): UserService = UserServiceImpl() // 编译错误!
}
@Provides,为什么也会报作用域冲突?后来发现是因为父类用了@Singleton,子类又声明了@ActivityScoped继承过来的。解决办法:检查父类/接口的注解是否继承。
2.6 错误日志解读技巧
好,分类讲完了。但实际你拿到一坨几十行的错误日志时,该怎么看?我有个固定套路,分享给你。
- 先看第一行——
error:后面的摘要,往往直接告诉你错误类型(MissingBinding / Circular / ...)。 - 再看最后几行——Hilt会标注
required by,这是关键线索,指向哪个Activity或Fragment触发了这个依赖。 - 找
@Provides-annotated method或@Inject constructor——如果日志里出现了这两个短语,那肯定是缺少提供者。 - 跳转到第一个带文件路径的行——通常形如
.../MyModule.kt:42,点进去就能看到具体问题。 - 不要被「中间的长堆栈」吓到——那些大多是Dagger自动生成代码的内部调用,90%情况下你可以直接忽略。
嗯,这里还要补充一点:Hilt的错误消息里偶尔会出现类似[Dagger/MissingBinding]方括号前缀。这个前缀其实是错误的唯一标识,你在搜索解决方案时,直接拿这个前缀搜会比搜全文更准确。例如搜「Dagger/MissingBinding」能快速找到官方文档或StackOverflow的高赞答案。
另外,再给个小技巧:如果你用的是Android Studio,直接双击错误日志里带路径的行,IDE会自动跳转到对应的源文件。千万别再手动去项目里翻目录了——我刚入行时就这么傻干过,一翻就是半小时。