依赖注入(十)—— Jetpack Hilt

1,727 阅读6分钟

Hilt是Google基于Dagger2开发的专用于Android的依赖注入框架,相比Dagger,它的优势如下:

  • 简化了Android应用中Dagger相基础架构。
  • 创建了一组标准的组件和作用域,以简化设置、提高可读性以及在应用之间共享代码。
  • 提供了一种简单的方法来为各种构建类型(如测试、调试或发布)配置不同的绑定。

引入依赖

  1. 在项目根级build.gradle中添加hilt-android-gradle-plugin插件。

    buildscript {
        dependencies {
            classpath 'com.google.dagger:hilt-android-gradle-plugin:x.y.z'
        }
    }
    
  2. 在模块级build.gradle中应用插件和导入依赖。

    plugins {
      id 'kotlin-kapt'
      id 'dagger.hilt.android.plugin'
    }
    android {
        ...
        // hilt需要开启Java8
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    dependencies {
        implementation "com.google.dagger:hilt-android:2.38.1"
        kapt "com.google.dagger:hilt-compiler:2.38.1"
    }
    

使用

Hilt是在Dagger2的基础上进行开发的,其本质还是对Dagger的使用进行简化。相比dagger- android,Hilt会帮助我们自动生成更多的模板代码,从而让使用过程更加简洁。但无论怎么精简,其Dagger的底层实现还是不变的,因此,了解Dagger将帮助我们更好的掌握Hilt,关于Dagger的介绍参见Dagger 系列文章。

定义应用类

使用Hilt需要为应用的Application类添加@HiltAndroidApp注解,使用了此注解后,Hilt将会使用gradle plugin为MyApplication生成一个名为Hilt_Application的父类(gradle插件会自动调整MyApplication的继承)以及应用级别的Component。

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

Hilt_Application中包含着对应用级别Component的初始化代码以及对Application的注入代码:

public abstract class Hilt_MyApplication 
  extends Application 
  implements GeneratedComponentManagerHolder 
{
  private final ApplicationComponentManager componentManager = 
    new ApplicationComponentManager(new ComponentSupplier() {
      @Override
      public Object get() {
        // 应用级别的Component的初始化
        return DaggerMyApplication_HiltComponents_SingletonC.builder()
            .applicationContextModule(new ApplicationContextModule(Hilt_MyApplication.this))
            .build();
      }
    });

  @Override
  public final ApplicationComponentManager componentManager() {
    return componentManager;
  }

  @Override
  public final Object generatedComponent() {
    return this.componentManager().generatedComponent();
  }

  @CallSuper
  @Override
  public void onCreate() {
    // 注入
    ((MyApplication_GeneratedInjector) generatedComponent()).injectMyApplication(UnsafeCasts.<MyApplication>unsafeCast(this));
    super.onCreate();
  }
}

关于DaggerMyApplication_HiltComponents_SingletonC的内容将在后文分析。

为Android其它组件注入依赖

Hilt支持为以下Android组件注入依赖:

  • Application
  • ViewModel
  • ComponentActivity(不支持基础的Activity)
  • androidx.Fragment(不支持基础的Fragment)
  • View
  • Service
  • BroadcastReceiver

要为Android组件注入依赖,需要使用@AndroidEntryPoint注解标记对应的组件类(对于Application类需要使用@HiltAndroidApp,对于ViewModel需要使用@HiltViewModel),获取依赖项则与Dagger一样,使用@Inject注解标记非私有字段即可。

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { 
    @Inject lateinit var analytics: AnalyticsAdapter
    ...
}

@HiltAndroidApp一样,Hilt也会为@AndroidEntryPoint@HiltViewModel标记的组件生成相应的Component和用于实现注入的父类。

绑定(依赖提供)

Hilt中的绑定与Dagger中的定义方式一致。
对于我们可以控制的依赖类型,直接在构造函数上使用@Inject注解即可。

class Student @Inject constructor() {
    ...
}

对于无法使用constructor-inject的依赖类型,也可以使用模块提供绑定信息。Hilt的模块和Dagger的模块是一样的,也是使用@Module标记的类。但需要注意的是,在Hilt中需要使用@InstallIn为模块指定要安装到的Component,关于Component的介绍见后文组件 Component

@Module
@InstallIn(SingletonComponent::class)
class CommonModule {
   ...
}

和Dagger一样,在模块中,我们可以使用@Provides以及@Binds系列的注解来绑定依赖项。

@Module
@InstallIn(SingletonComponent::class)
class CommonModule {
    @Provides
    fun provideSp(
        context: Application
    ): SharedPreferences {
        return context.getSharedPreferences("main", Context.MODE_PRIVATE)
    }

    @Binds
    abstract fun bindAnalyticsService(
        analyticsServiceImpl: AnalyticsServiceImpl
    ): AnalyticsService
    ...
}

限定符 Qualifier

Hilt中限定符的使用也与Dagger中一样。但是Hilt为Android提供了两个预定义的限定符:@ApplicationContext@ActivityContext,用来限定注入的context是应用的context还是Activity的context。

组件 Component

使用Hilt时,大部分时候我们都无需手动定义Component,Hilt会自动的帮我们生成与Android组件相应的Component。Hilt提供了以下组件的自动生成:

Android 组件Hilt Component
ApplicationSingletonComponent
-ActivityRetainedComponent
ViewModelViewModelComponent
ActivityActivityComponent
FragmentFragmentComponent
ViewViewComponent
使用@WithFragmentBindings标记的ViewViewWithFragmentComponent
ServiceServiceComponent

💡 Hilt不会为BroadcastReceiver生成组件,BroadcastReceiver直接通过SingletonComponent执行注入。

💡 Hilt也允许你自定义Component,但是大部分时候都不需要它,关于自定义Component可以参考官方文档

组件的层次结构

Hilt会将生成的Component组织成一定的层次结构,在这个层次结构中,位于下面的组件可以直接使用上层的子组件的依赖项,比如ViewComponent可以使用ActivityComponent中的依赖项;ActivityComponent可以使用SingletonComponent中的依赖项。

image.png

组件的生命周期

Hilt会按照对应Android组件类的生命周期自动创建和销毁生成Component实例。

Component创建时机销毁时机
SingletonComponentApplication#onCreate()Application#已销毁
ActivityRetainedComponentActivity#onCreate()Activity#onDestroy()
ViewModelComponentViewModel已创建ViewModel已销毁
ActivityComponentActivity#onCreate()Activity#onDestroy()
FragmentComponentFragment#attach()Fragment#onDestroy()
ViewComponentView#super()View已销毁
ViewWithFragmentComponentView#super()View已销毁
ServiceComponentService#onCreate()Service#onDestroy()

💡 相比ActivityComponentActivityRetainedComponent在应用配置更改(如横竖屏)引起Activity重建后依然存在。

❓ Q:ActivityRetainedComponentActivityComponent创建和销毁时机一致,为何ActivityRetainedComponent可以在配置更改后仍然存在,ActivityComponent则不行?
💡 A:ActivityRetainedComponentManager会创建ActivityRetainedComponentViewModel,然后把ActivityRetainedComponent的实例存储在其中,ActivityRetainedComponentViewModel实例则通过ViewModelStore保存到Activity中。换句话说,其实就是借用了ViewModel的重建保存能力。

Component的默认绑定

Hilt会为生成的Component提供一些默认绑定,比如对于SingletonComponent,Hilt会为其默认绑定Application实例,对于ActivityComponent,Hilt则会为其默认绑定对应Activity的实例。除此之外,因为Hilt中Component的层次关系,Component也可以访问到SubComponent提供的默认绑定。比如ActivityComponent也可以访问到SingletonComponent中的Application实例。

下表给出了各种Component可以访问到的默认绑定依赖项:

Component默认绑定依赖项
SingletonComponentApplication
ActivityRetainedComponentApplication
ViewModelComponentSavedStateHandle
ActivityComponentApplication、Activity
FragmentComponentApplication、Activity、Fragment
ViewComponentApplication、Activity、View
ViewWithFragmentComponentApplication、Activity、Fragment、View
ServiceComponentApplication、Service

作用域

Hilt中作用域的使用也与Dagger中一样。同样的Hilt也为生成的Component提供了预置的作用域注解,用于将依赖项实例的生命周期限制在对应Component的生命周期中:

Component作用域
SingletonComponent@Singleton
ActivityRetainedComponent@ActivityRetainedScoped
ViewModelComponent@ViewModelScoped
ActivityComponent@ActivityScoped
FragmentComponent@FragmentScoped
ViewComponent@ViewScoped
ViewWithFragmentComponent@ViewScoped
ServiceComponent@ServiceScoped

在Hilt不支持的类中实现注入

Dagger中,我们可以在Component中编写返回依赖项实例的接口来对外暴露依赖图中的依赖项。

@Component(modules = [CommonModule::class])
interface AppComponent {
    fun inject(application: Application)
    fun exposeSharedPreference(): SharedPreference
}

class MyClassCannotBeInject {
    fun test(component: AppComponent) {
        val sp = component.exposeSharedPreference()
        ...
    }
}

但在Hilt中因为Component是自动生成的,我们无法直接为其编写暴露依赖项的接口。为此Hilt提供了@EntryPoint注解,@EntryPoint可以定义一个入口点,其它Hilt不支持管理的类可以通过入口点访问依赖图中的依赖项。@EntryPoint需要配合@InstallIn一起使用,表明要附加到哪个Component上。

@EntryPoint
@InstallIn(SingletonComponent::class)
interface ExampleEntryPoint {
    fun sharedPreference(): SharedPreference
}

定义了入口点后,我们就可以使用EntryPointAccessors中适当的静态方法获取访问点。比如我们的访问点安装在SingletonComponent上,就需要使用fromApplication方法。

EntryPointAccessors.fromApplication(appContext,
    ExampleEntryPoint::class.java)
val sp = hiltEntryPoint.sharedPreference()

实现原理

Hilt的访问点在实现上实际上是在生成Component时,将@EntryPoint标记的接口作为其父接口继承其中暴露的接口方法。

@Component(
      modules = {
          ApplicationContextModule.class,
          CommonModule.class,
          ActivityRetainedCBuilderModule.class,
          ServiceCBuilderModule.class
      }
)
@Singleton
public abstract static class SingletonC implements 
    HiltWrapper_ActivityRetainedComponentManager_ActivityRetainedComponentBuilderEntryPoint,
    ServiceComponentManager.ServiceComponentBuilderEntryPoint,
    SingletonComponent,
    GeneratedComponent,
    MyApplication_GeneratedInjector,
    ExampleEntryPoint { }

public final class DaggerMyApplication_HiltComponents_SingletonC 
extends MyApplication_HiltComponents.SingletonC {
    ...
    @Override
    public SharedPreference sharedPreference() {
        return provideSharedPreferenceProvider.get();
    }
}

资料