24、扩展函数与高阶函数在依赖注入中的应用:Hilt的扩展函数、Kodein的DSL语法、自定义作用域
依赖注入(DI)在Android开发中已经成了标配。但说实话,很多人用Hilt或Kodein,只是照着文档抄,并不清楚背后的扩展函数和高阶函数到底起了什么作用。
今天我们就来扒一扒,这些DI框架是怎么利用Kotlin的语言特性,把依赖注入变得这么优雅的。
一、Hilt的扩展函数:@EntryPoint与Component的桥梁
Hilt是Google官方推荐的DI框架,基于Dagger。但Hilt比Dagger好用太多,原因之一就是它大量使用了扩展函数。
举个例子,当你想在非Android组件(比如一个普通的Java类)中获取依赖时,Hilt提供了EntryPoint机制。但每次都要写一大段样板代码,很烦。
Hilt的扩展函数帮你解决了这个问题。比如EntryPointAccessors系列函数:
// Hilt内部提供的扩展函数
inline fun <reified T> Context.getEntryPoint(): T {
return EntryPointAccessors.fromActivity(this, T::class.java)
}
// 使用的时候
val myEntryPoint = context.getEntryPoint<MyEntryPoint>()
val service = myEntryPoint.getMyService()
你看,一个扩展函数就把类型推断和强转都封装好了。我当初在项目里重构老代码时,就靠这个把几十个Activity里的DI代码砍掉了一半。
核心思路:Hilt的扩展函数本质上是把Dagger的Component访问逻辑,包装成对开发者更友好的API。你不需要关心Component是怎么创建的,只需要调用一个扩展函数就行。
二、Kodein的DSL语法:高阶函数构建依赖图
Kodein是我个人比较喜欢的轻量级DI框架。它的DSL语法,说白了就是高阶函数的极致运用。
你看Kodein的典型用法:
val kodein = Kodein {
bind<ApiService>() with singleton { RetrofitApiService() }
bind<UserRepository>() with scoped(SingletonScope) { UserRepositoryImpl(instance()) }
bind<LoginViewModel>() with factory { LoginViewModel(instance()) }
}
这个Kodein { ... }是什么?其实就是一个高阶函数。它的签名大概是这样的:
fun Kodein(block: KodeinBuilder.() -> Unit): Kodein
看到了吗?KodeinBuilder.() -> Unit就是一个带接收者的lambda。这个接收者KodeinBuilder里定义了bind、singleton、factory这些扩展函数。
我在项目中遇到过一个问题:团队里有人把singleton和factory搞混了,导致一个本该每次创建新实例的ViewModel变成了单例,结果数据一直不刷新。嗯,这里要注意:
- singleton:整个应用生命周期内只创建一次实例
- factory:每次请求都创建新实例
- scoped:在某个作用域内保持单例
避坑指南:我曾经在Kodein的DSL里嵌套了多层作用域,结果调试依赖关系时差点崩溃。建议你保持DSL结构扁平化,不要超过两层嵌套。
三、自定义作用域:高阶函数控制生命周期
作用域是DI框架里最容易被忽视但又最重要的概念。说白了,作用域就是控制对象什么时候创建、什么时候销毁。
在Kodein中,自定义作用域的实现其实就是一个高阶函数:
class UserSessionScope : Scope {
private val storage = mutableMapOf<ScopeKey, Any>()
override fun get(key: ScopeKey): Any? = storage[key]
override fun put(key: ScopeKey, item: Any) {
storage[key] = item
}
override fun remove(key: ScopeKey) {
storage.remove(key)
}
}
// 使用自定义作用域
val userScope = UserSessionScope()
val kodein = Kodein {
bind<UserData>() with scoped(userScope) { UserData() }
}
你想想看,这个scoped函数本身也是一个高阶函数。它接收一个Scope对象和一个创建实例的lambda,然后在内部管理这个实例的生命周期。
Hilt的作用域是通过注解实现的,比如@ActivityScoped、@FragmentScoped。但Hilt的作用域是编译期确定的,而Kodein的作用域是运行时动态的。各有优劣。
注意:自定义作用域一定要处理好内存泄漏问题。我曾经在项目里写了一个Activity作用域,但忘记在onDestroy时清理,结果导致整个Activity被泄漏。血的教训。
四、扩展函数与高阶函数的组合拳
其实,不管是Hilt还是Kodein,它们都在做同一件事:用扩展函数简化API调用,用高阶函数构建DSL。
我总结了一个小规律:
| 框架 | 扩展函数用途 | 高阶函数用途 |
|---|---|---|
| Hilt | 简化EntryPoint访问、Component获取 | @Module中的provider函数 |
| Kodein | bind、singleton、factory等DSL函数 | KodeinBuilder的lambda构建 |
| 自定义 | 封装作用域管理逻辑 | 控制实例创建与销毁 |
说白了,扩展函数让你写更少的代码,高阶函数让你写更清晰的代码。两者结合,就是DI框架的优雅之源。
五、知识体系总览
下面这张图帮你理清本章的核心逻辑:
这张图把三个核心模块的关系讲清楚了。Hilt和Kodein虽然实现方式不同,但底层都是靠扩展函数和高阶函数在支撑。自定义作用域则是你深入掌握DI的关键。
好了,这一章的内容就到这里。记住,扩展函数和高阶函数不是花架子,它们是真正能帮你写出干净代码的工具。下次你用Hilt或Kodein时,不妨想想背后那些优雅的扩展函数和高阶函数。
公众号:蓝海资料掘金营,微信deep3321