Hilt 的使用以及遇到的问题

3,760 阅读6分钟

简介

Hilt 提供了一种将Dagger 依赖注入到Android 应用程序的标准方法。为Android 应用程序简化提供一组标准的、简化设置、可以读的组件;且为不同类型的构建(例如:测试、调试、发行)提供一种简单的方法。

可以理解为Google 为了统一依赖注入组件,但是Dagger 用起来比较复杂。就针对Android开发了一套适配库。

导入Hilt

apply plugin: 'com.android.application'
apply plugin: 'dagger.hilt.android.plugin'

android {
  // ...
}

dependencies {
  implementation 'com.google.dagger:hilt-android:2.34.1-beta'
  kapt 'com.google.dagger:hilt-compiler:2.34.1-beta'

  // For instrumentation tests
  androidTestImplementation  'com.google.dagger:hilt-android-testing:2.34.1-beta'
  kaptAndroidTest 'com.google.dagger:hilt-compiler:2.34.1-beta'

  // For local unit tests
  testImplementation 'com.google.dagger:hilt-android-testing:2.34.1-beta'
  kaptTest 'com.google.dagger:hilt-compiler:2.34.1-beta'
}

kapt {
 correctErrorTypes true
}

设置correctErrorTypes 为true ,将kapt配置为更正错误类型 。
这里遇到一个问题,当我的gradle 版本为 3.4.1 的时候

classpath 'com.android.tools.build:gradle:3.4.1'

apply plugin: 'dagger.hilt.android.plugin'插件一直安装失败,
提示找不到 "com/android/Version" 把gradle 改成 4.1.2 就没问题了
且注意 如果你是多module的项目,
apply plugin: 'dagger.hilt.android.plugin' 一定要plugin在主module下
(也就是跟 apply plugin: 'com.android.application' 一起),
若是只在子module下,主module的注入不会被实现。(问题1,后面会解释问题原因)

buildscript {
  repositories {
    // other repositories...
    mavenCentral()
  }
  dependencies {
    // other plugins...
    classpath 'com.google.dagger:hilt-android-gradle-plugin:2.34.1-beta'
  }
}

组件层次

Hilt把Dagger 手动创建Component 改成了预定义的Component,且自动集成到Android应用程序的各个生命周期中。通过注解的方式@InstallIn(xxxComponent.class)进行绑定。
下图显示了标准的Hilt组件层次结构。每个组件上方的注释是作用域注释,用于将绑定范围限制为该组件的生存期。组件下方的箭头指向任何子组件。通常,子组件中的绑定可以依赖于祖先组件中的任何绑定。 image.png

组件默认绑定

每个Hilt 组件都带有一组默认绑定,这些默认绑定可以作为依赖注入到你自定义绑定中

ComponentDefault Bindings
SingletonComponentApplication
ActivityRetainedComponentApplication
ViewModelComponentSavedStateHandle
ActivityComponentApplication, Acitvity
FragmentComponentApplication, Acitvity, Fragment
ViewComponentApplication, Acitvity, View
ViewWithFragmentComponentApplication, Acitvity, Fragment, View
ServiceComponentApplication, Service

简单使用

下面我为大家介绍以下一些注解的使用:

  • @HiltAndroidApp
  • @AndroidEntryPoint
  • @InstallIn
  • @Module
  • @Provides
  • @Binds
  • @HiltViewModel
  • @EntryPoint

想要了解更多的建议直接查看官方文档

@HiltAndroidApp

介绍

所有使用Hilt的App 必须包含一个被@HiltAndroidApp 注释的Appliction 类。
@HiltAndroidApp 会生成一个Hilt_MyApplication 的基类,并且继承与@HiltAndroidApp 注释的类的基类,然后将@HiltAndroidApp 注释的类的基类替换成Hilt_MyApplication。例如:
这是我们应用的 MyApplication

@HiltAndroidApp
class MyApplication extends BaseApplication{
}

使用@HiltAndroidApp Hilt 将会生成 Hilt_MyApplication

public abstract class Hilt_MyApplication extends BaseApplication implements GeneratedComponentManagerHolder {
  private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
    @Override
    public Object get() {
      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() {
    // This is a known unsafe cast, but is safe in the only correct use case:
    // MyApplication extends Hilt_MyApplication
    ((MyApplication_GeneratedInjector) generatedComponent()).injectMyApplication(UnsafeCasts.<MyApplication>unsafeCast(this));
    super.onCreate();
  }
}

并且使我们的 MyApplication 继承 Hilt_MyApplication,通过这种方式将modules 注入到我们的应用中。
可以看到具体的注入方法就是Hilt_MyApplication onCreate() 函数中的
((MyApplication_GeneratedInjector) generatedComponent()).injectMyApplication(UnsafeCasts.unsafeCast(this));
这句代码,generatedComponent() 返回的是 MyApplication_HiltComponents.SingletonC 对象,这个对象中就是我们所有module 的代码实现。有兴趣的同学可以自己去看一下,我这里就不贴代码了

使用

使用分为两种情况,添加和没有添加 Hilt Gradle插件

//没有添加插件
@HiltAndroidApp(BaseApplication.class)
class MyApplication extends Hilt_MyApplication{}

//添加插件
@HiltAndroidApp
class MyApplication extends BaseApplication{}

建议添加插件,使用起来会更简单。本文以下的示例都假定以使用插件。
这里需要注意的是如果要在MyApplication 中使用注入的对象,需要在 super.onCreate() 之后才能使用。 原因且看介绍中的 Hilt_MyApplication 源码。
这里解释一下问题1出现的原因,是因为我没有添加插件但@HiltAndroidApp 使用的时候用的却是添加了插件的用法。所以会出现module 注入不被实现的情况。

@AndroidEntryPoint

介绍

安卓成员注入,使用@AndroidEntryPoint 注解后就可以在该类中使用module注入的成员变量。但@AndroidEntryPoint 有类型限制,只能在以下的类上使用:

