Jetpack架构组件库:Hilt_android hilt

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Hilt

Hilt 是基于 Dagger2 的依赖注入框架Google团队将其专门为Android开发打造了一种纯注解的使用方式相比 Dagger2 而言使用起来更加简单。

依赖注入框架的主要作用就是控制反转IOC, Inversion of Control, 那么什么是控制反转呢

首先它是一种技术思想而不是一种具体的技术体现。它描述的是面向对象的开发语言领域中的对象的创建和管理问题。

  • 控制指的是对象创建实例化、管理的权力
  • 反转将控制权交给外部环境IOC框架
  • 传统的开发方式一个类里面有很多成员变量对象这些成员对象都需要new出来
  • IOC思想的开发方式IOC原则NO我们不要new这样耦合度太高入参改变所有引用都要改)而是通过IOC容器如Hilt来帮助我们实例化对象并赋值。

总结一句话就是一个类中创建对象是一种控制能力控制反转就是将这种创建对象的控制权转交给外部框架来自动实现。

常见的依赖注入手段

  • 解决方案一配置xml文件里面标明哪个类用了哪些成员变量等需要加载这个类的时候我帮你注入new进去。Spring服务器开发常用IOC方案
  • 解决方案二在需要注入的成员变量上添加注解例如 @Inject在编译时生成相关的实现类运行时动态注入。(Android开发中常用的手段是 APT + JavaPoet 或者是 KSP + KotlinPoet)

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"
}

// Allow references to generated code
kapt {
  correctErrorTypes true
}

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

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

Hilt 应用类

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

@HiltAndroidApp 会触发 Hilt 的代码生成操作生成的代码包括应用的一个基类该基类充当应用级依赖项容器。
在这里插入图片描述

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

将依赖项注入 Android 类

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

在这里插入图片描述

Hilt 目前支持以下 Android

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

如果您使用 @AndroidEntryPoint 为某个 Android 类添加注解则还必须为依赖于该类的 Android 类添加注解。例如如果您为某个 fragment 添加注解则还必须为使用该 fragment 的所有 activity 添加注解。

注意在 HiltAndroid 类的支持方面适用以下几项例外情况

  • Hilt 仅支持扩展 ComponentActivityactivityAppCompatActivity
  • Hilt 仅支持扩展 androidx.FragmentFragment
  • Hilt 不支持保留的 fragment

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

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

在这里插入图片描述

注意由 Hilt 注入的字段不能为私有字段。尝试使用 Hilt 注入私有字段会导致编译错误。

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

如需详细了解 Android 类被注入的是哪个生命周期回调请参阅组件生命周期。

定义 Hilt 绑定

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

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

在这里插入图片描述

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

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

  • 构建并验证依赖关系图确保没有未满足的依赖关系且没有依赖循环。
  • 生成它在运行时用来创建实际对象及其依赖项的类。

Hilt 模块

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

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

注意Hilt 模块与 Gradle 模块不同。

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

注意由于 Hilt 的代码生成操作需要访问使用 Hilt 的所有 Gradle 模块因此编译 Application 类的 Gradle 模块还需要在其传递依赖项中包含您的所有 Hilt 模块和通过构造函数注入的类。

使用 @Binds 注入接口实例

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

@Binds 注解会告知 Hilt 在需要提供接口的实例时要使用哪种实现。
在这里插入图片描述
带有注解的函数会向 Hilt 提供以下信息

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

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

使用 @Provides 注入实例

接口不是无法通过构造函数注入类型的唯一一种情况。如果某个类不归您所有因为它来自外部库如 RetrofitOkHttpClientRoom 数据库等类或者必须使用构建器模式创建实例也无法通过构造函数注入。

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

在这里插入图片描述

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

  • 函数返回类型会告知 Hilt 函数提供哪个类型的实例。
  • 函数参数会告知 Hilt 相应类型的依赖项
  • 函数主体会告知 Hilt 如何提供相应类型的实例。每当需要提供该类型的实例时Hilt 都会执行函数主体。

