Jetpack Hilt

幻昼 2024年04月16日 73次浏览

使用 Hilt 框架简化 Android 依赖注入

在现代的 Android 应用开发中,依赖注入是一项关键的技术,它可以帮助我们更轻松地管理应用中的对象依赖关系。Hilt 是 Google 推出的一个用于简化依赖注入的框架,它基于 Dagger,为 Android 应用提供了依赖注入的标准解决方案。本文将介绍如何在 Android 应用中使用 Hilt 框架。

1. Hilt 依赖导入

添加依赖项

首先,将 hilt-android-gradle-plugin 插件添加到项目的根级 build.gradle 文件中:

plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.44' apply false
}

然后,应用 Gradle 插件并在 app/build.gradle 文件中添加以下依赖项:

...
plugins {
  id 'kotlin-kapt'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

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

// 允许引用生成的代码
kapt {
  correctErrorTypes true
}

Hilt 使用 Java 8 功能。如需在项目中启用 Java 8,请将以下代码添加到 app/build.gradle 文件中:

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

2. Hilt 注入快速使用

2.1 应用配置

在应用的 Application 类上添加 @HiltAndroidApp 注解,以启用 Hilt:

@HiltAndroidApp
class MyApplication : Application() {
    // 应用的其他初始化代码
}

2.2 模块提供实例

创建一个或多个 Dagger 模块,用于提供依赖的实例。例如:

@InstallIn(ActivityComponent.class)
@Module
public class HttpModule {
    
    @Provides
    @ActivityScoped
    public HttpObject getHttpObject(){
        return new HttpObject();
    }
}

2.3 在 Activity 中注入字段

在需要注入依赖的 Activity 中,使用 @AndroidEntryPoint 注解标记,并通过 @Inject 注解将依赖注入到字段中:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var httpProcessor: IHttpProcessor
    
    // Activity 的其他代码,可以调用 httpProcessor
}

至此,就完成了最简单的使用方法了

3. Hilt 进阶用法

3.1 接口快速实例化

通过 @Binds 注解,可以快速地将接口绑定到其实现类:

先定义一个接口和其多个实现类:

public interface IHttpProcessor {
    //网络访问的能力
    void post(String url, Map<String,Object> params,ICallback callback);

    //....
}

实现类

OkHttpProcessor

3.2 使用 Binds

@Binds 注解用于告诉 Dagger 如何提供一个接口的实现类,而无需编写提供实例的方法。

@Module
@InstallIn(ApplicationComponent.class)
public abstract class HttpProcessorModule {

    @Binds
    @Singleton
    abstract IHttpProcessor bindOkhttp(OkHttpProcessor okHttpProcessor);

}

3.3 自定义注解示例化接口的不同实现类

你可以定义自己的注解,并在不同的场景下使用它们来注入不同的实现类。

实现类

OkHttpProcessor VolleyProcessor XUtilsProcessor

3.3.1 定义不同注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindOkhttp {
}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindVolley {
}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindXUtils {
}
3.3.2 使用
@Module
@InstallIn(ApplicationComponent.class)
public abstract class HttpProcessorModule {

    @BindOkhttp
    @Binds
    @Singleton
    abstract IHttpProcessor bindOkhttp(OkHttpProcessor okHttpProcessor);

    @BindVolley
    @Binds
    @Singleton
    abstract IHttpProcessor bindVolley(VolleyProcessor volleyProcessor);

    @BindXUtils
    @Binds
    @Singleton
    abstract IHttpProcessor bindXUtils(XUtilsProcessor xUtilsProcessor);
}

需要使用的地方引入即可

@BindOkhttp
@Inject
IHttpProcessor iHttpProcessor;

这样,当你需要注入 IHttpProcessor 的实例时,可以使用 @SomeQualifier 注解来标记所需的实现类。

为 Android 类生成的组件

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

前面的示例演示了如何在 Hilt 模块中使用 ActivityComponent

Hilt 提供了以下组件:

Hilt 组件 注入器面向的对象
SingletonComponent Application
ActivityRetainedComponent 不适用
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent 带有 @WithFragmentBindings 注解的 View
ServiceComponent Service

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

组件生命周期

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

生成的组件 创建时机 销毁时机
SingletonComponent Application#onCreate() Application 已销毁
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel 已创建 ViewModel 已销毁
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View 已销毁
ViewWithFragmentComponent View#super() View 已销毁
ServiceComponent Service#onCreate() Service#onDestroy()

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

组件作用域

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

生成的组件对应 Module @InstallIn(ApplicationComponent.class)

作用域对应 提供的实例生命周期,如 @Singleton

在本例中,每当 Hilt 提供 AnalyticsAdapter 作为其他类型的依赖项或通过字段注入提供它(如在 ExampleActivity 中)时,Hilt 都会提供 AnalyticsAdapter 的一个新实例。

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

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

Android 类 生成的组件 作用域
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
带有 @WithFragmentBindings 注解的 View ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

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

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

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

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

以下示例演示了如何将绑定的作用域限定为 Hilt 模块中的某个组件。绑定的作用域必须与其安装到的组件的作用域一致,因此在本例中,您必须将 AnalyticsService 安装在 SingletonComponent 中,而不是安装在 ActivityComponent 中:

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

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

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

组件层次结构

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

Hilt 生成的组件的层次结构

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

组件默认绑定

每个 Hilt 组件都附带一组默认绑定,Hilt 可以将其作为依赖项注入您自己的自定义绑定。请注意,这些绑定对应于常规 activity 和 fragment 类型,而不对应于任何特定子类。这是因为,Hilt 会使用单个 activity 组件定义来注入所有 activity。每个 activity 都有此组件的不同实例。

Android 组件 默认绑定
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent ApplicationActivity
FragmentComponent ApplicationActivityFragment
ViewComponent ApplicationActivityView
ViewWithFragmentComponent ApplicationActivityFragmentView
ServiceComponent ApplicationService

还可以使用 @ApplicationContext 获得应用上下文绑定。例如:

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

此外,还可以使用 @ActivityContext 获得 activity 上下文绑定。例如:

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }