Android Jetpack架构组件(十二)之Hilt

一、 依赖注入简介

依赖注入(英文Dependency Injection,简写DI)是一种被广泛使用的编程技术,主要的作用代码解耦。 借助依赖注入,我们可以轻松的管理类之间的依赖,并最终建立高可维护性和可扩展性的应用代码。

事实上,依赖注入并不是一项新技术,它在Java的Spring框架中早有应用,记得0x年上大学学习Spring框架的时候就听说了依赖注入,当时对依赖注入还不是很理解,对它的应用基本也不了解,不过在那个MVC大行其道的当时,Spring的出现可以说为大型项目带来了开天辟地的影响也不为过。

1.1 什么是依赖注入

前面说过,依赖注入框架是帮助我们管理类之间的依赖关系的。比如,在某个应用中,Car 类需要引用Engine类,也即是说,Car 类依赖于拥有 Engine 类的一个实例才能运行。在依赖注入框架中,类之间的依赖主要有以下三个场景。

  • 类构造其所需的依赖项。例如,在Car类中将建并初始化 Engine 实例。
  • 从其他地方抓取。比如Android API,Context getter 和 getSystemService()等。
  • 以参数形式提供。应用可以在构造类时提供这些依赖项,或者将这些依赖项传入需要各个依赖项的函数。

例如,下面是在不使用依赖项注入的情况下,Car类创建自己的 Engine类依赖项的示例代码。

class Car {

    private Engine engine = new Engine();

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}

上面是使用new 关键字来自己的 Engine,不过这么引用Engine可能会带来如下问题。

  • Car 和 Engine 密切相关。 Car 的实例只能使用一种类型的 Engine,无法轻松使用子类或替代。
  • 对 Engine 的强依赖使得编写测试更加困难。

在上面的示例中,Car 和 Engine 的依赖关系如下图所示。
Android Jetpack架构组件(十二)之Hilt_第1张图片

那如果使用依赖项注入,代码是什么样子的呢?下面我们使用构造函数的方式来实现依赖注入。

class Car {

    private final Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Engine engine = new Engine();
        Car car = new Car(engine);
        car.start();
    }
}

在上面的代码中,Car 的每个实例在其构造函数中接收 Engine 对象作为参数,而不是在初始化时构造自己的 Engine 对象。由于 Car 依赖于 Engine,因此应用会创建 Engine 的实例,然后使用它构造 Car 的实例。使用这种基于 DI 的方法具有以下优势。

  • 重用依赖的类。在例子中,我们可以将 Engine 的不同实现传入 Car,例如可以定义一个想要 Car 使用的名为 ElectricEngine 的新Engine子类。
  • 轻松编写Car的测试用例。我们可以根据传入测试的不同依赖对象以测试不同场景。

在上面的示例中,Car和Engine的关系如下图所示。
Android Jetpack架构组件(十二)之Hilt_第2张图片

在Android开发中,依赖注入主要有构造函数和字段注入两种实现方式。

  • 构造函数注入。使用构造函数方式将某个类的依赖项传入进去。
  • 字段注入(或 setter 注入)。某些 Android 框架类(如 Activity 和 Fragment)是由系统实例化的,因此无法进行构造函数注入。

使用字段注入时,依赖项将在创建类后实例化,如下所示。

class Car {

    private Engine engine;

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.setEngine(new Engine());
        car.start();
    }
}

1.2 自定依赖注入

在上面的示例代码中,我们的依赖项需要开发者自行管理,这称为手动依赖项注入或人工依赖项注入。在上面的 示例中我们只有一个依赖项,如果依赖项和类越多,使用手动注入就会越繁琐,且会会带来多个问题。

  • 对于大型应用,获取所有依赖项并正确连接它们可能需要大量样板代码。特别是在多层架构中,要为顶层创建一个对象,必须提供其下层的所有依赖项。
  • 如果无法在传入依赖项之前构造依赖项,则需要编写并维护管理内存中依赖项生命周期的自定义容器(或依赖关系图),非常繁琐。

因此,更多的时候,我们需要有一些库通过自动执行创建和提供依赖项的过程解决此问题。它们归为两类。

  • 基于反射的解决方案,可在运行时连接依赖项。
  • 静态解决方案,可生成在编译时连接依赖项的代码。

在Android开发中,Dagger是一款支持Java、Kotlin 和 Android 的热门依赖项注入库,它提供了完全静态和编译时依赖项,解决了基于反射的解决方案(如 Guice)的诸多开发和性能问题,已经被广泛使用在Android应用开发中。