为同一类型提供多个绑定

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

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

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

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

在这里插入图片描述
然后Hilt 需要知道如何提供与每个限定符对应的类型的实例。在这种情况下您可以使用带有 @ProvidesHilt 模块。这两种方法具有相同的返回类型但限定符将它们标记为两个不同的绑定
在这里插入图片描述
您可以通过使用相应的限定符为字段或参数添加注解来注入所需的特定类型
在这里插入图片描述
最佳做法是如果您向某个类型添加限定符应向提供该依赖项的所有可能的方式添加限定符。让基本实现或通用实现不带限定符容易出错并且可能会导致 Hilt 注入错误的依赖项。

Hilt 中的预定义限定符

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

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

在这里插入图片描述
如需了解 Hilt 中提供的其他预定义绑定请参阅组件默认绑定。

为 Android 类生成的组件

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

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

Hilt 提供了以下组件
在这里插入图片描述

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

组件生命周期

Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例
在这里插入图片描述

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

组件作用域

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

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

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

下表列出了生成的每个组件的作用域注解
在这里插入图片描述
在本例中如果您使用 @ActivityScopedAnalyticsAdapter 的作用域限定为 ActivityComponentHilt 会在相应 activity 的整个生命周期内提供 AnalyticsAdapter 的同一实例

在这里插入图片描述

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

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

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

在这里插入图片描述
如需详细了解 Hilt 组件作用域请参阅 Android 和 Hilt 中的作用域限定。

注意如需详细了解使用 @ActivityRetainedScoped 或 @ViewModelScoped 限定作用域的区别请参阅 Hilt 和 Jetpack 集成文档中的 @ViewModelScoped 部分。

组件层次结构

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

在这里插入图片描述

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

组件默认绑定

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

在这里插入图片描述
还可以使用 @ApplicationContext 获得application上下文绑定。例如

在这里插入图片描述
此外还可以使用 @ActivityContext 获得 activity 上下文绑定。例如

在这里插入图片描述

在 Hilt 不支持的类中注入依赖项

Hilt 支持最常见的 Android 类。不过您可能需要在 Hilt 不支持的类中执行字段注入。

在这些情况下您可以使用 @EntryPoint 注解创建入口点。入口点是由 Hilt 管理的代码与并非由 Hilt 管理的代码之间的边界。它是代码首次进入 Hilt 所管理对象的图的位置。入口点允许 Hilt 使用并非由 Hilt 管理的代码提供依赖关系图中的依赖项。

例如Hilt 并不直接支持 content provider。如果您希望 content provider 使用 Hilt 来获取某些依赖项需要为所需的每个绑定类型定义一个带有 @EntryPoint 注解的接口并添加限定符。然后添加 @InstallIn 以指定要在其中安装入口点的组件如下所示

在这里插入图片描述

如需访问入口点请使用来自 EntryPointAccessors 的适当静态方法。参数应该是组件实例或充当组件持有者的 @AndroidEntryPoint 对象。确保您以参数形式传递的组件和 EntryPointAccessors 静态方法都与 @EntryPoint 接口上的 @InstallIn 注解中的 Android 类匹配

在这里插入图片描述
在本例中您必须使用 ApplicationContext 检索入口点因为入口点安装在 SingletonComponent 中。如果您要检索的绑定位于 ActivityComponent 中应改用 ActivityContext

Hilt 和 Dagger

Hilt 在依赖项注入库 Dagger 的基础上构建而成提供了一种将 Dagger 纳入 Android 应用的标准方法。

关于 DaggerHilt 的目标如下

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

由于 Android 操作系统会实例化它自己的许多框架类因此在 Android 应用中使用 Dagger 要求您编写大量的样板。Hilt 可减少在 Android 应用中使用 Dagger 所涉及的样板代码。Hilt 会自动生成并提供以下各项

  • 用于将 Android 框架类与 Dagger 集成的组件 - 您不必手动创建。
  • 作用域注解 - 与 Hilt 自动生成的组件一起使用。
  • 预定义的绑定 - 表示 Android 类如 ApplicationActivity
  • 预定义的限定符 - 表示 @ApplicationContext@ActivityContext

