共计 3590 个字符,预计需要花费 9 分钟才能阅读完成。
Dagger 在 Android 开发中相当风行,它是一个提供齐全动态和在编译时生成代码的依赖注入框架,它解决了很多基于反射而实现的计划中所遇到的开发和性能问题。
为了让您更好地理解 Dagger 的工作原理,咱们于 2019 年公布了一个 新的教程。本文将重点介绍如何 在 Kotlin 中应用 Dagger,包含优化构建工夫的 最佳实际 以及一些可能会遇到的问题。
Dagger 是通过 Java 的注解模型实现的,而 Kotlin 中注解的编写形式同 Java 的并不是一一对应的,这篇文章会重点介绍它们之间的不同之处,并且会介绍怎么轻松地将 Dagger 同 Kotlin 联合起来应用。
本文的写作灵感来自 Dagger issue 中的一些倡议,这些倡议间接代表了在 Kotlin 中应用 Dagger 的最佳实际和一些痛点。在此要感激所有的 issue 贡献者。
进步构建效率
为了缩短构建工夫,Dagger 在 v2.18 版本中新增了 对 gradle 增量注解解决 (gradle’s incremental annotation processing) 的反对。在 Dagger v2.24 版本中这个性能是默认启用的。如果您应用的是较低版本,您须要增加以下几行代码来激活该性能。
另外,您能够配置 Dagger 让它不要格式化生成的代码。这一选项是在 Dagger v2.18 版本中增加的,并且是 v2.23 版本中的默认行为 (不再生成格式化代码)。如果您应用的是较低的版本,同样能够增加上面的代码来禁用格式化代码以缩短构建工夫。
在 build.gradle 中增加以下编译参数来进步 Dagger 在构建时的性能:
allprojects {
afterEvaluate {extensions.findByName('kapt')?.arguments {arg("dagger.formatGeneratedSource", "disabled")
arg("dagger.gradle.incremental", "enabled")
}
}
}
另外,如果您应用的是 Kotlin DSL 脚本文件,那么您须要在 build.gradle.kts 文件中蕴含以下内容:
kapt {
arguments {arg("dagger.formatGeneratedSource", "disabled")
arg("dagger.gradle.incremental", "enabled")
}
}
应用 Qualifier 作为 field 的属性
在 Kotlin 的某个 property 上增加注解时,不分明最终 Java 是否可能在该 property 的 field 或者 method 中获取到该注解。在注解之前增加 field: 前缀可能确保 qualifier 会作用到正确的中央 (查看 官网文档 获取更多详情)。
✅ 将 qualifier 作用于一个已注入的 field 的正确办法如下:
@Inject @field:MinimumBalance lateinit var minimumBalance: BigDecimal
❌ 上面的做法是不对的:
@Inject @MinimumBalance lateinit var minimumBalance: BigDecimal
// @MinimumBalance 被忽略了
遗记增加 field: 如果在 Dagger 中存在一个不匹配该类型的实例,则可能会导致注入到谬误的对象中。
在 Dagger v2.25 版本中曾经修复该问题了,如果您应用的是该版本,之前这样的写法会呈现问题,当初不会了。
@Inject @MinimumBalance lateinit var minimumBalance: BigDecimal
// 已修复:@MinimumBalance 不再被疏忽
应用动态的 @Provides 办法来进步性能
如果应用的是动态的 @Provides 办法,那么 Dagger 生成的代码将具备更好的性能。要达成这一成果,应用 Kotlin 中的 object 而不是 class,并在办法前增加 @JvmStatic 注解。这是您应该尽可能遵循的 最佳实际 。
@Module
object NetworkModule {
@JvmStatic
@Provides
fun provideOkHttpClient(): OkHttpClient {return OkHttpClient.Builder().build()}
}
如果须要应用形象办法,则须要将 @JvmStatic 增加到 companion object 中,并减少 @Module 注解。
@Module
abstract class NetworkModule {@Binds abstract fun provideService(retrofitService: RetrofitService): Service
@Module
companion object {
@JvmStatic
@Provides
fun provideOkHttpClient(): OkHttpClient {return return OkHttpClient.Builder().build()}
}
}
或者,您能够将 object 模块代码抽取进去,并将其蕴含在形象模块中:
@Module(includes = [OkHttpClientModule::java])
abstract class NetworkModule {@Binds abstract fun provideService(retrofitService: RetrofitService): Service
}
@Module
object OkHttpClientModule {
@JvmStatic
@Provides
fun provideOkHttpClient(): OkHttpClient {return OkHttpClient.Builder().build()}
}
在 Dagger v2.25 版本中,您不再须要应用 @JvmStatic 来标记 @Provides 函数了,Dagger 会正确地辨认它。
泛型注入
Kotlin 应用通配符编译泛型使 Kotlin API 和 Java 能一起应用。当某个参数或者字段的类型为 泛型 时,会在 Java 代码中主动生成。比方,Kotlin 的代码 List<Foo> 参数就会在 Java 中显示为 List<? super Foo>。
但这种个性会导致在 Dagger 中呈现问题,因为它冀望类型是齐全 (也称 invariant) 匹配的。应用 @JvmSuppressWildcards 将确保 Dagger 会看到没有通配符的类型。
当您应用 Dagger 的多重绑定个性时,这是一个经常会遇到的问题,比方:
class MyVMFactory @Inject constructor(
private val vmMap: Map<String, @JvmSuppressWildcards
Provider<ViewModel>>
) {...}
在 Dagger v2.25 版本中,您将不再须要应用 @JvmSuppressWildcards 了,Dagger 会正确地辨认它。
内联办法体
Dagger 通过查看返回值类型来确定由 @Provides 办法配置的类型。在 Kotlin 函数中的返回类型是可选的,甚至 IDE 有时也会建议您重构代码应用内联办法体来暗藏返回值类型的申明。
如果推断的类型与您所冀望的类型不统一,就会引起 bug 呈现。咱们来看一些例子:
如果要在 Dagger 中增加特定的类型,应用内联将是最好的抉择。咱们来看看在 Kotlin 中实现同样成果的另外一种办法:
@Provides
fun provideNetworkPrinter() = NetworkPrinter()
@Provides
fun provideNetworkPrinter(): NetworkPrinter = NetworkPrinter()
@Provides
fun provideNetworkPrinter(): NetworkPrinter {return NetworkPrinter()
}
如果您须要提供接口的实现,则必须显示指定返回类型。不这样做的话会出问题:
@Provides
// 配置 Printer
fun providePrinter(): Printer = NetworkPrinter()
@Provides
// 配置 NetworkPrinter,不是一个一般的 Printer
fun providePrinter() = NetworkPrinter()
Dagger 基本上是同 Kotlin 兼容的,然而您依然要留神,来确保代码不会出问题: 应用 @field: 来限定字段属性,内联办法体,当对汇合进行注入时应用 @JvmSuppressWildcards 注解。
本次 Dagger 带来的优化不会带来额定的损耗,遵循这些最佳实际,诸如启用增量正文解决、禁用格式化设置以及应用动态 @Provides 办法等,能够缩短我的项目的构建工夫。