1.3 依赖项注入的替代方法

依赖项注入的替代方法使用的是服务定位器方法。服务定位器设计模式还改进了类与具体依赖项的分离。使用时,我们可以创建一个名为服务定位器的类,该类创建和存储依赖项,然后按需提供这些依赖项。

class ServiceLocator {

    private static ServiceLocator instance = null;

    private ServiceLocator() {}

    public static ServiceLocator getInstance() {
        if (instance == null) {
            synchronized(ServiceLocator.class) {
                instance = new ServiceLocator();
            }
        }
        return instance;
    }

    public Engine getEngine() {
        return new Engine();
    }
}

class Car {

    private Engine engine = ServiceLocator.getInstance().getEngine();

    public void start() {
        engine.start();
    }
}

class MyApp {
    public static void main(String[] args) {
        Car car = new Car();
        car.start();
    }
}

服务定位器模式与依赖项注入在元素使用方式上有所不同。使用服务定位器模式,类可以控制并请求注入对象;使用依赖项注入,应用可以控制并主动注入所需对象。在上面的示例中,服务定位器模式借助了单例模式,因此可以对传入的对象进行控制。

1.4 Android 自动依赖注入

Android中有两种自动依赖项注入方式,即Hilt和Dagger 2,官方推荐使用Hilt。Hilt 在依赖项注入库Dagger的基础上构建而成,提供了一种将Dagger纳入Android应用的标准方法,因而能够受益于 Dagger 提供的编译时正确性、运行时性能、可伸缩性和 Android Studio 支持。

不过,相比Dagger 2,Hilt带来了如下的一些改变。

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

同时,Hilt 在减少Android 应用中使用 Dagger 所涉及的样板代码的基础上,Hilt 会自动生成并提供以下各项。

  • 用于将 Android 框架类与 Dagger 集成的组件,开发者不需要再手动创建。
  • 作用域注释,与 Hilt 自动生成的组件一起使用。
  • 预定义的绑定,表示 Android API 类,如 Application 或 Activity。
  • 预定义的限定符,如@ApplicationContext 和 @ActivityContext。

二、Hilt的基本使用

2.1 添加依赖项

Hilt 是 Android 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。执行手动依赖项注入要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。

使用Hilt之前,首先将 hilt-android-gradle-plugin 插件添加到项目的根级 build.gradle 文件中,如下所示。

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

然后,在 app/build.gradle 文件中添加以下依赖项。

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

android {
    ...
}

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

同时,Hilt用到了Java 8的一些功能,如需在项目中启用 Java 8,请将以下代码添加到 app/build.gradle 文件中。

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

需要说明的是,由于kotlin-kapt会依赖kotlin的相关环境,因此新建项目的时候,请添加Kotlin的支持。

2.2 添加依赖注入

2.2.1 定义Hilt应用类

所有使用Hilt注入框架的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application 类。@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器,如下所示。

@HiltAndroidApp
public class MyApplication extends Application {
    
    @Override
    public void onCreate() {
        super.onCreate();
    }
}

然后,在AndroidManifest.xml中引入我们自行用的Application类。此时,生成的Hilt组件会附加到 Application 对象的生命周期,并为其提供依赖项。此外,由于它也是应用的父组件,所以其他组件可以访问它提供的依赖项。

2.2.2 依赖项注入 Android 类

在Application类中设置另外 Hilt 且有了应用级组件后,接下来就可以为带有 @AndroidEntryPoint 注释的其他 Android 类提供依赖项,如下所示。

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

Hilt目前支持常用的Android基类,比如Application、Activity、Fragment、View、Service和BroadcastReceiver等。不过需要说明的是,目前Hilt只支持androidx组件,如果是support则不支持。

同时,但我们使用@AndroidEntryPoint 为某个 Android 类添加注释,则还必须为依赖于该类的 Android 类添加注释。例如,如果您为某个Fragment添加注释,则还必须为使用该Fragment的所有Activity添加注释。如果需要从组件获取依赖项,那么可以使用@Inject 注释执行字段注入。

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

当然,Hilt注入的类可以有同样使用注入的其他基类,如果这些类是抽象类,则它们不需要使用@AndroidEntryPoint注释。

2.3 定义Hilt绑定

