Dagger Hilt - Android官方推荐的依赖注入框架

简介: Dagger Hilt 帮助 Android 项目实现依赖注入

在这里插入图片描述

Dagger Hilt


Android端有不少DI框架可供选择 -- 例如用于控件注入的ButterKnife、用于Kotlin的Koin等,但唯有Dagger才称得上是谷歌官方认可的DI方案。

Dagger最早由Square开发,后被谷歌fork并升级为Dagger2,成为了Android官方推荐的DI最佳实践。Dagger较好地实现了JSR-330规范,虽然功能强大,但是无法很好地应对Android项目。谷歌随后推出dagger-android(及dagger-android-support),试图通过新的注解降低Android开发中Dagger的使用成本,但效果并不理想,因此 Android Dev Summit 2019上Dagger Hilt发布了,并在今年正式推出了alpha版

https://developer.android.com/training/dependency-injection/hilt-android

有些文章说Hilt是替代Dagger的,更准确的说法是用来替代dagger-android的。Hilt的名字非常秒,其目的就是帮助初学者在Android开发中更好地使用Dagger,代理了很多复杂的初始配置,大大降低开发成本,避免了来自“匕首”的反噬。

接下来通过一个简单的例子,学习一下Dagger Hilt的基本使用。

https://github.com/vitaviva/DaggerHiltSample
<br/>

Gradle


// build.gradle
buildscript {
    dependencies {
       classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}
// app/build.gradle
apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'kotlin-kapt'

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

<br/>

Application


使用dagger-andorid时,需要定义App级别的Component同时声明其依赖的Module

@Singleton
@Component(modules = [
  AndroidInjectionModule::class,
  ActivityModule::class,
  FragmentModule::class,
  ViewModelModule::class])
interface AppComponent : AndroidInjector<DaggerApplication> {

  @Component.Factory
  interface Factory {
    fun create(@BindsInstance application: Application): AppComponent
  }
}

然后要么继承DaggerApplication要么实现HasAndroidInjector接口,来创建Component。

class MyApplication : DaggerApplication() {
  override fun applicationInjector() = DaggerAppComponent.factory().create(this)
  
}

现在使用Hilt,只需@HiltAndroidApp一个注解,搞定上面这一切。

@HiltAndroidApp
class App : Application() {
}

<br/>

Component & Module


dagger-andorid需要向上面那样通过@Component定义AppComponent,那么Hilt是如何创建AppComponent,又是如何确定其依赖的Module的呢?。

Hilt已经为各种Android组件预置了Component

在这里插入图片描述

Hilt的Module也不需要在Component中声明,而是使用@InstallIn在定义Modle时反向声明Component

// ApplicationModule.kt

@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {

  @Singleton
  @Provides
  fun provide(): String {
      return hashCode().toString()
  }
}
//ActivityModule.kt
@Module
@InstallIn(ActivityComponent::class)
class ActivityModule {

    @ActivityScope
    @Provides
    fun provide(): String {
        return hashCode().toString()
    }
}

例子中Provide的类型都是String,所以自定义注解加以区分,@Qualifier的使用与以往没有区别

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class AppScope

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
internal annotation class ActivityScope

<br/>

Activity & Fragment


上面简简单单就完成了DI的Provide侧实现,接下来看Inject侧 -- 主要是针对Activity以及Fragment进行注入。
在这里插入图片描述
dagger-android通过@ContributesAndroidInjector帮我们生成SubComponent;通过继承DaggerAppCompatActivity可以在onCreate时对Activity进行自动注入。虽然节省了一些模板代码,但是@ContributesAndroidInjector的出现某种程度上又成了新的模板代码。

@Module
abstract class ActivityModule {

  @ActivityScope
  @ContributesAndroidInjector(modules = [FragmentModule::class])
  internal abstract fun contributeMainActivity(): MainActivity

  @ActivityScope
  @ContributesAndroidInjector
  internal abstract fun contributeSecondActivity(): SecondActivity

}

由于Hilt有了各预置Component,不再依赖SubComponent的创建(准确地说是无需开发者自定义SubComponent了,但是仍然会根据预置Component创建SubComponent,例如针对ActivityComponent生成Hilt_ActivityComponent,再次印证了Hilt只用来替代dagger-android,底层仍然依靠Dagger的运作机制),只需要@AndroidEntryPoint一个注解实现Activity等组件的注入。

@AndroidEntryPoint通过字节码插桩在编译期改变目标类对象的继承结构(例如MainActivity与AppComponentActivity之间插入Hilt_MainActivity作为父类),在父类的的各生命周期回调中,创建上述SubComponent并对目标实时注入

Component Description Created at Destroyed at
ApplicationComponent 为App提供依赖 Application#onCreate() Application#onDestroy()
ActivityComponent 为Activity提供依赖 Activity#onCreate() Activity#onDestroy()
ActivityRetainedComponent Retained顾名思义,其生命周期更长,不会因屏幕旋转等因素重建,实际上是借助ViewModel实现的 Activity#onCreate() Activity#onDestroy()
FragmentComponent 为Fragment提供依赖 Fragment#onAttach() Fragment#onDestroy()
ViewComponent 为View提供依赖,构造函数中进行注入 View#super() View destroyed
ViewWithFragmentComponent 为Fragment中的View提供依赖 View#super() View destroyed
ServiceComponent 为Service提供依赖 Service#onCreate() Service#onDestroy()

像SubComponent一样,预置Component的Scope同样具有继承关系
在这里插入图片描述

// MainActivity.kt

@AndroidEntryPoint
class MainActivity : AppCompatActivity(R.layout.activity_main) {

    @AppScope
    @Inject
    lateinit var appHash: String

    @ActivityScope
    @Inject
    lateinit var activityHash: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Log.v(TAG, "app : $appHash")
        Log.v(TAG, "activity : $activityHash")
    }
}
// MainFirstFragment.kt

