乐趣区

Android-Hilt实战初体验-Dagger替换成Hilt

在组件化 AwesomeGithub 我的项目中应用了 Dagger 来缩小手动依赖注入代码。尽管它能自动化帮咱们治理依赖项,然而写过之后的应该都会领会到它还是有点繁琐的。我的项目中到处充斥着 Component,这让我想起了传统MVP 模式的接口定义。

简略来说就是 吃力 ,有许多大量的相似定义。可能google 也意识到这一点了,所以前不久公布出了Hilt

Hilt

为了避免没听说过的小伙伴们一头雾水,首先咱们来理解下 Hilt 是什么?

HiltAndroid 的依赖注入库,可缩小在我的项目中执行手动依赖项注入的样板代码。

Hilt通过为我的项目中的每个 Android 类提供容器并主动治理其生命周期,提供了一种在利用中应用 DI(依赖项注入)的规范办法。HiltDagger 的根底上构建而成,因此可能具备 Dagger 的编译时正确性、运行时性能、可伸缩性。

那么有的小伙伴可能会有疑难,既然曾经有了 Dagger 那为什么还要 Hilt 的呢?

HiltDagger 的次要指标都是统一的:

  1. 简化 Android 利用的 Dagger 相干基础架构。
  2. 创立一组规范的组件和作用域,以简化设置、进步可读性以及在利用之间共享代码。
  3. 提供一种简略的办法来为各种构建类型(如测试、调试或公布)配置不同的绑定。

然而 Android 中会实例化许多组件类,例如 Activity,因而在利用中应用Dagger 须要开发者编写大量的样板代码。Hilt能够缩小这些样板代码。

Hilt做的优化包含

  1. 无需编写大量的 Component 代码
  2. Scope也会与 Component 主动绑定
  3. 预约义绑定,例如 ApplicationActivity
  4. 预约义的限定符,例如 @ApplicationContext@ActivityContext

上面通过 AwesomeGithub 中 Dagger 来比照理解 Hilt 的具体应用。

依赖

应用之前将 Hilt 的依赖增加到我的项目中。

首先,将 hilt-android-gradle-plugin 插件增加到我的项目的根级 build.gradle文件中:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

而后,利用 Gradle 插件并在 app/build.gradle 文件中增加以下依赖项:

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
 
android {...}
 
dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

Application 类

应用 Dagger 时,须要一个 AppComponent 单例组件,我的项目中的其它 SubComponent 都将依赖于它,所以在 AwesomeGithub 中它大略是这个样子

@Singleton
@Component(
    modules = [
        SubComponentModule::class,
        NetworkModule::class,
        ViewModelBuilderModule::class
    ]
)
interface AppComponent {
 
    @Component.Factory
    interface Factory {fun create(@BindsInstance applicationContext: Context): AppComponent
    }
 
    fun welcomeComponent(): WelcomeComponent.Factory
 
    fun mainComponent(): MainComponent.Factory
     
    ...
 
    fun loginComponent(): LoginComponent.Factory}
 
@Module(
    subcomponents = [
        WelcomeComponent::class,
        MainComponent::class,
        ...
        LoginComponent::class
    ]
)
object SubComponentModule

下面的我曾经省略了大半,是不是看起来很多,而且最重要的是很多反复的构造根本都是一样的。所以 Hilt 基于这一点进行了简化,将这些反复的编写转成构建的时候主动生成。

Hilt要做的很简略,增加几个正文

@HiltAndroidApp
class App : Application() { ...}

所有的 Hilt 利用都必须蕴含一个带 @HiltAndroidApp 正文的 Application。它将代替Dagger 中的AppComponent

Android 类

对于 Android 类,应用 Dagger 时须要定义 SubComponent 并将它依赖到 Application 类中。上面以 WelcomeActivity 为例。

@Subcomponent(modules = [WelcomeModule::class])
interface WelcomeComponent {
    @Subcomponent.Factory
    interface Factory {fun create(): WelcomeComponent
    }

    fun inject(activity: WelcomeActivity)
}

module 的局部先不说,前面会提及

上面看 Hilt 的实现

@AndroidEntryPoint
class MainActivity : BaseHiltActivity<ActivityMainBinding, MainVM>() { ...}

Hilt要做的是增加 @AndroidEntryPoint 正文即可。

诧异,联合下面的,两个注解就替换了 Dagger 的实现,当初是否领会到 Hilt 的简洁?对老手来说也能够升高很大的学习老本。

目前 Hilt 反对上面 Android

  1. Application (@HiltAndroidApp)
  2. Activity
  3. Fragment
  4. View
  5. Searvice
  6. BroadcastReceiver

有一点须要留神,如果应用 @AndroidEntryPoint 正文了某个类,那么依赖该类的其它类也须要增加。

典型的就是 Fragment,所以除了Fragment 还须要给依赖它的所有 Activity 进行正文。

@AndroidEntryPoint的作用,对照一下 Dagger 就晓得了。它会主动帮咱们生成对应 Android 类的 Componennt,并将其增加到Application 类中。

@Inject

@Inject的应用根本与 Dagger 统一,能够用来定义构造方法或者字段,申明该构造方法或者字段须要通过依赖获取。

class UserRepository @Inject constructor(private val service: GithubService) : BaseRepository() { ...}

@Module

Hilt模块也须要增加 @Module 正文,与 Dagger 不同的是它还必须应用 @InstallIn 为模块增加正文。目标是告知模块用在哪个 Android 类中。

@Binds

@Binds正文会告知 Hilt 在须要提供接口的实例时要应用哪种实现。
它的用法与 Dagger 没什么区别