为了执行字段注入,Hilt 需要知道如何在相应组件中提供必要依赖项的实例。然后,在“绑定”时提供某个类型的实例作为依赖项提供所需的信息。在Android中,向Hilt提供绑定信息的一种方法是构造函数注入。在某个类的构造函数中使用 @Inject 注释,以告知 Hilt 如何提供该类的实例。

public class AnalyticsAdapter {

  private final AnalyticsService service;

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

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

2.4 Hilt模块

有时候,类型是不能通过构造函数注入的,对于这种情况,我们可以使用Hilt 模块来向Hilt提供绑定信息。事实上,Hilt模块是一个带有@Module注释的类,与Dagger模块的作用一样,它会告知Hilt如何提供某些类型的实例。与Dagger模块不同的是,我们必须使用@InstallIn注解为Hilt模块添加注释,以告知Hilt模块将用在哪个Android类中。

2.4.1 @Binds注入接口

以AnalyticsService接口类为例,由于AnalyticsService 是一个接口,无法通过构造函数注入Hilt,而应向Hilt提供绑定信息。即在Hilt模块内创建一个带有@Binds注释的抽象函数。通常,带有注释的函数会向Hilt提供以下信息。

  • 函数返回类型会告知Hilt函数提供的是哪个接口的实例。
  • 函数参数会告知Hilt需要提供哪种实现。

下面是一段@Binds注入接口的实例。

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
  );
}

由于AnalyticsModule 带有 @InstallIn(ActivityComponent.class) 注释,因为我们可以将该依赖项注入MainActivity中。并且,AnalyticsModule中的所有依赖项都可以在所有Activity中使用。

2.4.2 @Provides 注入

接口并不是无法通过构造函数注入类型的唯一一种情况。比如,某个类并不在当前的库所有(因为它来自外部库,如 Retrofit、OkHttpClient 或 Room 数据库等类),那么必须使用构建器模式创建实例。

以上面的例子来说,如果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 提供多个绑定,同一类型定义多个绑定可以使用限定符来实现。限定符也是一种注解,当为某个类型定义了多个绑定时,我们可以使用它来标识该类型的特定绑定。

以上面的例子为例,如果需要拦截对AnalyticsService的调用,您可以使用带有拦截器的 OkHttpClient 对象。对于其他服务,可能需要以不同的方式拦截调用,在这种情况下,我们需要告知Hilt如何提供两种不同的OkHttpClient实现。

首先,定义要用于为 @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 MainActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @Inject
  OkHttpClient okHttpClient;
  ...
}

如果需要向某个类型添加限定符,那么应该向提供该依赖项的所有可能的渠道都添加限定符。这是因为让基本的实现或通用的实现在不带限定符的情况下避免出错,也是为了避免导致Hilt注入错误的依赖项。

Hilt中的预定义限定符

Hilt提供了一些预定义的限定符。比如,我们需要获取Activity中的Context 类,那么可以使用Hilt 提供的@ApplicationContext 和 @ActivityContext 限定符。

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service;
  }
}

2.5 为Android类生成组件

当我们使用依赖注入到Android类时,依赖注入框架会为生成一个关联的Hilt组件,我们可以使用 @InstallIn注释来引用该组件。当我们使用Hilt注入框架时,每个Hilt组件负责将其绑定注入相应的Android类。下面是Hilt提供了常用的组件。

Hilt 组件 注入器面向的对象
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent 带有 @WithFragmentBindings 注释的 View
ServiceComponent Service

对应的组件的生命周期

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

组件作用域

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

当然,Hilt也允许将绑定的作用域限定为特定组件。此时,Hilt只为绑定作用域限定到的组件的每个实例创建一次限定作用域的绑定,对该绑定的所有请求共享同一实例,类似于单例模式。下面是Hilt生成的组件的默认的作用域。

Android 类 生成的组件 作用域
Application ApplicationComponent @Singleton
ViewModel ActivityRetainedComponent @ActivityRetainedScope
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
带有 @WithFragmentBindings 注释的 View View#super() View#onDestroy()
ServiceComponent Service#onCreate() Service#onDestroy()

比如,在上面的例子中,如果我们使用@ActivityScoped将AnalyticsAdapter的作用域限定为 ActivityComponent,那么Hilt会在相应Activity的整个生命周期内提供 AnalyticsAdapter 的同一实例,如下所示。

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

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

不过,我们希望在应用中的任何位置都使用同一实例,而不只是在某个Activity中,那么对于这种情况,可以将 AnalyticsService 的作用域限定为 ApplicationComponent。此时,每当组件需要提供AnalyticsService的实例时,都会提供同一实例。

