美文网首页Android开发经验谈Android技术知识Android开发
避坑:在 Kotlin 中使用 Dagger 会遇到的陷阱以及相

避坑:在 Kotlin 中使用 Dagger 会遇到的陷阱以及相

作者: 码农的书柜 | 来源:发表于2020-11-02 20:50 被阅读0次

    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 参数就会在 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 方法等,可以缩短项目的构建时间。

    关于作者

    image

    本人是一个拥有6年开发经验的帅气Android攻城狮,记得看完点赞,养成习惯,微信搜一搜「 程序猿养成中心 」关注这个喜欢写干货的程序员。

    另外耗时两年整理收集的Android一线大厂面试完整考点PDF出炉,资料【完整版】已更新在我的【Github】,有面试需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!

    地址:【https://github.com/733gh/xiongfan】

    相关文章

      网友评论

        本文标题:避坑:在 Kotlin 中使用 Dagger 会遇到的陷阱以及相

        本文链接:https://www.haomeiwen.com/subject/mpqvvktx.html