  1. Activity
  2. Fragment
  3. View
  4. Service
  5. BroadcastReceiver

使用

@AndroidEntryPoint
public final class MyActivity extends MyBaseActivity {
  // Bindings in SingletonComponent or ActivityComponent
  @Inject Bar bar;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // Injection happens in super.onCreate().
    super.onCreate();

    // Do something with bar ...
  }
}

同样要注意是是需要在 super.onCreate() 后使用注入的成员变量

@Module 和 @InstallIn

介绍

@Module 跟Dagger 里的是同一个,没什么好说的。
@InstallIn 通过使用@InstallIn(xxxComponent.class) 将module 安装到指定的组件中,在Hilt 中所以module 都必须添加这个注释,如果组件中就找不到这个module ,可能引起编译错误。
当然一个module 也可安装到多个组件上如:@InstallIn({ViewComponent.class, ViewWithFragmentComponent.class})

使用

@Module
@InstallIn(SingletonComponent.class)
public final class FooModule {
  // @InstallIn(SingletonComponent.class) module providers have access to
  // the Application binding.
  @Provides
  static Bar provideBar(Application app) {...}
}

每个组件都带有作用域注释,该注释可用于记住对组件生存期的绑定。例如,要将范围绑定到 SingletonComponent组件,请使用@Singleton批注:

@Module
@InstallIn(SingletonComponent.class)
public final class FooModule {
  // @Singleton providers are only called once per SingletonComponent instance.
  @Provides
  @Singleton
  static Bar provideBar() {...}
}

此外,每个组件都有默认情况下可用的绑定。例如,该SingletonComponent组件提供了Application 绑定:

@Module
@InstallIn(SingletonComponent.class)
public final class FooModule {
  // @InstallIn(SingletonComponent.class) module providers have access to
  // the Application binding.
  @Provides
  static Bar provideBar(Application app) {...}
}

@Provides 和 @Binds

介绍

@Provides 注释Module 中的方法以创建提供者方法绑定。该方法的返回类型绑定到其返回值。
@Binds 注释Module 中的抽象方法,一般方法的返回是一个接口,参数是实现接口的子类,在调用是会调用参数的子类中的方法实现。

使用

@Module
@InstallIn(SingletonComponent.class)
public final class FooModule {
  @Provides
  @Singleton
  static Bar provideBar() {...}
}

@Module
@InstallIn(SingletonComponent.class)
public abstract class BindModule {  
  @Binds
  @Singleton
  abstract Random bindRandom(SecureRandom secureRandom);
}

@HiltViewModel

介绍

使用 @HiltViewModel 注释ViewModel,ViewModel 在创建的时候就会走Hilt 创建的HiltViewModelFactory 进行创建。就可以使用在创建的时候使用Module 中提供的实例

使用

@HiltViewModel
public final class FooViewModel extends ViewModel {

  @Inject
  FooViewModel(SavedStateHandle handle, Foo foo) {
    // ...
  }
}

然后就可以在带有@AndroidEntryPoint 注解的activity、fragment 中使用了

@AndroidEntryPoint
public final class MyActivity extends AppCompatActivity {

  private FooViewModel fooViewModel;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    fooViewModel = new ViewModelProvider(this).get(FooViewModel.class);
  }
}

@EntryPoint

介绍

为不能使用注解的地方提供注入的对象。因为@AndroidEntryPoint 使用范围有限,在这范围之外要使用Hilt 注入的实例就可以使用@EntryPoint 来实现。
这个像是Hilt 把Component标准化后,使用者不能再里面添加方法,导致不能为使用不了注解的地方提供依赖而做出的解决方案。

使用

@EntryPoint
@InstallIn(SingletonComponent.class)
public interface FooBarInterface {
  Bar getBar();
}

如果使用上面的定义

Bar bar = EntryPoints.get(applicationContext, FooBarInterface.class).getBar();

小结

一开始使用的时候我看到是 安卓开发平台“Hilt 和 Jetpack 集成”这个文档,真坑,文档不及时更新也不把官方链接放一下。吐槽一下。然后几经周转找到了官方文档才能有幸为大家介绍一下Hilt。
使用起来确实要比Dagger 舒服的多,少了很多模板代码,范围和生命周期的绑定也更好理解。不多bb 学它