使用 Hilt 实现依赖项注入

使用 Hilt 实现依赖项注入简单使用


Android 官网

译文

Hilt 是 Android 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码

简解属于个人理解,仅供参考

添加依赖项

project的build.gradle

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

app的build.gradle

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
    //启用 Java 8
   compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

使用方法

Hilt Application 类

所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application

@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖 项容器

@HiltAndroidApp
public class ExampleApplication extends Application { ... }

生成的这一 Hilt 组件会附加到 Application 对象的生命周期,并为其提供依赖项。此外,它也是应用的父组件,这意味着,其他组件可以访问它提供的依赖项。

简解:HiltAndroidApp必须声明,Application 添加HiltAndroidApp注解会触发 Hilt 的代码生成

将依赖项注入 Android 类

Application 类中设置了 Hilt 且有了应用级组件后,Hilt 可以为带有 @AndroidEntryPoint 注解的其他 Android 类提供依赖项

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }

Hilt 目前支持以下 Android 类:

  • Application(通过使用 @HiltAndroidApp
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

限制条件:如果您使用 @AndroidEntryPoint 为某个 Android 类添加注解,则还必须为依赖于该类的 Android 类添加注解.

例如,如果您为某个 Fragment 添加注解,则还必须为使用该 Fragment 的所有 Activity 添加注解

注意在 Hilt 对 Android 类的支持方面还要注意以下几点:

@AndroidEntryPoint 会为项目中的每个 Android 类生成一个单独的 Hilt 组件。这些组件可以从它们各自的父类接收依赖项,如组件层次结构中所述

简解:就是下层组件可以使用上层组件的依赖项,例如,ViewComponent 可以使用 ActivityComponent 中定义的绑定

如需从组件获取依赖项,请使用 @Inject 注解执行字段注入:

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}
注意:由 Hilt 注入的字段不能为私有字段。尝试使用 Hilt 注入私有字段会导致编译错误。
定义Hilt绑定

为了执行注入,Hilt 需要知道如何从相应组件提供必要依赖项的实例。“绑定”包含将某个类型的实例作为依赖项提供所需的信息。

向 Hilt 提供绑定信息的一种方法是构造函数注入。在某个类的构造函数中使用 @Inject 注释,以告知 Hilt 如何提供该类的实例:

public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

在一个类的代码中,带有注释的构造函数的参数即是该类的依赖项。AnalyticsServiceAnalyticsAdapter 的一个依赖项。因此,Hilt 还必须知道如何提供 AnalyticsService 的实例

注意:在构建时,Hilt 会为 Android 类生成 Dagger 组件。然后,Dagger 会查您的代码,并执行以下步骤:

  • 构建并验证依赖关系图,确保没有未满足的依赖关系且没有依赖循环。

  • 生成它在运行时用来创建实际对象及其依赖项的类

    我感觉到官网文章对我深深的恶意,理解太费劲了。。。

    **本人简解:**构造函数注入只是向Hilt提供绑定信息的一种方法, 在一个类的构造函数上使用@Inject 注解只是告知Hilt如何提供该类所需的实例

Hilt 模块

本人简解: Hilt 模块主要解决不能通过构造函数注入的类型类,比如接口 ,外部库的类

有时,类型不能通过构造函数注入。发生这种情况可能有多种原因。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不属于您所有的类型,如来自外部库的类。在这些情况下,您可以使用 Hilt 模块向 Hilt 提供绑定信息。

Hilt 模块是一个带有 @Module 注解的类。与 Dagger 模块一样,它会告知 Hilt 如何提供某些类型的实例。与 Dagger 模块不同的是,您必须使用 @InstallIn 为 Hilt 模块添加注解,以告知 Hilt 每个模块将用在或安装在哪个 Android 类中

您在 Hilt 模块中提供的依赖项可以在生成的所有组件中使用,这个组件与 安装Hilt 模块的 Android 类关联。

简解: 其实声明一个带有@Module 注解的类,必也就是Hilt 模块,须使用@InstallIn注解来告知该模块使用范围

使用@Binds注入接口实例

AnalyticsService 为例。如果 AnalyticsService 是一个接口,则您无法通过构造函数注入它,而应向 Hilt 提供绑定信息,方法是在 Hilt 模块内创建一个带有 @Binds 注解的抽象函数。

