使用 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 类的支持方面还要注意以下几点:
- Hilt 仅支持扩展
ComponentActivity
的 Activity,如AppCompatActivity
。 - Hilt 仅支持扩展
androidx.Fragment
的 Fragment。 - Hilt 不支持保留的 Fragment。
@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;
}
...
}
在一个类的代码中,带有注释的构造函数的参数即是该类的依赖项。AnalyticsService
是 AnalyticsAdapter
的一个依赖项。因此,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 注入实例
如果某个类不归您所有(因为它来自外部库,如 Retrofit、OkHttpClient
或 Room 数据库等类),或者必须使用构建器模式创建实例,也无法通过构造函数注入。
接着前面的例子来讲。如果 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 组件 | 注入器面向的对象 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | 带有 @WithFragmentBindings 注释的 View |
ServiceComponent | Service |
注意:Hilt 不会为广播接收器生成组件,因为 Hilt 直接从 ApplicationComponent
注入广播接收器。
组件生命周期
Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例。
生成的组件 | 创建时机 | 销毁时机 |
---|---|---|
ApplicationComponent | Application#onCreate() | Application#onDestroy() |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | 视图销毁时 |
ViewWithFragmentComponent | View#super() | 视图销毁时 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
注意:ActivityRetainedComponent
在配置更改后仍然存在,因此它在第一次调用 Activity#onCreate()
时创建,在最后一次调用 Activity#onDestroy()
时销毁。
组件作用域
默认情况下,Hilt 中的所有绑定都未限定作用域。这意味着,每当应用请求绑定时,Hilt 都会创建所需类型的一个新实例。
不过,Hilt 也允许将绑定的作用域限定到特定组件上。Hilt 只为受绑定作用域限定到的组件的每个实例, 创建一次限定作用域的绑定,对该绑定的所有请求共享同一实例。
下表列出了生成的每个组件的作用域注释:
Android 类 | 生成的组件 | 作用域 |
---|---|---|
Application | ApplicationComponent | @Singleton |
View Model | ActivityRetainedComponent | @ActivityRetainedScope |
Activity | ActivityComponent | @ActivityScoped |
Fragment | FragmentComponent | @FragmentScoped |
View | ViewComponent | @ViewScoped |
带有 @WithFragmentBindings 注释的 View | ViewWithFragmentComponent | @ViewScoped |
Service | ServiceComponent | @ServiceScoped |
如果您使用 @ActivityScoped
将 AnalyticsAdapter
的作用域限定为 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
一起使用