@AndroidEntryPoint
class FirstFragment : Fragment(R.layout.fragment_first) {

    @AppScope
    @Inject
    lateinit var appHash: String

    @ActivityScope
    @Inject
    lateinit var activityHash: String

    @FragmentScope
    @Inject
    lateinit var fragmentHash: String

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        Log.d(TAG, "app : $appHash")
        Log.d(TAG, "activity : $activityHash")
        Log.d(TAG, "fragment : $fragmentHash")
    }
}

<br/>

ViewModel


随着MVVM架构的推广,ViewModel成为了Android项目的标配。但是ViewModel的注入一向是比较繁琐的。一方面ViewModel大都是ViewModelFactory提供的,我们无法自定义Provider,另一方面Factory大都是反射创建ViewModel,所以无法进行构造器注入,ViewModel最近新添加的SavedStateHandle就比较难处理。

目前为止,常用@IntoMap配合ViewModelFactory的定义实现ViewModel的注入。


@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(ActivityViewModel::class)
    abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(FragmentViewModel::class)
    abstract fun bindFragmentViewModel(viewModel: FragmentViewModel): ViewModel

}

@Singleton
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 {
        val found = creators.entries.find { modelClass.isAssignableFrom(it.key) }
        val creator = found?.value
                ?: throw IllegalArgumentException("unknown model class " + modelClass)
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

Hilt提供了androidx的扩展库,很好地解决了ViewModel的注入问题
关于ViewModel注入的更多细节,可以参考我的另一篇文章
ViewModel的依赖注入及实现原理

Gradle

首先,配置androidx仓库地址

// build.gradle
allprojects {
    repositories {
        maven {
            url "https://androidx.dev/snapshots/builds/6543454/artifacts/repository/"
        }
    }
}
// app/build.gradle
dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'

    implementation 'androidx.hilt:hilt-common:1.0.0-SNAPSHOT'
    implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-SNAPSHOT'
    kapt 'androidx.hilt:hilt-compiler:1.0.0-SNAPSHOT'
}

ViewModel

