在组件化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要做的很简略,增加几个正文

@HiltAndroidAppclass 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的实现

@AndroidEntryPointclass 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的应用

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

比照一下Hilt

@InstallIn(ActivityComponent::class)@Moduleobject 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

@Moduleabstract 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)@MapKeyannotation class ViewModelKey(val value: KClass<out ViewModel>)

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

@Moduleabstract 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补给站】

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