DaggerHilt 代码可以共存于同一代码库中。不过在大多数情况下最好使用 Hilt 管理您在 Android 上对 Dagger 的所有使用。

将 Hilt 和其他 Jetpack 库一起使用

Hilt 包含可用于从其他 Jetpack 库提供类的扩展。Hilt 目前支持以下 Jetpack 组件

  • ViewModel
  • Navigation
  • Compose
  • WorkManager

您必须添加 Hilt 依赖项才能利用这些集成。

使用 Hilt 注入 ViewModel 对象

提供 ViewModel方法是为其添加 @HiltViewModel 注解并在 ViewModel 对象的构造函数中使用 @Inject 注解。
在这里插入图片描述
然后带有 @AndroidEntryPoint 注解的 activityfragment 可以使用 ViewModelProviderby viewModels() KTX 扩展照常获取 ViewModel 实例

在这里插入图片描述

@ViewModelScoped

所有 Hilt ViewModel 都由 ViewModelComponent 提供后者遵循与 ViewModel 相同的生命周期因此可以在配置更改后继续存在。如需将依赖项的作用域限定为 ViewModel请使用 @ViewModelScoped 注解。

使用 @ViewModelScoped 类型后系统会在注入 ViewModel 的所有依赖项中提供限定了作用域的类型的单个实例。请求限定了作用域的类型的 ViewModel 的其他实例会收到其他实例。

如果需要在不同 ViewModel 之间共享单个实例则应使用 @ActivityRetainedScoped@Singleton 限定其作用域。

与 Jetpack Navigation 库集成

请将下面这些额外的依赖项添加到 Gradle 文件中

dependencies {
    ...
    implementation 'androidx.hilt:hilt-navigation-fragment:1.0.0'
}

如果 ViewModel 的作用域限定为导航图请使用 hiltNavGraphViewModels 函数该函数可与带有 @AndroidEntryPoint 注解的 fragment 搭配使用。

val viewModel: ExampleViewModel by hiltNavGraphViewModels(R.id.my_graph)

与 Jetpack Compose 集成

ViewModel 部分提及的 viewModel() 函数自动使用 Hilt 通过 @HiltViewModel 注解构造的 ViewModel
在这里插入图片描述

Hilt 和 Navigation

Hilt 还与 Navigation Compose 库集成。请将下面这些额外的依赖项添加到 Gradle 文件中

dependencies {
    implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
}

使用 Navigation Compose 时请始终使用 hiltViewModel 可组合函数获取带有 @HiltViewModel 注解的 ViewModel 的实例。该函数可与带有 @AndroidEntryPoint 注解的 fragmentactivity 搭配使用。

例如如果 ExampleScreen 是导航图中的目的地请调用 hiltViewModel() 来获取作用域限定为该目的地的 ExampleViewModel 实例如以下代码段所示
在这里插入图片描述
如果您需要改为检索作用域限定为导航路线或导航图的 ViewModel 实例请使用 hiltViewModel 可组合函数并将相应的 backStackEntry 作为参数传递

在这里插入图片描述

使用 Hilt 注入 WorkManager

将下面这些额外的依赖项添加到 Gradle 文件中。请注意除了库之外您还需要添加一个额外的注释处理器它在 Hilt 注释处理器的基础上运行

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

注入一个 Worker方法是在类中使用 @HiltWorker 注解并在 Worker 对象的构造函数中使用 @AssistedInject。您只能在 Worker 对象中使用 @Singleton 或未限定作用域的绑定。您还必须使用 @AssistedContextWorkerParameters 依赖项添加注解

在这里插入图片描述
然后让 Application 类实现 Configuration.Provider 接口注入 HiltWorkFactory 的实例并将其传入 WorkManager 配置如下所示