@Module
@InstallIn(ActivityComponent::class)
abstract class WelcomeModule {
 
    @Binds
    @IntoMap
    @ViewModelKey(WelcomeVM::class)
    abstract fun bindViewModel(viewModel: WelcomeVM): ViewModel
}

不同的是须要增加 @InstallInActivityComponent::class 用来表明该模块作用范畴为Activity

其实下面这块对 ViewModel 的注入,应用 Hilt 时会主动帮咱们编写,这里只是为了展现与 Dagger 的不同之处。后续会提到 ViewModel 的注入。

@Providers

提供一个 FragmentManager 的实例,首先是 Dagger 的应用

@Module
class MainProviderModule(private val activity: FragmentActivity) {
 
    @Provides
    fun providersFragmentManager(): FragmentManager = activity.supportFragmentManager}

比照一下Hilt

@InstallIn(ActivityComponent::class)
@Module
object MainProviderModule {
 
    @Provides
    fun providerFragmentManager(@ActivityContext context: Context) = (context as FragmentActivity).supportFragmentManager
}

区别是在 Hilt@Providers必须为 static 类并且构造方法不能有参数。

@ActivityContextHilt 提供的预约限定符,它能提供来自与 ActivityContext,对应的还有@ApplicationContext

提供的组件

对于之前提到的 @InstallIn 会关联不同的 Android 类,除了 @ActivityComponent 还有以下几种

对应的生命周期如下

同时还提供了相应的作用域

所以 Hilt 的默认提供将大幅提高开发效率,缩小许多反复的定义

ViewModel

最初再来说下 ViewModel 的注入。如果你应用到了 Jetpack 置信少不了它的注入。

对于 Dagger 咱们须要自定义一个 ViewModelFactory,并且提供注入形式,例如在 AwesomeGithub 的componentbridget 模块中定义了ViewModelFactory

@Module
abstract class ViewModelBuilderModule {
 
    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
  
}
 
class ViewModelFactory @Inject constructor(private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {override fun <T : ViewModel?> create(modelClass: Class<T>): T {var creator = creators[modelClass]
        if (creator == null) {for ((key, value) in creators) {if (modelClass.isAssignableFrom(key)) {creator = value}
            }
        }
 
        if (creator == null) {throw IllegalArgumentException("Unknown model class: $modelClass")
        }
 
        try {@Suppress("UNCHECKED_CAST")
            return creator.get() as T} catch (e: Exception) {throw RuntimeException()
        }
    }
 
}

通过 @Inject 来注入结构实例,但构造方法中须要提供 Map 类型的 creators。这个时候能够应用@IntoMap,为了匹配Map 的类型,须要定义一个 @MapKey 的正文

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

而后再到对应的组件下应用,例如匹配MainVM

@Module
abstract class MainModule {
 
    @Binds
    @IntoMap
    @ViewModelKey(MainVM::class)
    abstract fun bindViewModel(viewModel: MainVM): ViewModel
 
}

这样就提供了 Map<Class<MainVM>, MainVM> 的参数类型,这时咱们自定义的 ViewModelFactory 就可能被胜利注入。

例如 basic 模块外面的BaseDaggerActivity

abstract class BaseDaggerActivity<V : ViewDataBinding, M : BaseVM> : AppCompatActivity() {

    protected lateinit var viewDataBinding: V

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    protected val viewModel by lazy {ViewModelProvider(this, viewModelFactory)[getViewModelClass()] }
    ...
}

当然,别忘了 MainVM 也须要应用 @Inject 来申明注入

class MainVM @Inject constructor() : BaseVM() {...}

以上是 DaggerViewModel应用的注入形式。

尽管自定义的 ViewModelFactory 是专用的,然而对于不同的 ViewModel 还是要手动定义不同的 bindViewModel 办法。

而对于 Hilt 却能够省略这一步,甚至说下面的全副都不须要手动编写。咱们须要做的是只需在 ViewModel 的构造函数上增加@ViewModelInject

例如下面的 MainVM,应用Hilt 的成果如下

class MainVM @ViewModelInject constructor() : BaseVM() {...}

至于 Hilt 为什么会这么简略呢?咱们不要忘了它的实质,它是在 Dagger 之上建设的,实质是为了帮忙咱们缩小不必要的样板模板,不便开发者更好的应用依赖注入。

Hilt 中,下面的实现会主动帮咱们生成,所以才会应用起来这么简略。

如果你去比照看 AwesomeGithub 上的 feat_dagger 与 feat_hilt 两个分支中的代码,就会发现应用 Hilt 显著少了许多代码。对于简略的 Android 类来说就是减少几个正文而已。

目前惟一一个比拟不现实的是对于 @Providers 的应用,构造方法中不能有参数,如果在用 Dagger 应用时曾经有参数了,再转变成 Hilt 可能不会那么容易。

庆幸的是,DaggerHilt 能够共存。所以你能够选择性的应用。

然而整体而言 Hilt 真香,你只有尝试了绝不会悔恨~

AwesomeGithub

AwesomeGithub 是基于 Github 的客户端,纯练习我的项目,反对组件化开发,反对账户明码与认证登陆。应用 Kotlin 语言进行开发,我的项目架构是基于 JetPack&DataBinding 的 MVVM;我的项目中应用了 Arouter、Retrofit、Coroutine、Glide、Dagger 与 Hilt 等风行开源技术。

除了 Android 原生版本,还有基于 Flutter 的跨平台版本 flutter_github。

如果你喜爱我的文章模式,或者对我接下来的文章感兴趣,你能够关注我的微信公众号:【Android 补给站】

或者扫描下方二维码,与我建设无效的沟通,同时可能更不便的收到相干的技术推送。

退出移动版