@Binds 注解会告知 Hilt 在需要提供接口的实例时要使用哪种实现。

带有该注解的函数会向 Hilt 提供以下信息:

  • 函数返回类型会告知 Hilt 函数提供哪个接口的实例。
  • 函数参数会告知 Hilt 要提供哪种实现。
public interface AnalyticsService {
  void analyticsMethods();
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {
  ...
  @Inject
  AnalyticsServiceImpl(...) {
    ...
  }
}

@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

Hilt 模块 AnalyticsModule 带有 @InstallIn(ActivityComponent::class) 注解,因为您希望 Hilt 将该依赖项注入 ExampleActivity。此注释意味着,AnalyticsModule 中的所有依赖项都可以在应用的所有 Activity 中使用。

使用 @Provides 注入实例

如果某个类不归您所有(因为它来自外部库,如 RetrofitOkHttpClientRoom 数据库等类),或者必须使用构建器模式创建实例,也无法通过构造函数注入。

接着前面的例子来讲。如果 AnalyticsService 类不直接归您所有,您可以告知 Hilt 如何提供此类型的实例,方法是在 Hilt 模块内创建一个函数,并使用 @Provides 为该函数添加注解

如果 AnalyticsService 类不直接归您所有,您可以告知 Hilt 如何提供此类型的实例,方法是在 Hilt 模块内创建一个函数,并使用 @Provides 为该函数添加注解。

带有注解的函数会向 Hilt 提供以下信息:

  • 函数返回类型会告知 Hilt 函数提供哪个类型的实例。
  • 函数参数会告知 Hilt 相应类型的依赖项。
  • 函数主体会告知 Hilt 如何提供相应类型的实例。每当需要提供该类型的实例时,Hilt 都会执行函数主体。
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    // Potential dependencies of this type
  ) {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}
为同一类型提供多个绑定

参考官网

让 Hilt 以依赖项的形式提供同一类型的不同实现,必须向 Hilt 提供多个绑定。您可以使用限定符为同一类型定义多个绑定

限定符是一种注解,当为某个类型定义了多个绑定时,您可以使用它来标识该类型的特定绑定

简解:就是通过定义一种注解/限定符,来标识该类型的特定绑定,来提供同一类型的不同实现,就像空对空,空对地一样

例如

定义要用于为 @Binds@Provides 方法添加注解的限定符:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}

Hilt 需要知道如何提供与每个限定符对应的类型的实例,在这种情况下,您可以使用带有 @Provides 的 Hilt 模块。这两种方法具有相同的返回类型,但限定符将它们标记为两个不同的绑定:

@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideAuthInterceptorOkHttpClient(
    AuthInterceptor authInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(authInterceptor)
                   .build();
  }

  @OtherInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideOtherInterceptorOkHttpClient(
    OtherInterceptor otherInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(otherInterceptor)
                   .build();
  }
}

您可以通过使用相应的限定符为字段或参数添加注解来注入所需的特定类型:

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

  @Provides
  public static AnalyticsService provideAnalyticsService(
    @AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
      return new Retrofit.Builder()
                  .baseUrl("https://example.com")
                  .client(okHttpClient)
                  .build()
                  .create(AnalyticsService.class);
  }
}

// As a dependency of a constructor-injected class.
public class ExampleServiceImpl ... {

  private final OkHttpClient okHttpClient;

  @Inject
  ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient;
  }
}

// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @Inject
  OkHttpClient okHttpClient;
  ...
}

最佳做法是,如果您向某个类型添加限定符,应向提供该依赖项的所有可能的方式添加限定符

预定义限定符

Hilt 提供了一些预定义的限定符。例如,由于您可能需要来自应用或 Activity 的 Context 类,因此 Hilt 提供了 @ApplicationContext@ActivityContext 限定符。

假设本例中的 AnalyticsAdapter 类需要 Activity 的上下文。以下代码演示了如何向 AnalyticsAdapter 提供 Activity 上下文:

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service;
  }
}
为 Android 类生成的组件

对于可以从中执行字段注入的每个 Android 类,都有一个关联的 Hilt 组件,您可以在 @InstallIn 注释中引用该组件。每个 Hilt 组件负责将其绑定注入相应的 Android 类。