下面的示例演示了如何将绑定的作用域限定为Hilt模块中的某个组件,并且绑定的作用域必须与其安装到的组件的作用域一致。因此在本例中,我们必须将AnalyticsService安装在ApplicationComponent中,而不是安装在ActivityComponent中,代码如下。

// 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);
  }
}

组件层次结构

将Hilt模块安装到组件后,其绑定就可以与其他绑定的依赖项相互发生作用了,也可以用作组件层次结构中该组件下的任何子组件中其他绑定的依赖项。下图是组件的层次结构。
Android Jetpack架构组件(十二)之Hilt_第3张图片

默认绑定

每个Hilt组件都附带一组默认绑定,Hilt可以将其作为依赖项注入到自定义绑定。不过需要注意的是,这些绑定对应于的是常规 Activity 和 Fragment 类型,而不对应于任何特定子类。

Android 组件 默认绑定
ApplicationComponent Application
ActivityRetainedComponent Application
ActivityComponent Application 和 Activity
FragmentComponent Application、Activity 和 Fragment
ViewComponent Application、Activity 和 View
ViewWithFragmentComponent Application、Activity、Fragment 和 View
ServiceComponent Application 和 Service

开发中,我们可以使用 @ApplicationContext 获得应用上下文绑定,例子如下。

public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

也可以使用 @ActivityContext来获得 Activity 上下文绑定,如下所示。

public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

2.6 Hilt不支持的注入依赖项

目前,Hilt支持一些最常见的 Android 类,如Application、Activity、Fragment、View、Service
和BroadcastReceiver。不过,有时候我们需要在Hilt不支持的类中执行字段注入。在这些情况下,我们可以使用@EntryPoint来注释创建入口点。入口点是由Hilt管理的代码与并非由Hilt管理的代码之间的边界,它是代码首次进入Hilt所管理对象的图的位置,入口点允许Hilt使用它并不管理的代码提供依赖关系图中的依赖项。

例如,下面是一个使用@EntryPoint自定义注入依赖组件的示例,代码如下。

public class ExampleContentProvider extends ContentProvider {

  @EntryPoint
  @InstallIn(ApplicationComponent.class)
  interface ExampleContentProviderEntryPoint {
    public AnalyticsService analyticsService();
  }
  ...
}

当我们需要访问入口点时,只需要使用EntryPointAccessors 即可获取静态方法,如下所示。

public class ExampleContentProvider extends ContentProvider {

  @Override
  public Cursor query(...) {
    Context appContext = getContext().getApplicationContext();
    ExampleContentProviderEntryPoint hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint.class);
    AnalyticsService analyticsService = hiltEntryPoint.analyticsService();
  }
}

其中,参数是组件实例或充当组件持有者的 @AndroidEntryPoint 对象,使用时以参数形式传递的组件和 EntryPointAccessors 静态方法都与 @EntryPoint 接口上的 @InstallIn 注释中的 Android 类匹配。

在本例中,我们使用 ApplicationContext 作为检索入口点,因为入口点安装在 ApplicationComponent 中。如果需要检索的绑定位于 ActivityComponent 中那么改为 ActivityContext即可。

三、在多模块应用中使用Hilt

由于Hilt框架在生成注入代码时需要访问所有Gradle模块,对于多模块的Android应用来说应该如何操作呢?

3.1 在动态功能模块中使用Hilt

在动态功能模块 (DFM) 的Android应用中,模块之间相互依赖是比较混乱的,因此Hilt 无法在动态功能模块中处理注释,此时必须在 DFM 中使用 Dagger来执行依赖项的注入。为了解决 DFM的组件依赖关系,我们需要使用到Dagger,集成的步骤如下。

  1. 在具有DFM所需依赖项的app模块(或可由 Hilt 处理的其他任何模块)中声明一个 @EntryPoint 接口。
  2. 创建一个依赖于@EntryPoint接口的Dagger组件。
  3. 在DFM中照常使用Dagger。

现在,假如我们需要向项目中添加了一个login动态功能模块,我们使用一个名为 LoginActivity 的Activity实现登录功能。这意味着,我们只能从应用组件获取绑定。首先,我们创建一个安装在login模块所需绑定的ApplicationComponent 中的@EntryPoint接口,代码如下。

@EntryPoint
@InstallIn(ApplicationComponent.class)
public interface LoginModuleDependencies {

