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.gradlebuildscript {    dependencies {       classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'    }}
// app/build.gradleapply 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.Factory  interface Factory {    fun create(@BindsInstance application: Application): AppComponent  }}

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

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

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

@HiltAndroidAppclass 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  @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

Activity & Fragment


上面简简单单就完成了DI的Provide侧实现,接下来看Inject侧 – 主要是针对Activity以及Fragment进行注入。

dagger-android通过@ContributesAndroidInjector帮我们生成SubComponent;通过继承DaggerAppCompatActivity可以在onCreate时对Activity进行自动注入。虽然节省了一些模板代码,但是@ContributesAndroidInjector的出现某种程度上又成了新的模板代码。

@Moduleabstract 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@AndroidEntryPointclass 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@AndroidEntryPointclass 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")    }}

ViewModel


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

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

@Moduleabstract class ViewModelModule {    @Binds    @IntoMap    @ViewModelKey(ActivityViewModel::class)    abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel    @Binds    @IntoMap    @ViewModelKey(FragmentViewModel::class)    abstract fun bindFragmentViewModel(viewModel: FragmentViewModel): ViewModel}
@Singletonclass 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.gradleallprojects {    repositories {        maven {            url "https://androidx.dev/snapshots/builds/6543454/artifacts/repository/"        }    }}
// app/build.gradledependencies {    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,这个目前为止的写法一样

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

Activity & Fragment

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

// MainActivity.kt@AndroidEntryPointclass 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@AndroidEntryPointclass 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的依赖注入及实现原理

更多相关文章

  1. android 7.1 找不到 ll (ls -l)命令
  2. Android(安卓)中代码去模拟人的操作
  3. Android圆形头像的绘制(二)之自定义视图
  4. android GridView学习笔记
  5. 【工利其器】必会工具之(四)Refactor篇——Android(安卓)Studio在
  6. 【转】eclipse 上调试android的自带应用方法
  7. Android绘图系列(一)——自定义View基础
  8. Unity3D游戏开发之Unity与Android交互调用研究
  9. Android之Intent传递数据的方式

随机推荐

  1. 小胖加入Android(安卓)Fans的 大军了 呵
  2. Android有用代码片断(五)
  3. Android-View的滑动
  4. 转:深入解读Linux与Android的相互关系
  5. Android(安卓)Camera的接口与架构介绍
  6. Android开发环境的搭建
  7. Android(安卓)SDK安装更新Failed to fetc
  8. Android项目发布 ---- Jcenter篇
  9. .Net 转战 Android(安卓)4.4 日常笔记(5)--
  10. 最牛逼android上的图表库MpChart(三) 条形