5

Dagger之十七、从 Dagger 迁移到 Hilt

 2 years ago
source link: http://blog.chengyunfeng.com/?p=1129
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从 Dagger 迁移到 Hilt 的难易程度取决于你应用的复杂度以及是如何使用 Dagger 部件的。本节课介绍迁移到 Hilt 的常规步骤,以及迁移过程中会遇到的一些常见问题。

从 Dagger 迁移到 Hilt 一般有下面四个步骤:

  1. 提前规划下大概要迁移的内容
  2. 迁移 Application
  3. 迁移 Activity、Fragment 安卓组件
  4. 迁移其他安卓组件

在迁移之前再来了解下 Hilt 中的 Entry Points 概念,因为在迁移的过程中要使用 Entry Points 来过度,所以理解了这个概念可以让你迁移起来更加顺畅。

Entry Points

在第十三节课中所介绍的 Activity、Fragment、View、Service、BroadcastRecevier 都是 Hilt 定义的标准 Entry Points,通过在这些类上面使用 @AndroidEntryPoint 注解即可。

Entry point 是 Dagger 所管理的依赖对象和外部使用这些对象的桥梁。在 Hilt 中,自动生成的部件会继承使用 @EntryPoint 注解所定义的 Entry point 接口。

除了上面常用的安卓组件之外,其他 Hilt 不支持的地方就需要使用自定义的 Entry point ,比如在第十三节课中对 ContentProvider 使用依赖注入就需要自定义一个 Entry point 接口。

所以对于一个 Entry point 主要有两部分工作要做,一、定义 Entry point;二、使用 Entry point

创建 EntryPoint

定义一个接口并使用 @EntryPoint 注解,同时使用 @InstallIn 注解告诉 Hilt 这个 Entry point 被安装到那个部件中,然后在接口里面定义需要向外界提供的依赖对象:

@EntryPoint
@InstallIn(ApplicationComponent.class)
public interface FooBarInterface {
  @Foo Bar getBar(); // 可以使用限定符

对于上面的 FooBarInterface 这个 Entry point 接口, Hilt 会自动生成一个 ApplicationComponent 部件的子部件,该子部件会实现 FooBarInterface 这个接口。

使用 EntryPoint

通过 EntryPoints 所提供的函数来获取 EntryPoint 对象。该函数需要的参数为部件实例或者使用了 @AndroidEntryPoint 的实例(可以从该实例中获取到 部件对象),需要注意使用和 @InstallIn 所安装的部件要匹配。

上面定义的 FooBarInterface EntryPoint 被安装到了 ApplicationComponent 中,所以在通过 EntryPoints 获取该对象的时候,需要使用 applicationContext 对象作为参数。

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

如果是从 Hilt 所支持的安卓组件中获取 EntryPoint 对象的话, EntryPointAccessors 类使用起来更方便和更安全。

提前做好迁移规划

在迁移到 Hilt 之前,应该先组织下需要分几步完成迁移,每步迁移哪些内容。推荐从 Application 或者 @Singleton 入手,然后逐步往下推进迁移 ActivityFragment,毕竟这些是安卓应用开发中通常会使用 Dagger 注入对象的地方。一般来说,这种一步一步推进的方法都是可行的,这样可以及时发现迁移中的问题。

比较部件层级关系

第一步要做的就是把项目中 Dagger 部件的层级关系和 Hilt 中的做对比。然后看看哪些 Dagger 部件可以映射到 Hilt 中定义好的部件,对于找不到对应关系的 Dagger 部件,可以先保留这些 Dagger 部件,然后使用 Hilt 里面的自定义部件把这些部件当做 Hilt 预定义部件的子部件来定义(对于 Dagger 部件依赖的情况和如何自定义 Hilt 子部件我们放到最后来讨论,下面的内容假设 Dagger 部件和 Hilt 部件都是一一对应的情况)。

如果你在项目中使用了 @ContributesAndroidInjector, 那么一般来说你的项目中的 Dagger 部件和 Hilt 部件是一致的。

注意 Hilt 注入对象的时机

在前面介绍 Hilt 的时候,对于不同的安卓组件 Hilt 在每个组件的生命周期回调函数中来执行注入操作,一般来说 Hilt 里面的注入时机和我们使用 Dagger 部件来注入的时机应该是一致的,如果有差别的话,这个地方需要额外注意。

迁移完后,代码应该有如下变化:
– 所有使用 @Component 或者 @Subcomponent 或者使用 @ContributesAndroidInjector 的代码都应该被删除了
– 所有的 @Module 类都应该添加了 @InstallIn 注解
– 所有的里面有变量需要注入的 Application/Activity/Fragment/View/Service/BroadcastReceiver 类都应该使用了 @AndroidEntryPoint 注解
– 所有实例化 Dagger 部件或者把 Dagger 部件暴露给外界的代码(比如 在 Application 中提供一个 getComponent 的函数)都应该被移除了
– 所有 Dagger 安卓扩展功能的类都应该被移除了(位于 dagger.android 包下面的类和注解)

迁移 Application

第一件事就是把 Application 和使用了 @Singleton 注解的部件给迁移到 Hilt 自动生成的 ApplicationComponent 部件。要实现这一步,需要先确保安装到当前部件上的模块都被安装到了Hilt 自动生成的 ApplicationComponent 中。

要迁移 Application,需要先把之前的 @Singleton 部件中的所有内容都迁移到 Hilt 生成的ApplicationComponent 中。

1. 处理好模块

首先把模块都安装到 ApplicationComponent 中,通过在每个模块上使用 @InstallIn(ApplicationComponent.class) 注解可以完成这一步,如果你的项目中有很多个模块,则可以创建一个新的模块,让这个新的模块包含所有其他模块,这样只需要在这个新的模块上使用 @InstallIn(ApplicationComponent.class) 即可(但是这只是权宜之计,只是为了迁移的方便,最终迁移完成后,我们需要在将来把这些聚合在一起的模块给拆分开)。

// 从这个单例的部件入手
@Component(modules = {
    FooModule.class,
    BarModule.class,
interface MySingletonComponent {
// 上面的 MySingletonComponent 部件可以删除,然后从新定义一个新的聚合模块,
// 把上面部件中的模块安装到 ApplicationComponent 中
@InstallIn(ApplicationComponent.class)
@Module(includes = {
    FooModule.class,
    BarModule.class,
interface AggregatorModule {}

Hilt 不处理没有使用 @InstallIn 的模块。 默认情况下,当 Hilt 遇到了没有使用 @InstallIn 的模块会抛出错误,通过上一节介绍的编译选项可以禁止 Hilt 报错。

2. 处理所继承的接口和实现的函数

使用 @EntryPoint 可以来处理当前部件所继承的接口。

部件上的接口通常都是用来添加注入函数或者对外提供依赖的对象或者子部件的。当迁移完成后,这些接口在 Hilt 中是不需要的,Hilt 要么可以自动生成这些接口要么就是可以通过其他 Hilt 工具来替代这些接口。但是在迁移的过程中,为了保证代码可以执行,需要保留当前的行为。 下面来演示下如何使用 @EntryPoint

在部件所实现的接口上使用 @EntryPoint@InstallIn(ApplicationComponent.class),如果有多个接口,可以像上面的模块一样创建一个接口来继承多个接口。在部件中定义的接口可以移到新的接口中:

// 该部件实现了两个接口,分别用来注入 MyApplication 和对外提供 Foo 对象
@Component
@Singleton
interface MySingletonComponent extends FooInjector, BarInjector {
    void inject(MyApplication myApplication);
    Foo getFoo();
// 上面的两个接口可以修改为如下方式
@InstallIn(ApplicationComponent.class)
@EntryPoint
interface AggregatorEntryPoint extends FooInjector, BarInjector {
  // 下面这个注入 MyApplication 的函数放这里只是为了演示,后面可以看到
  // 注入 MyApplication 的操作函数是可以删除的
  void inject(MyApplication myApplication);
  Foo getFoo();

注入函数

Hilt 自动注入 Application 类中的 @Inject 变量,所以对于在部件中定义的注入 Application 的函数都可以删除,对于注入其他安卓组件的注入函数,当在安卓组件上使用了 @AndroidEntryPoint 后也是可以删除的。

@Component
@Singleton
interface MySingletonComponent {
  // Hilt 会自动注入 Application,所以下面的函数不需要
  void inject(MyApplication myApplication);
  // 当在 FooActivity 上使用了 @AndroidEntryPoint 后,下面的函数也不需要了
  void inject(FooActivity fooActivity);

访问接口

在 Dagger 中通常会有一些函数对外暴露部件对象,这样其他需要执行注入操作的地方可以通过这种方式获取到部件对象来执行操作。在迁移的时候,为了先不修改使用这些访问接口的代码,可以通过 EntryPoints 类来获取这些接口的实例。后面随着迁移的进行,最终这些函数都是可以被移除的,在调用这些函数的地方直接使用 EntryPoints api 就可以了。

// 比如下面的 MyApplication 有个 component()  函数, Activity 等其他类
// 可以通过该函数获取到 MySingletonComponent 部件对象
public final class MyApplication extends Application {
  MySingletonComponent component() {
    return component;
// 像下面这样使用 EntryPoint 来重新定义这些接口
@InstallIn(ApplicationComponent.class)
@EntryPoint
interface AggregatorEntryPoint extends LegacyInterface, ... {
@HiltAndroidApp
public final class MyApplication extends Application {
  // component() 函数的范围值修改为 AggregatorEntryPoint 类型,这个
  // AggregatorEntryPoint 接口实现了旧的 MySingletonComponent 所实现
  // 的接口,所以调用 component() 的代码是不需要做修改的。
  AggregatorEntryPoint component() {
    // Use EntryPoints to get an instance of the AggregatorEntryPoint.
    return EntryPoints.get(this, AggregatorEntryPoint.class);

3. 范围(Scopes)

当把部件迁移到 Hilt 时,部件中的依赖对象也需要使用 Hilt 预定义的范围注解,对于 ApplicationComponent 部件可以使用 @Singleton 注解,对于其他组件也需要选择第十三节课中所介绍的对应的注解。如果你没有使用 @Singleton 而是使用了其他自定义的注解,则可以使用 范围别名 把自定义注解定义为 @Singleton 的别名。最后迁移完成后,可以再来删除这些自定义的注解。

范围别名 Scope aliases

如果你的代码中使用了大量的自定义范围注解,在迁移的时候同时修改大量代码可能是有风险的,所以通过 范围别名 可以把这些自定义的范围注解等价为 Hilt 定义的注解,范围别名需要使用 @AliasOf 注解来定义,使用的方式如下:

@Scope
@AliasOf(dagger.hilt.android.scopes.ActivityScoped.class)
public @interface MyActivityScoped {}

上面通过使用 @AliasOf 把自定义的 @MyActivityScoped 范围注解定义为 Hilt 预定义的 @ActivityScoped 注解的别名,所以 Hilt 会把 @MyActivityScoped 当做 @ActivityScoped 来对待。

4. 处理部件参数

Hilt 中的部件是由 Hilt 自动完成创建的,所以用户无法主动的设置部件参数。在之前使用 Dagger 的时候,通常需要把 Application 实例作为参数设置到 ApplicationComponent 上,或者需要把 Activity 实例作为参数设置到 Activity 子部件上。对于这种情况,可以直接使用 Hilt 预定义的依赖对象(参考第十三节课)。

如果你的部件中除此之外还使用了 @BindsInstance 或者其他模块实例来设置外部的对象,那么可以通过下面的方式来处理这几种常见的特殊情况。

Fragment等实例作为参数

@Component.Builder
interface Builder {
  @BindsInstance
  Builder fragment(BaseFragment fragment);
// 上面的 Component.Builder 需要一个 Fragment 实例参数,可以通过下面的方式把该
// Fragment 参数放到 @Module 中作为一个   @Provides 函数来实现
@InstallIn(FragmentComponent.class)
@Module
final class BaseFragmentModule {
  @Provides
  static BaseFragment provideBaseFragment(Fragment fragment) {
    return (BaseFragment) fragment;

参数为默认依赖对象

@Component.Builder
interface Builder {
  @BindsInstance
  Builder intent(Intent intent);
// 上面Activity 子部件需要一个当前 Activity 的 intnet 对象,可以
// 同样通过 @Module 的 @Provides 函数来实现
@InstallIn(ActivityComponent.class)
@Module
final class IntentModule {
  @Provides
  static Intent provideIntent(Activity activity) {
    return activity.getIntent();

通过在安卓组件上实现接口的方式来提供对象

这种方式需要对代码做些调整:

@Component.Builder
interface Builder {
  @BindsInstance
  Builder foo(Foo foo);  // 每个 Activity 所设置的 Foo 对象是不一样的
// 定义一个获取 Foo 对象的接口,让每个 Activity 实现这个接口
interface HasFoo {
  Foo getFoo();
// 通过上面的接口来提供需要的 Foo 对象
@InstallIn(ActivityComponent.class)
@Module
final class FooModule {
  @Provides
  @Nullable
  static Foo provideFoo(Activity activity) {
    if (activity instanceof HasFoo) {
      return ((HasFoo) activity).getFoo();
    return null;

5. 清理前面临时定义的模块接口和 @EntryPoint 接口

最后可以把前面使用的临时模块接口和@EntryPoint 接口都给删除掉了。通过在每个接口上使用 @InstallIn 注解来删除临时定义的模块接口。

@InstallIn(ApplicationComponent.class)
@Module(includes = {FooModule.class, ...})
interface AggregatorModule {
// 从上面模块的 includes 中删除 FooModule引用,直接在 FooModule 上使用 @InstallIn
@InstallIn(ApplicationComponent.class)
@Module
interface FooModule {

在 Application 上使用 Hilt

现在可以在 Application 类上使用 @HiltAndroidApp 注解了。然后可以把所有使用之前定义的 Applicaiton 部件的代码都删除了,同时使用 @Component@Component.Builder 注解的代码也可以删除了。

Application 实现 HasAndroidInjector 接口的情况

如果你的 Application 实现了 HasAndroidInjector 接口或者是继承自 DaggerApplication 的,那么还需要保留这些代码,等所有的使用 dagger.android 的 Fragment 、Activity 都迁移后再来删除这些代码。在迁移过程中,可以让 dagger.android 的 Activity 使用 Hilt 的 ApplicationComponent 对象来替代之前定义的 App 部件。

例如下面是一个同时支持注入 Hilt Activity 和 dagger.android Activity 的情况:

@HiltAndroidApp
public final class MyApplication implements HasAndroidInjector {
  @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector;
  @Override
  public AndroidInjector<Object> androidInjector() {
    return dispatchingAndroidInjector;

dagger.android Activity 通过 androidInjector() 方法获取到 Hilt 生成的 ApplicationComponent 对象来实现注入操作。

如果 Application 继承自 DaggerApplication 类,则可以通过 @EntryPoint 来定义一个实现了 AndroidInjector<MyApplication> 的入口:

@HiltAndroidApp
public final class MyApplication extends DaggerApplication {
  @EntryPoint
  @InstallIn(ApplicationComponent.class)
  interface ApplicationInjector extends AndroidInjector<MyApplication> {
  @Override
  public AndroidInjector<MyApplication> applicationInjector() {
    return EntryPoints.get(this, ApplicationInjector.class);

当其他所有使用 dagger.android 的代码都迁移完毕后,只需要把 MyApplication 类继承 Application 并删除上面的代码即可。

检测项目是否能够编译

目前你的项目应该可以正常通过编译,这样就代表项目中已经成功的替换为 Hilt 的 ApplicationComponent 部件了。

迁移 Activity、Fragment 以及其他组件

当把应用的 application 迁移到 Hilt 以后,就可以开始迁移 Activity 和 Fragment 了。在迁移的过程中,使用 @AndroidEntryPoint 的 Activity 和 不使用 @AndroidEntryPoint 的 Activity 可以同时存在,对于 Fragment 也是这样的,这样可以方便一个一个 Activity 的来迁移。在迁移的过程中,对于 使用 Hilt 的组件只能依附于另外一个 Hilt 组件,比如如果一个 Fragment 使用了 Hilt,那么该 Fragment 的 Activity 也必须使用 Hilt。因此,推荐先把所有的 Activity 迁移为 Hilt ,然后再来迁移 Fragment。对于一些特殊情况,如果你必须要先迁移 Fragment,则可以使用 @OptionalInject 来帮助你突破这个限制,在最后将会介绍如何使用 @OptionalInject

迁移 Activity/Fragment 和迁移 Application 部件的方法类似,先把所有安装到当前 Activity/Fragment 部件的模块使用 @InstallIn 注解安装到合适的 Hilt 部件中。对于 Activity/Fragment 部件继承的其他接口可以按照上面迁移 Application 的方式给这些接口定义一个 @EntryPoint

如果项目中使用了 dagger.android 扩展里面的 @ContributesAndroidInjector,则可以按照上面 迁移部件 所介绍的方式来迁移 @ContributesAndroidInjector 上所使用的模块,这种情况下是无需使用 @EntryPoint 来迁移其他额外接口的。

注意单态部件的区别

如第十五节课所属, Hilt 中使用的是单态部件,对于应用中的所有 Activity Hilt只生成了一个 ActivityComponent 来实现注入。在之前使用 Dagger 或者使用 dagger.android 扩展功能的时候,项目中的每个 Activity 都有一个自己的子部件,现在要把这些子部件都迁移到同一个 ActivityComponent 中。根据项目的情况,可能会遇到下面两种常见的问题:

依赖对象冲突

当你在多个 Activity 子部件中同时定义了同一个类型的依赖对象的时候,就会发生依赖对象冲突的情况,需要修改代码只定义一个提供该依赖对象的方法或者使用限定符来区分。通常情况下,这种修改都是比较简单,需要在使用该对象的 Activity 上添加一些基本的逻辑判断就可以了。具体做法,可以参数前面的 4. 处理部件参数 一小节。

依赖特定Activity的对象

在使用 Hilt 之前,项目中可能有些依赖对象会需要使用特定的 Activity 实例,比如一个依赖对象 Foo 需要依赖特定的 FooActivity。这种情况下,需要重构代码让这些依赖对象尽量不要依赖特定的 Activity,而是修改为依赖通用的 Activity,比如 FragmentActivity。如果确实需要使用特定的 Activity 或者 Fragment,那么可以按照前面 4. 处理部件参数 一节中的方式来实现。

下面是一个把依赖特定 Activity 的情况修改为依赖通用 Activity 的示例:

// 下面的类仅仅使用 Activity 来获取里面的 FragmentManager 对象,所以
// 可以使用 FragmentActivity 类
final class Foo {
  private final FooActivity activity;
  @Inject Foo(FooActivity activity) {
    this.activity = activity;
  void doSomething() {
    activity.getSupportFragmentManager()...
// 迁移在 Hilt 后,修改为 依赖 FragmentActivity
final class Foo {
  private final FragmentActivity activity;
  @Inject Foo(FragmentActivity activity) {
    this.activity = activity;
  void doSomething() {
    activity.getSupportFragmentManager()...

Retained fragments

Hilt 并不支持 Retained fragments,原因在于 Hilt Fragment 中持有了一个 FragmentComponent 对象,该部件对象又引用了 ActivityComponent 对象。所以对于 Retained fragments 将会导致这些部件无法回收引起内存泄露。 如果项目中使用到了 Retained fragments 则可以把相关的状态代码迁移到 ViewModel 中。

在 Activity/Fragment 上使用 Hilt

现在可以在 Activity/Fragment 上添加 @AndroidEntryPoint 了。如果你的 Activity 中并没有要注入的变量,但是在该 Activity 中的 Fragment 需要注入,那么对于该 Activity 依然需要使用 @AndroidEntryPoint 注解。

然后就可以移除初始化 Dagger 部件的代码了,同时部件中的注入接口也可以删除了。

dagger.android

如果在项目中使用了 @ContributesAndroidInjector,现在这些代码也可以删除了。比如可以删除调用 AndroidInjection/AndroidSupportInjection 类的代码。如果项目中有类实现了 HasAndroidInjector 并且该类中的 Fragment、View 都已经迁移到 Hilt 了, 那么这个接口也可以删除了。

对于那些继承自 DaggerAppCompatActivity 、 DaggerFragment 的类,现在可以修改为直接继承普通的父类了,比如 AppCompatActivity 或者 Fragment。 如果在一个 Activity 中的 Fragment 或者 View 依然在使用 dagger.android 中的代码,那么该类还是需要实现 HasAndroidInjector 接口并注入 DispatchingAndroidInjector 对象,当把所有的 Fragment 或者 View 都迁移到 Hilt 后,再来删除这些代码。

dagger.android 迁移示例

下面的代码演示了如何迁移一个 Activity,在迁移的过程中,该 Activity 同时支持 Hilt 和 dagger.android Fragment。

刚开始使用 @ContributesAndroidInjector 的代码:

public final class MyActivity extends DaggerAppCompatActivity {
  @Inject Foo foo;
@Module
interface MyActivityModule {
    // 如果这里还定义了范围,则参考上面的 范围别名 一小节
    @ContributesAndroidInjector(modules = { FooModule.class, ... })
    MyActivity bindMyActivity()

先修改为可以同时支持 Hilt Fragment 和 dagger.android Fragment 的情况:

@AndroidEntryPoint
public final class MyActivity extends AppCompatActivity
    implements HasAndroidInjector {
  @Inject Foo foo;
  // 当所有的 Fragment 都迁移到 Hilt 后,这个就可以删除了
  @Inject DispatchAndroidInjector<Object> androidInjector;
  @Override
  public AndroidInjector<Object> androidInjector() {
    return androidInjector;
// 如果该 Activity 只有简单几个模块,则无需定义这个
// MyActivityAggregatorModule 接口,只需在每个模块上
// 使用 @InstallIn(ActivityComponent.class) 即可
@Module(includes = { FooModule.class, ...})
@InstallIn(ActivityComponent.class)
interface MyActivityAggregatorModule {}

最终全部迁移到 Hilt 后的代码:

@AndroidEntryPoint
public final class MyActivity extends AppCompatActivity {
  @Inject Foo foo;
// Activity 所依赖的模块都使用了 @InstallIn(ActivityComponent.class) 注解

检测项目是否能够编译

当迁移完 Activity 和 Fragment 后可以尝试编译下项目。如果进展顺利,现在可以开始迁移其他安卓组件了。

其他安卓组件

现在可以按照上面的方式来迁移 View、Service、 和 BroadcastReceiver 了。当把这些安卓组件迁移完毕后,基本上项目就完全迁移到 Hilt 了。 下面是几点迁移注意事项:
– 注意检查下是否还有遗留的使用 HasAndroidInjector 的地方,把这些代码删除掉
– 清理所定义的聚合多个模块的接口,清理所定义的 @EntryPoint 接口。一般来说在 Hilt 中无需使用 @Module(includes=) 这种形式了,对于这种形式可以修改为直接在每个模块使用 @InstallIn 注解。
– 如果使用了范围别名记得修改为 Hilt 中的预定义范围
– 修改任何给部件提供外部对象的 @Binds 函数定义

未尽事宜的应对之策

下面是对其他一些尚未讨论过的情况做些补充,比如 Qualifiers、部件依赖 等情况。

Qualifiers(限定符)

项目中所定义的限定符在 Hilt 中依然是合规的。关于限定符 Hilt 和 Dagger 并无区别。唯一的区别就是 Hilt 预定义了几个和安卓相关的限定符,比如 @ApplicationContext@ActivityContext 来代表不同类型的 Context。

可以通过模块中的 @Binds 函数来把 Hilt 中的 Context 映射为你自己定义的限定符对象:

@InstallIn(ApplicationComponent.class)
@Module
interface ApplicationContextModule {
  @Binds
  @my.app.ApplicationContext
  Context bindAppContext(
      @dagger.hilt.android.qualifiers.ApplicationContext Context context);

上面的 bindAppContext 函数参数为 @dagger.hilt.android.qualifiers.ApplicationContext 限定符限制的 Context, 所以当其他地方需要注入 @my.app.ApplicationContext 限定符的 Context 对象时候,Dagger 会使用 bindAppContext() 参数,也就是 Application 对象。

自定义部件

对于其他无法映射到 Hilt 部件的自定义部件,首先要考虑看看这些部件的功能是否可以迁移到 Hilt 对应的部件中。如果不能,那么可以继续使用这些自定义的 Dagger 部件,在 Hilt 中使用自定义部件的方式有如下两种,根据你的需要来选择合适的方式。

部件依赖(Component dependencies)

部件依赖可以通过 @EntryPoint 来实现,通过 @EntryPoint 把 Dagger 部件和 Hilt 部件连接到一起。

比如如果有个部件需要依赖 ApplicationComponent 部件,那么可以把该部件对外暴露的依赖对象放到 @EntryPoint 接口中:

// 自定义的 MyApplicationComponent 部件
@Component
interface MyApplicationComponent {
  // 下面这些依赖对象通过该部件暴露给了下面的 MyCustomComponent 部件
  Foo getFoo();
  Bar getBar();
  Baz getBaz();
// MyCustomComponent 自定义部件依赖上面的 MyApplicationComponent
@Component(dependencies = {MyApplicationComponent.class})
interface MyCustomComponent {
  @Component.Builder
  interface Builder {
    // 需要 MyApplicationComponent 部件对象来创建 MyCustomComponent 对象
    Builder appComponent(MyApplicationComponent appComponent);
    MyCustomComponent build();
// 下面是在 Hilt 中通过 @EntryPoint 把 MyCustomComponent 部件修改
// 为依赖 ApplicationComponent 的情况
@InstallIn(ApplicationComponent.class)
@EntryPoint
interface CustomComponentDependencies {
  // 定义 CustomComponentDependencies @EntryPoint,对外暴露需要的依赖对象
  Foo getFoo();
  Bar getBar();
  Baz getBaz();
// MyCustomComponent 现在依赖上面定义的 CustomComponentDependencies
@Component(dependencies = {CustomComponentDependencies.class})
interface MyCustomComponent {
  @Component.Builder
  interface Builder {
    Builder appComponentDeps(CustomComponentDependencies deps);
    MyCustomComponent build();

在使用 MyCustomComponent 的时候,可以通过 EntryPoints 来获取到 CustomComponentDependencies 实例:

DaggerMyCustomComponent.builder()
    .appComponentDeps(
        EntryPoints.get(
            applicationContext,
            CustomComponentDependencies.class))
    .build();

除了使用 @EntryPoint 来定义部件依赖之外,还可以通过定义 Hilt 部件的子部件的方式来实现。

子部件

子部件在 Hilt 中的使用方式和 Dagger 中的使用方式是一样的,对于任意的 Hilt 部件都可以设置子部件。具体的使用方式就是定义一个模块,把该模块安装到 Hilt 部件中,同时在该模块上指定 子部件。

比如定义了一个 FooSubcomponent 子部件,希望该部件作为 ApplicationComponent 的子部件存在,则可以通过下面的方式来实现:

@InstallIn(ApplicationComponent.class)
@Module(subcomponents = FooSubcomponent.class)
interface FooModule {}

@OptionalInject

Hilt Fragment 需要在 Hilt Activity 中使用,而 Hilt Activity 需要在 Hilt Application 中使用。当在迁移项目的时候,对于一个 Fragment 被多个 Activity 使用的情况下,如果要迁移这个 Fragment,可能需要同时先迁移多个 Activity,当代码比较复杂的时候,同时做这个事可能有点困难,这个时候 @OptionalInject 就派上用场了。

如果在使用 @AndroidEntryPoint 的地方同时使用了 @OptionalInject,那么只有当使用该组件的父组件也使用 Hilt 的时候才会去注入该组件里面需要注入的对象。比如在一个 Fragment 上使用 @OptionalInject,当这个 Fragment 在 Hilt Activity 中使用的时候,里面的注入对象会被赋值,而当该 Fragment 在非 Hilt Activity 中使用的时候,则不会启用注入操作。 当使用了 @OptionalInject注解后,在该组件上也会自动生成一个 wasInjectedByHilt() 函数,可以通过该函数的返回值来判断该组件是否被 Hilt 注入了。

由于 Hilt 的 gradle plugin 是通过修改字节码的方式来添加 wasInjectedByHilt() 函数的,所以在代码中无法直接调用该函数,可以通过 OptionalInjectCheck 助手类来调用该函数。

通过这种方式,当你发现该组件并不是通过 Hilt 来注入的时候,你可以通过其他方式来初始化这些需要注入的变量:

@OptionalInject
@AndroidEntryPoint
public final class MyFragment extends Fragment {
  @Inject Foo foo;
  @Override public void onAttach(Activity activity) {
    super.onAttach(activity);  // 如果 Activity 使用了 Hilt
    //则在 super.onAttach 中会执行注入操作
    if (!OptionalInjectCheck.wasInjectedByHilt(activity)) {
      // 如果 Hilt 没有执行注入操作,则通过之前的方式来获取 foo 实例

如果你的项目中使用了大量的自定义子部件,那么可能从 Dagger 迁移到 Hilt 会需要更多的精力来处理这些子部件,如果你的项目中之前使用的是 dagger.android 扩展功能,那么迁移起来会相对比较简单一些。当迁移到 Hilt 的时候,主要是想办法把之前自定义的子部件迁移到 Hilt 预定义的部件中,可以把之前的多个子部件对应到一个 Hilt 部件中去。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK