它是理性、技术导向且务实的。
文章的策略不是"为了用框架而用框架",而是从痛点出发(手动写 Factory 的繁琐、SavedStateHandle 的难搞、Compose 的未来),最后通过对比得出结论。
Android 架构进化之路:为何在 Retrofit + 协程重构中,我们需要引入 Hilt?
背景
目前我们的项目正在进行现代化的架构升级:
- 网络层 :从 RxJava 迁移到
Kotlin Coroutines+Retrofit。 - 数据流 :使用
Flow和密封类 (NetworkResult) 替代回调,实现更安全的单向数据流。 - UI层 :逐步引入
Jetpack Compose,同时兼容现有的 View/XML。
在重构交易模块(Trade Module)时,我发现了一个绕不开的架构痛点:ViewModel 的依赖注入问题 。为了解决这个问题,并为未来的 Compose 铺路,我建议在部分新模块中引入 Hilt。
本文将通过实际代码对比,解释为什么要这么做,以及它能为团队带来什么实际收益。
痛点:手动管理的"依赖地狱"
在新的架构中,我们遵循 MVVM 原则。一个标准的 TradeViewModel 通常需要两个依赖:
TradeApiService:用于网络请求(我们需要注入它)。SavedStateHandle:用于在进程被杀后恢复数据(系统提供,用于获取 Intent 参数)。
❌ 如果不使用 Hilt(现状)
由于 SavedStateHandle 是系统创建的,而 ApiService 是我们要传入的,手动 把这两个东西组合进 ViewModel 的构造函数非常痛苦。我们必须不得不为每一个 ViewModel 手动写一个 Factory 类:
1. 繁琐的 ViewModelFactory
kotlin
// 每一个 ViewModel 都要写这样一个 Factory,全是样板代码
class TradeViewModelFactory(
private val apiService: TradeApiService,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
override fun <T : ViewModel> create(
key: String, modelClass: Class<T>, handle: SavedStateHandle
): T {
// 我们必须手动组装:系统给的 handle + 我们给的 apiService
if (modelClass.isAssignableFrom(TradeViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return TradeViewModel(handle, apiService) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
2. 在 Activity 中尴尬的调用
kotlin
// 我们必须手动获取 Retrofit 实例,手动创建 Repository,再手动 new Factory
val api = RetrofitClient.getInstance().create(TradeApiService::class.java)
val factory = TradeViewModelFactory(api, this, intent.extras)
// 终于拿到了 ViewModel
val viewModel = ViewModelProvider(this, factory)[TradeViewModel::class.java]
问题总结:
- 代码冗余:每增加一个页面,就得写一个 Factory,维护成本高。
- 容易出错 :如果 ViewModel 加了一个参数(比如
UserHelper),需要修改 Factory 和所有调用这个 Factory 的 Activity。 - 生命周期风险 :如果
RetrofitClient不是单例,或者我们需要传递一个Activity级别的对象,手动管理生命周期很容易导致内存泄漏。
解决方案:引入 Hilt 后的世界
Hilt 是 Google 官方推荐的依赖注入库,它是专门为 Android 场景优化的(基于 Dagger 但去除了复杂性)。
✅ 使用 Hilt 之后
1. ViewModel 极其清爽
删掉 Factory 类,直接在构造函数上加注解。Hilt 会自动处理 SavedStateHandle 和 ApiService 的混合注入。
kotlin
@HiltViewModel
class TradeViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle, // Hilt 自动处理系统参数
private val apiService: TradeApiService // Hilt 自动从容器中注入
) : ViewModel() {
// ... 业务逻辑
}
2. Activity 中零样板代码
kotlin
@AndroidEntryPoint
class TradeActivity : AppCompatActivity() {
// 就像魔法一样,直接获取,所有依赖自动注入完成
private val viewModel: TradeViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
}
}
收益总结:
- 消灭样板代码 :彻底删除了所有的
ViewModelFactory。 - 关注点分离:Activity 不需要知道 ViewModel 依赖了什么,只需要使用它。
- 自动生命周期管理 :Hilt 自动处理单例(
@Singleton)或跟随 Activity 销毁的对象,杜绝内存泄漏。
核心收益分析
1. 解决 SavedStateHandle 的注入难题
这是最直接的收益。在现代 Android 开发中,SavedStateHandle 是标准组件。手动混合注入它和其他业务 Service 非常反人类。Hilt 对此有原生支持,能够极大地提升开发体验。
2. 为 Jetpack Compose 铺路
团队规划未来会引入 Compose。在 Compose 中,UI 是一棵函数树。
- 没有 Hilt :我们需要把 ViewModel 或者 Repository 从最顶层的
Screen一层层传到最底层的Button(即 Prop Drilling),代码非常丑陋。 - 有了 Hilt :我们可以在任何层级的 Composable 函数中,通过
hiltViewModel()直接获取 ViewModel,这是 Compose 开发的最佳实践。
3. 渐进式迁移,不影响旧代码
引入 Hilt 不需要重构现有的 Java 代码或旧模块。
- Hilt 可以和手动注入共存。
- 我们可以在新的"交易模块"中试用 Hilt。
- 旧的 Activity 和 Java 逻辑保持原样,互不干扰。
常见顾虑解答
Q: 引入 Hilt 会不会让代码变得很复杂?
A: 不会,反而更简单了。以前的 Dagger2 确实复杂,但 Hilt 隐藏了 Component/Module 的组装逻辑。对于业务开发来说,90% 的场景只需要
@HiltViewModel、@Inject和@AndroidEntryPoint三个注解。
Q: 会影响编译速度吗?
A: Hilt 使用 KAPT/KSP 处理注解,会有轻微的编译时间增加(通常几秒),但换来的是运行时性能的提升(没有反射)和代码量的显著减少。对于我们的项目规模,这点损耗完全可以接受。
结论
引入 Hilt 不是为了追求新技术,而是为了解决 ViewModel 工厂代码冗余 和 依赖管理混乱 的实际问题。
特别是在结合 Retrofit + Coroutines + Sealed Classes 的新架构下,Hilt 补全了最后一块拼图,让我们能以更少的代码写出更健壮的逻辑。
建议方案:在当前的"交易模块"重构分支中试行 Hilt,验证其对开发效率的提升,若效果良好再逐步推广。