  @AuthInterceptorOkHttpClient
  OkHttpClient okHttpClient();
}

接下来,为了能够在LoginActivity中执行字段注入,还需要创建一个依赖于@EntryPoint接口的 Dagger组件。

@Component(dependencies = LoginModuleDependencies.class)
public interface LoginComponent {

  void inject(LoginActivity loginActivity);

  @Component.Builder
  interface Builder {
    Builder context(@BindsInstance Context context);
    Builder appDependencies(LoginModuleDependencies loginModuleDependencies);
    LoginComponent build();
  }
}

然后,我们在 DFM 中照常使用Dagger添加依赖项,如下所示。

public class LoginAnalyticsAdapter {

  private final OkHttpClient okHttpClient;

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

为了执行字段注入,需要使用applicationContext创建Dagger组件的实例以获取ApplicationComponent依赖项,如下所示。

public class LoginActivity extends AppCompatActivity {

  @Inject
  LoginAnalyticsAdapter loginAnalyticsAdapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    DaggerLoginComponent.builder()
        .context(this)
        .appDependencies(
          EntryPointsAccessors.fromApplication(
            getApplicationContext(),
            LoginModuleDependencies.class
          )
        )
        .build()
        .inject(this);

    super.onCreate(savedInstanceState);
    ...
  }
}

四、Hilt和Jetpack集成

目前,Hilt支持和Jetpack库配合使用,支持的Jetpack组件包括ViewModel和WorkManager。

4.1 Hilt注入ViewModel对象

如果需要注入ViewModel对象,请将下面的依赖项添加到 Gradle 文件中。

...
dependencies {
  ...
  implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
  // Kotlin.
  kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
  // Java.
  annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
}

然后,在ViewModel 对象的构造函数中使用 @ViewModelInject 注解创建一个ViewModel,还必须使用@Assisted为SavedStateHandle依赖项添加注解。

public class ExampleViewModel extends ViewModel {

  private final ExampleRepository repository;
  private final SavedStateHandle savedStateHandle;

  @ViewModelInject
  ExampleViewModel(
      ExampleRepository repository,
      @Assisted SavedStateHandle savedStateHandle)
    {
    this.repository = repository;
    this.savedStateHandle = savedStateHandle;
  }
  ...
}

我们可以使用带有@AndroidEntryPoint注解标记Activity或Fragment,然后使用 ViewModelProvider或viewModels() 的KTX 扩展来获取 ViewModel 实例,如下所示。

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  private ExampleViewModel exampleViewModel;

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

4.2 Hilt注入WorkManager

使用Hilt注入WorkManager,需要在Gradle文件中添加如下一些额外的依赖项。

...
dependencies {
  ...
  implementation 'androidx.hilt:hilt-work:1.0.0-alpha01'
  // Kotlin.
  kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
  // Java.
  annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
}

首先,在Worker对象的构造函数中使用@WorkerInject注解来注入一个 Worker,然后在 Worker对象中使用@Singleton或未限定作用域的绑定对象的作用域,还必须使用 @Assisted 为 Context 和 WorkerParameters 依赖项添加注释。

public class ExampleWorker extends Worker {

  private final WorkerDependency workerDependency;

  @WorkerInject
  ExampleWorker(
    @Assisted @NonNull Context context,
    @Assisted @NonNull WorkerParameters params,
    WorkerDependency workerDependency
  ) {
    super(context, params);
    this.workerDependency = workerDependency;
  }
  ...
}

然后,让Application类实现Configuration.Provider接口,注入HiltWorkFactory 的实例,并将其传入WorkManager配置,如下所示。

@HiltAndroidApp
public class ExampleApplication extends Application implements Configuration.Provider {

  @Inject HiltWorkerFactory workerFactory;

  @Override
  public Configuration getWorkManagerConfiguration() {
    return Configuration.Builder()
             .setWorkerFactory(workerFactory)
             .build();
  }
}

参考:

Android Jetpack架构组件(十)之Slices
Android Jetpack架构组件(九)之Paging
Android Jetpack架构组件(八)之DataBinding
Android Jetpack架构组件(七)之WorkManager
Android Jetpack架构组件(六)之Room
Android Jetpack架构组件(五)之Navigation
Android Jetpack架构组件(四)之LiveData
Android Jetpack架构组件(三)之ViewModel
Android Jetpack架构组件(二)之Lifecycle
Android Jetpack架构组件(一)与AndroidX

你可能感兴趣的:(java,android,kotlin)