Hilt (刀把)是 Android 的依赖项注入库,专门面向Android的依赖注入框架。可减少在项目中执行手动依赖项注入的样板代码。执行手动依赖项注入要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。
Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法
依赖注入的英文名是Dependency Injection,简称DI
。DI的最大作用就是解耦。本质其实在开发程序这块模块清晰,底层还是需要代码逻辑来去支撑的,只不过通过这种技术把一些依赖关系给封装了而已。
本文结合众多网上文档,取精华和重点。
目前很多项目使用了众多MVP模式,后期又提出使用MVVM模式来搭建项目。Android在复杂项目中,对象之间的依赖关系也十分可怕,所以Android如果依赖注入也能使项目结构更清晰。
谷歌推荐MVVM 架构来搭建Android应用。
Dagger(匕首)Dagger2也是依赖注入的框架,国内使用的不是太多。Dagger1前期是Square维护,主要以反射技术实现,但是反射很性能方面的问题。后期Google开始维护Dagger2,使用编译时期生成对象之间的各种依赖关系,这样编译期就可以知道报错信息,性能更好。Hilt是基于Dagger2之上做的进一步升级,所以学习Hilt最好学习下Dagger2的内容。
project== build.gradle
//project.build.gradle文件
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
module==build.gradle
//module.build.gradle
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
//类似于Java的注解处理器
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
//jdk 1.8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application 类。它是Hilt的一个启动入口。 Application 记得注册。
@HiltAndroidApp
public class ExampleApplication extends Application { ... }
Hilt大幅简化了Dagger2的用法,使得我们不用通过@Component注解去编写桥接层的逻辑,但是也因此限定了注入功能只能从几个Android固定的入口点开始。
Hilt一共支持6个入口点,分别是:
Application
Activity
Fragment
View
Service
BroadcastReceiver
在 Application 类中设置了 Hilt 且有了应用级组件后,Hilt 可以为带有 @AndroidEntryPoint 注释的其他 Android 类提供依赖项:
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }
如果您使用 @AndroidEntryPoint 为某个 Android 类添加注释,则还必须为依赖于该类的 Android 类添加注释
。例如,如果您为某个 Fragment 添加注释,则还必须为使用该 Fragment 的所有 Activity 添加注释。
如需从组件获取依赖项,请使用 @Inject 注释执行字段注入:
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {
//由 Hilt 注入的字段不能为私有字段
@Inject
AnalyticsAdapter analytics;
...
}
@AndroidEntryPoint注入的类的基类不能是抽象类。
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject
User user;
public class User {
String name;
Address address;
@Inject
public User( Address address) {
this.address = address;
}
...
}
public class Address {
@Inject
public Address() {
}
}
MainActivity—》User—>Address
搞明白他们之间的依赖关系。
有些类型不能通过构造函数注入。如接口
Hilt 模块是一个带有 @Module 注释的类。与 Dagger 模块一样,它会告知 Hilt 如何提供某些类型的实例。与 Dagger 模块不同的是,您必须使用 @InstallIn 为 Hilt 模块添加注释,以告知 Hilt 每个模块将用在或安装在哪个 Android 类中。
任何一辆卡车都需要有引擎才可以正常行驶,那么这里我定义一个Engine接口,如下所示
//启用引擎和关闭引擎。
interface Engine {
fun start()
fun shutdown()
}
//电动引擎
public class EleEngine implements Engine{
@Inject
public EleEngine () {
}
@Override
public void start() {
Log.d(TAG, "EleEngine start: ");
}
@Override
public void shutdown() {
Log.d(TAG, "EleEngine shutdown: ");
}
private static final String TAG = "EleEngine";
}
//常规引擎
public class GasEngine implements Engine{
@Inject
public GasEngine() {
}
@Override
public void start() {
Log.d(TAG, "GasEngine start: ");
}
@Override
public void shutdown() {
Log.d(TAG, "GasEngine shutdown: ");
}
private static final String TAG = "GasEngine";
}
创建一个EngineModule 抽象类,使用Module来修饰,说明它是一个模块,或者是一个清单,描述需要完成哪些对象的创建。
@InstallIn 直译按照到哪里,其实是作用到什么地方,传入ActivityCompent.class,作用到Activity对象上。
@Binds 绑定什么对象,注意方法的返回值,抽象函数接收了什么参数,就提供什么实例给它
@Module
@InstallIn(ActivityComponent.class)
public abstract class EngineModule {
//抽象函数接收了什么参数,就提供什么实例给它
@Binds
abstract Engine bindEngine(GasEngine gasEngine);
}
上面Engine是个接口,它的实现类有两个:GasEngine 和EleEngine
假如现在Truck需要两种引擎,燃油引擎和电能引擎,那我们是不是可以直接添加两个方法,每个方法返回不同的对象呢?
此处参考郭霖大神原文,如果直接添加两个方法,会报错。
说Engine的对象绑定了多次。
我们在EngineModule中提供了两个不同的函数,它们的返回值都是Engine。Hilt也搞不清楚。
此时Qualifier注解出场。
1.声明两种注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindElectricEngine {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindGasEngine {
}
//给不同的方法添加不同的注解对象
@Module
@InstallIn(ActivityComponent.class)
public abstract class EngineModule {
@BindGasEngine
@Binds
abstract Engine bindGasEngine( GasEngine gasEngine);
@BindElectricEngine
@Binds
abstract Engine bindEleEngine( EleEngine eleEngine);
}
public class Truck {
@Inject
public Truck() {
}
@BindElectricEngine
@Inject
Engine engine1;
@Inject
@BindGasEngine
Engine engine2;
public void deliver() {
engine1.start();
engine2.start();
Log.d(TAG, "deliver: ---------------");
engine1.shutdown();
engine2.shutdown();
}
private static final String TAG = "Truck";
}
接口不是无法通过构造函数注入类型的唯一一种情况。如果某个类不归您所有
(因为它来自外部库,如 Retrofit、OkHttpClient 或 Room 数据库等类),或者必须使用构建器模式创建实例,也无法通过构造函数注入。
不同的地方在于,这次我们写的不是抽象函数了,而是一个常规的函数。在这个函数中,按正常的写法去创建OkHttpClient的实例,并进行返回即可。
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
}
使用
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var okHttpClient: OkHttpClient
...
}
更多时候我们使用Retrofit,而不是Okhttp,但是我们可能需要添加一些拦截器之类的,所以又需要依赖Okhttp.
我们只需要在刚才的类里添加获取Retrofit对象,且添加参数Okhttp即可。
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://example.com/")
.client(okHttpClient)
.build()
}
然后在Activity中调用Retrofit的变量即可。
对于您可以从中执行字段注入的每个 Android 类,都有一个关联的 Hilt 组件,您可以在 @InstallIn 注释中引用该组件。每个 Hilt 组件负责将其绑定注入相应的 Android 类。Hilt 不会为广播接收器生成组件,因为 Hilt 直接从 ApplicationComponent 注入广播接收器。
@InstallIn(ActivityComponent::class)
,就是把这个模块安装到Activity组件当中。既然是安装到了Activity组件当中,那么自然在Activity中是可以使用由这个模块提供的所有依赖注入实例
。另外,Activity中包含的Fragment和View也可以使用
,但是除了Activity、Fragment、View之外的其他地方就无法使用了
组件名称 | Value |
---|---|
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() | 视图销毁时 |
ViewWithFragmentComponent | View#super() | 视图销毁时 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
默认情况下,Hilt 中的所有绑定都未限定作用域。这意味着,每当应用请求绑定时,Hilt 都会创建所需类型的一个新实例。
简单来讲,就是对某个类声明了某种作用域注解之后,这个注解的箭头所能指到的地方,都可以对该类进行依赖注入,同时在该范围内共享同一个实例。
比如@Singleton注解的箭头可以指向所有地方。而@ServiceScoped注解的箭头无处可指,所以只能限定在Service自身当中使用。@ActivityScoped注解的箭头可以指向Fragment、View当中。
有时候我们的对象构造的时候,需要传入Context,上下文环境。它是系统提供的,无法通过Module模块来获取。Hilt已经帮我们提供了默认的几种context.
@ApplicationContext
@ActivityContext
@Singleton
class Driver @Inject constructor(@ApplicationContext val context: Context) {
}
本文结合多个文章,总结而成,官方文档讲解倒是全面,但是逻辑差,不便于理解。看完诸多大神,心中豁然开朗。尤其郭霖大神,佩服至极。