通过@ViewModelInject进行构造器注入,无需构造任何Factory。SavedStateHandle使用@Assisted注解

class ActivityViewModel @ViewModelInject constructor(
    private val repository: Repository,
    @Assisted private val savedState: SavedStateHandle
) : ViewModel() {

    val repository(): String = repository.toString()
}

@Singletonmock一个远程Repo,这个目前为止的写法一样

@Singleton
class Repository @Inject constructor() {
  fun getSomething(): Something
}

Activity & Fragment

Activity和Fragment像往常一样通过ktx的viewModelsactivityViewModels获取ViewModel即可

// MainActivity.kt

@AndroidEntryPoint
class MainActivity : AppCompatActivity(R.layout.activity_main) {

    private val viewModel by viewModels<ActivityViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        Log.v(TAG, "repository : $repository")
        Log.v(TAG, "activity vm : $viewModel")
        Log.v(TAG, "activity vm repo : ${viewModel.repository}")
    }
}
FirstFragment.kt
@AndroidEntryPoint
class FirstFragment : Fragment(R.layout.fragment_first) {

    private val activityViewModel by activityViewModels<ActivityViewModel>()
    private val fragmentViewModel by viewModels<FragmentViewModel>()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        Log.d(TAG, "activity vm: $activityViewModel")
        Log.d(TAG, "fragment vm: $fragmentViewModel")
        Log.d(TAG, "activity vm repo: ${activityViewModel.repository}")
        Log.d(TAG, "fragment vm repo: ${fragmentViewModel.repository}")
    }
}

Repository由于是@Singleton的,所以全局单例存在。ViewModel则根据by位置的不同以多实例存在

<br/>

Summary


通过例子可以感受到,Hilt相对于dagger-android减少了大量的模板代码,但也以为着灵活性上的降低,例如若使用预置ActivityComponent,则针对所有Activity都可以提供同样的注入,代码隔离上达不到自定义Component那样的精细化程度。但是相对于开发体验的提升,这点牺牲不算什么。Hilt作为官方推荐的DI库,未来的前景十分值得期待~

Sample repo:

https://github.com/vitaviva/DaggerHiltSample

更多参考

Dagger Hilt - ViewModel的依赖注入及实现原理

目录
相关文章
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
4月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台框架解析
在移动应用开发的广阔舞台上,安卓和iOS一直是两大主角。随着技术的进步,开发者们渴望能有一种方式,让他们的应用能同时在这两大平台上运行,而不必为每一个平台单独编写代码。这就是跨平台框架诞生的背景。本文将探讨几种流行的跨平台框架,包括它们的优势、局限性,以及如何根据项目需求选择合适的框架。我们将从技术的深度和广度两个维度,对这些框架进行比较分析,旨在为开发者提供一个清晰的指南,帮助他们在安卓和iOS的开发旅程中,做出明智的选择。
|
15天前
|
算法 JavaScript Android开发
|
25天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
93 1
|
2月前
|
开发工具 Android开发 git
Android实战之组件化中如何进行版本控制和依赖管理
本文介绍了 Git Submodules 的功能及其在组件化开发中的应用。Submodules 允许将一个 Git 仓库作为另一个仓库的子目录,有助于保持模块独立、代码重用和版本控制。虽然存在一些缺点,如增加复杂性和初始化时间,但通过最佳实践可以有效利用其优势。
39 3
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
352 3
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
77 8
|
2月前
|
API Android开发 iOS开发
掌握安卓与iOS应用开发中的依赖注入技术
本文探讨了在安卓和iOS应用开发中,如何有效利用依赖注入技术来提升代码的模块化、可测试性和可维护性。通过对比分析两种平台下依赖注入的实现方式与工具,本文旨在为开发者提供一套清晰、实用的依赖管理策略,助力打造高质量软件产品。
|
3月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
3月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
如何使用Amlogic T972安卓9.0系统上的misc框架来简化驱动程序开发,通过misc框架自动分配设备号并创建设备文件,从而减少代码量并避免设备号冲突。
45 0
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单