Hilt 提供了以下组件:

Hilt 组件注入器面向的对象
ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponent带有 @WithFragmentBindings 注释的 View
ServiceComponentService

注意:Hilt 不会为广播接收器生成组件,因为 Hilt 直接从 ApplicationComponent 注入广播接收器。

组件生命周期

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

生成的组件创建时机销毁时机
ApplicationComponentApplication#onCreate()Application#onDestroy()
ActivityRetainedComponentActivity#onCreate()Activity#onDestroy()
ActivityComponentActivity#onCreate()Activity#onDestroy()
FragmentComponentFragment#onAttach()Fragment#onDestroy()
ViewComponentView#super()视图销毁时
ViewWithFragmentComponentView#super()视图销毁时
ServiceComponentService#onCreate()Service#onDestroy()

注意ActivityRetainedComponent 在配置更改后仍然存在,因此它在第一次调用 Activity#onCreate() 时创建,在最后一次调用 Activity#onDestroy() 时销毁。

组件作用域

默认情况下,Hilt 中的所有绑定都未限定作用域。这意味着,每当应用请求绑定时,Hilt 都会创建所需类型的一个新实例。

不过,Hilt 也允许将绑定的作用域限定到特定组件上。Hilt 只为受绑定作用域限定到的组件的每个实例, 创建一次限定作用域的绑定,对该绑定的所有请求共享同一实例。

下表列出了生成的每个组件的作用域注释:

Android 类生成的组件作用域
ApplicationApplicationComponent@Singleton
View ModelActivityRetainedComponent@ActivityRetainedScope
ActivityActivityComponent@ActivityScoped
FragmentFragmentComponent@FragmentScoped
ViewViewComponent@ViewScoped
带有 @WithFragmentBindings 注释的 ViewViewWithFragmentComponent@ViewScoped
ServiceServiceComponent@ServiceScoped

如果您使用 @ActivityScopedAnalyticsAdapter 的作用域限定为 ActivityComponent,Hilt 会在相应 Activity 的整个生命周期内提供 AnalyticsAdapter 的同一实例:

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}
注意:将绑定的作用域限定为某个组件的成本可能很高,因为提供的对象在该组件被销毁之前一直保留在内存中。请在应用中尽量少用限定作用域的绑定。如果绑定的内部状态要求在某一作用域内使用同一实例,或者绑定的创建成本很高,那么将绑定的作用域限定为某个组件是一种恰当的做法

简解:就是当前使用作用域限定符的类,在生成的组件中的每个实例只能创建单一实例

假设 AnalyticsService 的内部状态要求每次都使用同一实例 - 不只是在 ExampleActivity 中,而是在应用中的任何位置。在这种情况下,将 AnalyticsService 的作用域限定为 ApplicationComponent 是一种恰当的做法。结果是,每当组件需要提供 AnalyticsService 的实例时,都会提供同一实例

// If AnalyticsService is an interface.
@Module
@InstallIn(ApplicationComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

// If you don't own AnalyticsService.
@Module
@InstallIn(ApplicationComponent.class)
public class AnalyticsModule {

  @Singleton
  @Provides
  public static AnalyticsService provideAnalyticsService() {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}
组件层次结构

将模块安装到组件后,其绑定就可以用作该组件中其他绑定的依赖项,也可以用作组件层次结构中该组件下的任何子组件中其他绑定的依赖项

参考:官网

注意:默认情况下,如果您在视图中执行字段注入,ViewComponent 可以使用 ActivityComponent 中定义的绑定。如果您还需要使用 FragmentComponent 中定义的绑定并且视图是 Fragment 的一部分,应将 @WithFragmentBindings 注释和 @AndroidEntryPoint 一起使用

组件默认绑定

就可以用作该组件中其他绑定的依赖项,也可以用作组件层次结构中该组件下的任何子组件中其他绑定的依赖项

参考:官网

注意:默认情况下,如果您在视图中执行字段注入,ViewComponent 可以使用 ActivityComponent 中定义的绑定。如果您还需要使用 FragmentComponent 中定义的绑定并且视图是 Fragment 的一部分,应将 @WithFragmentBindings 注释和 @AndroidEntryPoint 一起使用

组件默认绑定
在 Hilt 不支持的类中注入依赖项
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值