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

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'
}

Application


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

@Singleton
@Component(modules = [AndroidInjectionModule::class,ActivityModule::class,FragmentModule::class,ViewModelModule::class])
interface AppComponent : AndroidInjector<DaggerApplication> {@Component.Factoryinterface 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() {}

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@Providesfun provide(): String {return hashCode().toString()}
}
//ActivityModule.kt
@Module
@InstallIn(ActivityComponent::class)
class ActivityModule {@ActivityScope@Providesfun provide(): String {return hashCode().toString()}
}

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

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

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@ContributesAndroidInjectorinternal 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@Injectlateinit var appHash: String@ActivityScope@Injectlateinit var activityHash: Stringoverride 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@Injectlateinit var appHash: String@ActivityScope@Injectlateinit var activityHash: String@FragmentScope@Injectlateinit var fragmentHash: Stringoverride 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")}
}

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位置的不同以多实例存在

Summary


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

Sample repo:

https://github.com/vitaviva/DaggerHiltSample

更多参考

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

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

  1. Android神匕首—Dagger2依赖注入框架详解

    简介 Dagger-匕首,鼎鼎大名的Square公司旗下又一把利刃(没错!还有一把黄油刀,唤作ButterKnife) Dagger2 是一个Android依赖注入框架,由谷歌开发,最早的版本Dagg ...

  2. 深入浅出依赖注入框架Dagger2

    目录 目录 依赖注入 依赖注入实现的三种方式 1. 构造注入 2. 属性注入 3. 接口注入 Dagger2 Dagger2的引入 不带Module的Inject方式(Inject+Component ...

  3. android组件浮动在activity上_Jetpack Hilt 依赖注入框架上手指南

    code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群 作者:LvKang-insist 链接:https://juejin.im/post/5efdff9d6fb9a07e ...

  4. android dagger2 懒加载,Android Dagger依赖注入框架浅析

    今天接触了Dagger这套android的依赖注入框架(DI框架),感觉跟Spring 的IOC差不多吧.这个框架它的好处是它没有采用反射技术(Spring是用反射的),而是用预编译技术,因为基于反射 ...

  5. Android:dagger2让你爱不释手-基础依赖注入框架篇

    前言 dagger2的大名我想大家都已经很熟了,它是解决Android或java中依赖注入的一个类库(DI类库).当我看到一些开源的项目在使用dagger2时,我也有种匆匆欲动的感觉,因此就立马想一探 ...

  6. dagger2 注入_使用Dagger 2在GWT中进行依赖注入

    dagger2 注入 依赖注入是一种软件开发概念,其中为对象提供了创建所需的所有对象或值. GWT用户已经熟悉GIN,但已经不推荐使用此工具,因此不再支持它,因此使用GIN的应用程序当前确实需要告别. ...

  7. 使用Dagger 2在GWT中进行依赖注入

    依赖注入是一种软件开发概念,其中为对象提供了创建所需的所有对象或值. GWT用户已经熟悉GIN,但已不推荐使用此工具,因此不再支持,因此使用GIN的应用程序当前确实需要告别. Dagger是GWT的新 ...

  8. 简述依赖注入框架 Hilt 的实现原理

    目录 结论 1.Application 注解 @HiltAndroidApp 注解生成的文件 代码的执行流程 2.对象的创建流程 build 一下,看一下生成的类: 对象初始化流程 ActivityC ...

  9. Koin--适用于Kotlin的超好用依赖注入框架,Dagger替代者,Koin史上最详细解说,一篇就够了,妈妈再也不用担心我不会依赖注入了

    今年呆在家中实在无聊,外面太危险了,还是在家学习比较安全可持续. 过年期间,我又复习了几遍依赖注入框架Dagger. 诶,什么是依赖注入? 说白了就是降低跟类对象之间的耦合,当需要修改类对象的时候,能 ...

最新文章

  1. linux 库函数拦截,如何使用net_dev_add()API过滤和拦截Linux数据包?
  2. redhat6.4 添加yum本地源和安装virtualbox增强组件
  3. 面向对象的三大特性之继承
  4. wireshark抓包数据学习
  5. 文巾解题3. 无重复字符的最长子串
  6. java: command not found_/bin/bash: java: command not found 问题解决
  7. 《剑指offer》把二叉树打印成多行
  8. HTML meta refresh 刷新与跳转(重定向)页面
  9. InputStream 、 InputStreamReader和BufferedReader
  10. 线性表的链式存储-单链表
  11. systemtap原理及使用
  12. Linux Matlab服务器进一步改造成Application Server(应用程序服务器)
  13. html加拼音注释,满江红岳飞全文带拼音(注释+译文)
  14. String格式问题:将String格式请求方法时,String格式突然转化为对象的问题
  15. 手机系统软件测试员,手机软件测试员做啥的?行业分析
  16. IE下载文件时,中文文件名乱码问题
  17. 曾经是“杀手级”桌面语言,Java桌面开发为何走向衰落?
  18. 我是如何零基础开始能写爬虫的?
  19. 微信小程序 获取当前日期时间
  20. 你真的会用区块链赚钱吗?论区块链的商业思维

热门文章

  1. 电商平台-销售管理设计与架构
  2. PHP生成随机字符串
  3. webpack-多页面打包
  4. 快速排序,快速选择排序,选择排序的区别
  5. iOS抓包工具Charles的使用
  6. EXCEL中12个条件判断
  7. vue-cli(vue脚手架)
  8. C语言指针水平等级测试(面试常考)
  9. IT66352是 HDMI一分二的switch芯片,2 路HDMI 2.0输入
  10. 每日推荐:文字转语音朗读软件