在这里插入图片描述

注意由于这会自定义 WorkManager 配置因此您还必须按照 WorkManager 文档中指定的方法从 AndroidManifest.xml 文件中移除默认的初始化程序。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Hilt组件库背后做了啥

添加了Hilt相应注解的类会在build目录下会生成对应的以Hilt_xxx开头的java

在这里插入图片描述

在这里插入图片描述
可以看到对于 Activity 是在生成的 Hilt_xxxActivity 的构造方法中调用了 inject() 方法。 Hilt_xxxActivity 里面的逻辑很简单就是将当前对象强转成对应的子类对象然后保存到对应类型的Component中。

而对于 Application 则是在 onCreate() 方法中调用了 inject() 方法

import android.app.Application;
import androidx.annotation.CallSuper;
import dagger.hilt.android.internal.managers.ApplicationComponentManager;
import dagger.hilt.android.internal.managers.ComponentSupplier;
import dagger.hilt.android.internal.modules.ApplicationContextModule;
import dagger.hilt.internal.GeneratedComponentManagerHolder;
import dagger.hilt.internal.UnsafeCasts;
import java.lang.Object;
import java.lang.Override;

/**
 * A generated base class to be extended by the @dagger.hilt.android.HiltAndroidApp annotated class. If using the Gradle plugin, this is swapped as the base class via bytecode transformation.
 */
public abstract class Hilt_MyApp extends Application implements GeneratedComponentManagerHolder {
  private boolean injected = false;

  private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
    @Override
    public Object get() {
      return DaggerMyApp_HiltComponents_SingletonC.builder()
          .applicationContextModule(new ApplicationContextModule(Hilt_MyApp.this)).build();
    }
  });

  @Override
  public final ApplicationComponentManager componentManager() {
    return componentManager;
  }

  @Override
  public final Object generatedComponent() {
    return this.componentManager().generatedComponent();
  }

  @CallSuper
  @Override
  public void onCreate() {
    hiltInternalInject();
    super.onCreate();
  }

  protected void hiltInternalInject() {
    if (!injected) {
      injected = true;
      // This is a known unsafe cast, but is safe in the only correct use case:
      // MyApp extends Hilt_MyApp
      ((MyApp_GeneratedInjector) generatedComponent()).injectMyApp(UnsafeCasts.<MyApp>unsafeCast(this));
    }
  }
}

但是这些都是一些抽象类那么在哪里实现的呢

在build目录下搜索会发现添加了相关注解的类都会生成对应类名的dex文件比如MainActivity会生成一个MainActivity.dex使用反编译工具打开对应的dex文件进行查看

在这里插入图片描述
在这里插入图片描述
可以看到我们自己写的业务类的父类被修改为继承了Hilt生成的Hilt_XXX相关的抽象类。

因此可知Hilt 会在编译器修改字节码修改了继承的父类为Hilt_XXXA相关的抽象类然后在父类中进行了强行注入处理逻辑。

IOC 的优缺点

Hilt 很好但是我们一定要在项目中使用它吗文章的最后我们来总结一下 IOC 的优缺点。

IOC 的优势

  • 对象之间的耦合度降低
  • 对象实例的创建变得比较容易管理很容易创建一个全局/局部的共享单例
  • 场景模板代码创建实例对象全局或局部对象共享

IOC 的缺点

  • 代码可读性较差不知道对象在哪里被创建的实例创建的时机在哪里入参是什么
  • 增加新人学习成本和维护成本尤其是对团队的新成员而言对接手的新项目要熟悉整个IOC框架的运作模式才敢下手改代码。
  • 额外生成大量的父类或方法来做IOC注入处理逻辑增加编译成本和App的体积加速触及65536方法数等

是否有必要引入 Hilt 或其他类似的 IOC?

  • 通常来说这不是必须的取决于你的项目复杂程度。如果是简单的小项目使用它并不会变得更加简单反而可能会适得其反学习成本和工作收益不成正比。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: android