a. 关于 Hilt
首先 Hilt 是 Android 的依赖注入库。什么是依赖注入?简单点理解就是Java中我们使用对象时,需要去 new一个对象,或者使用 builder模式去构建一个对象,无论那些模式,其目标都是从无到有去创建一个对象。但是在创建对象的过程中,可能存在多个依赖,比如像下面这种情况:
C c = new C();
D d = new D();
B b = new B(C,D);
A a = new A(B);
我们在创建A 对象的过程中,需要初始化B 对象,但是初始化B 对象时,需要同时初始化C 对象和D 对象,也就是说A 的创建依赖了B ,B 的创建同时依赖了C 和D 对象。 这还是一个比较直观简单的依赖关系,在随着我们项目功能的越来越多,越来越复杂,那么对象之间的相互的依赖就会越来越复杂,为了解决这个问题,Google就开发出了Hilt ,旨在解决减少项目中执行手动依赖注入(自己去new对象的过程)和样板代码,并且它还可以借助容器的来重复使用对象。
因为Hilt主要是为Android项目开发的,因此它为项目中每个Android类提供容器并自动管理其生命周期,提供了一种在应用中使用DI(依赖注入)的标准方法。
b. 添加依赖项
项目根级build.gradle 添加 hilt-android-gradle-pugin 配置:
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
当然,很多人肯定对这个版本号的问题很纠结,这个版本号到底哪里查呢,是不是最新的呢?我这里提供一个地址,基本上可以查相关所有的jar的版本: Maven Repository 你可以看到,最新的版本号已经到了 2.38.1 (今天是2021年8月14日),至于其它的像什么 OKHttp ,Glide ,PhotoView 啥的,这里都能查找最新的版本.
在根级目录完成之后,在 app/build.gradle 下添加插件依赖
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
android {
...
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"
}
c. Hilt 应用
1. HiltAndroidApp 注解
这个注解是需要要加的,可以理解为Hilt的启动项,它会触发Hilt的代码生成,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。而且需要添加到 Application 头上:
@HiltAndroidApp
public class App extends Application {
}
2. 注入Android类
我们所说的Android类,并不是在Android项目中的Class类,而是带有Android特性的Class,分别有以下几种:
- Application (通过@HiltAndroidApp)
- ComponentActivity, 并非所有的Activity,只是 ComponentActivity的子类,比如AppCompatActivity
- androidx.Fragment, android.app.Fragment不支持
- View
- Service
- BroadcastReceiver
对于这些Android类,我们可以使用 @AndroidEntryPoint 进行注入, 比如这样:
@AndroidEntryPoint
public class HiltUI extends AppCompatActivity {
@Inject DataCreator mDataCreator;
}
我们在 HiltUI 这个Activity中使用了 @AndroidEntryPoint 注解, 同时我们还看到了一个新的DataCreator 类,被一个陌生的 @Inject 注解修饰, 这个是什么意思呢?
我们先定义一下 DataCreator 这个类:
public class DataCreator {
public String name ;
public DataCreator() {
name = "hello world" ;
}
@NonNull
@Override
public String toString() {
return "DataCreator{" +
"name='" + name + '\'' +
'}';
}
}
如果我把上面的代码改成这样:
public class HiltUI extends AppCompatActivity {
DataCreator mDataCreator = new DataCreator();
}
这样就很容易理解了,为了实现 new 一个 DataCreator 的效果, 为了简单,我们使用了一个 @Inject 注解,从组件中获取依赖项。
直接使用:
@Inject DataCreator mDataCreator;
就可以直接 new 一个 DataCreator 吗?当然不是,对于 DataCreator 还需要做一下更改:
public class DataCreator {
public String name ;
@Inject
public DataCreator() {
name = "hello world" ;
}
@NonNull
@Override
public String toString() {
return "DataCreator{" +
"name='" + name + '\'' +
'}';
}
}
只需要更改一下 DataCreator 的构造函数,同样添加一个 @Inject 即可。
3. 如果注入接口
假如存在一个接口 MyService :
public interface MyService {
String giveMeName();
}
在 HiltUI 中注入:
@AndroidEntryPoint
public class HiltUI extends AppCompatActivity {
@Inject MyService myService;
}
对于接口,一般的套路是要找到其实现类,然后再进行注入。首先我们来一个实现类:
public class MyServiceImpl implements MyService{
@Inject
public MyServiceImpl(){}
@Override
public String giveMeName() {
return "MyServiceImpl give you name";
}
}
现在接口有了,实现类也存在,二者应该怎样去关联呢?Hilt为我们提供一个 @Bind 注解,Bind字面的意思就是绑定,那么我们看一下怎么绑定了:
@Module
@InstallIn(ActivityComponent.class)
public abstract class MyServiceModule {
@Binds
public abstract MyService bindMyService(MyServiceImpl myService);
}
首先 @Module 是告知 Hilt 如何提供某些类型的实例,它需要配合 @InstallIn 注解使用,以告知Hilt每个模块将用或者安装在哪个Android类中。这里的ActivityComponent表明需要注入到Activity中,意味着所有的Activity中都可以使用到这个模块。
同时,@Binds 注解将会告知 Hilt 在需要提供接口的实例时需要哪种实现。 public abstract MyService bindMyService(MyServiceImpl myService) 这个方法将会为 Hilt提供以下信息:
- 函数返回类型会告之 Hilt 函数提供哪个接口的实例;
- 函数参数会告之Hilt要提供哪种实现。
此时如果存在两个实现,MyServiceImpl1 和 MyServiceImpl2 分别要注入到 Activity中,应该如何区分和对应呢?
@AndroidEntryPoint
public class HiltUI extends AppCompatActivity {
@Inject MyService myService1;
@Inject MyService myService2;
}
这里Hilt提供了一个一种自定义限定符的方案: 自定义两个注解:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface SimpleServiceQualifier {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface AdvanceServiceQualifier {}
对于自定义的 @SimpleServiceQualifier 和 @AdvanceServiceQualifier 两个自定义的注解,其作用来自Hilt 提供的 @Qualifier ,用于某个类型定义了多个绑定时,用来标识特定的绑定。那么应该这么做:
@Module
@InstallIn(ActivityComponent.class)
public abstract class MyServiceModule {
@Binds
@SimpleServiceQualifier
public abstract MyService bindMyService(MyServiceImpl1 myService);
@Binds
@AdvanceServiceQualifier
public abstract MyService bindMyService(MyServiceImpl2 myService);
}
定义并标识了MyService的两种实现之后,我们在 HiltUI 中同样需要使用两种限定符对注入有相关的对应:
public class HiltUI extends AppCompatActivity {
@Inject
@SimpleServiceQualifier
MyService myService1;
@Inject
@AdvanceServiceQualifier
MyService myService2;
}
4. 如果注入的对象不能new
这个应该很常见了,我们在Android开发中我们常用的Retrofit 、OkHttpClient 和Room 数据库都是通过Builder模式构建的,那么此时如果正常的注入,应该怎么做呢?Hilt提供了@Provides 注解:
@Module
@InstallIn(ActivityComponent.class)
public abstract class MyServiceModule {
@Provides
public static MyService provideMyService() {
return new Retrofit.Builder().
baseUrl("https://example.com").
build().
create(MyService.class);
}
}
5. @ApplicationContext 和 @ActivityContext
在注入时,我们可能会需要使用到Application或者Activity,Hilt非常贴心的为我们提供了@ApplicationContext 和 @ActivityContext , 使用时非常简单,直接添加注解即可:
@Inject
public DataCreator(@ActivityContext Context context) {
name = "hello world" ;
}
6. Android类生成组件
在上面的例子中,我们使用到一个 @InstallIn 的注解,Hilt组件通过这个注解将其绑定注入相应的Android类中;因为是Android类,同时也会存在作用域的问题,那么依次表现为:
Hilt组件 | 注入面向的对象 | 创建时机 | 销毁时机 | 作用域 |
---|
ApplicationComponent | Application | Application#onCreate() | Application#onDestory() | @Singleton | ActivityRetainedComponent | ViewModel | Activity#onCreate() | Activity#onDestroy() | @ActivityRetainScoped | ActivityComponent | Activity | Activity#onCreate() | Activity#onDestroy() | @ActivityScoped | FragmentComponent | Fragment | Fragment#onAttach() | Fragment#onDestory() | @FragmentScoped | ViewComponent | View | View#super() | 视图销毁时 | @ViewScoped | 带有@withFragmentBindings | ViewWithFragmentComponent | View#super() | 视图销毁时 | @ViewScoped | ServiceComponent | ServiceComponent | Service#onCreate() | Service#onDestory() | @ServiceScoped |
了解这些很奇怪的特性有什么用呢?其实我刚开始学的时候也存在这个问题,当然现在我可能也不是特别能描述其作用,先通过一些例子来聊聊其功能吧。
i. 全局单例模式
假如我们需要在Application中存在单例对象UniqueEntity ,可以使用下面方法实现:
public class UniqueEntity {
public UniqueEntity(){
Log.d("TAG","I am the unique entity");
}
}
生成单例对象:
@Module
@InstallIn(ApplicationComponent.class)
public class UniqueEntityProvider {
@Provides
@Singleton
public UniqueEntity provideUniqueEntity(){
return new UniqueEntity();
}
}
@InstallIn(ApplicationComponent.class) 表明是在整个Application 周期内都提供 UniqueEntity 实例,同时使用 @Singleon 限定了整个生命周期内只存在唯一一个 UniqueEntity 实例。当然如果不存在这个 @Singleon , 那么在整个 Application中,@Inject 一个 UniqueEntity ,就会生成一个新的UniqueEntity 实例。
ii. Activity内多Fragment共用组件
上图中,开发中比较常见的模式 Activity中 TabLayout + ViewPager 模式,ViewPager中是Fragment , 此时如果我想要下面的四个Fragment 共用同一个对象实例,应该怎么做呢?如果没有用 Hilt ,其实也是蛮简单的,因为四个 Fragment 都同时共用一个 Activity ,在Activity 中定义该对象就可以了,然后在Framgent 需要的时候再去拿,这样不是不可以,但是不够简单直接,同时不够骚气,看看Hilt是如果实现的。
定义对象实例 ActivityEntityTwo :
public class ActivityEntityTwo {
public ActivityEntityTwo(){}
}
编写注入代码:
@Module
@InstallIn(ActivityComponent.class)
public class CreateActivityModule {
@Provides
@ActivityScoped
public static ActivityEntityTwo findActivityEntityTwo() {
return new ActivityEntityTwo();
}
}
这里有两点需要说明一下,@InstallIn(ActivityComponent.class) 说明是在Activity生命周期内提供的实例的,并且在Activity生命周期内只提供ActivityEntityTwo 的同一个实例, 那么在 MyFragment 中可以这么写:
@AndroidEntryPoint
public class MyFragment extends Fragment {
@Inject
ActivityEntityTwo entityTwo;
}
因为 MyFragment的生命周期其实被 Activity的生命周期覆盖了(因为 MyFragment 其实内嵌在Activity中了),那么多个MyFragment 在初始化的过程中,entityTwo 始终只存在一份,那么多个Fragment 同时共享了同一个 entityTwo 了。
当然了,FragmentComponent 是表明在 Fragment 的生命周期只内存在同一个实例;ViewComponent 是表明在 View 的生命周期内只存在同一个实例;ServiceComponent 是表明在 Service 的生命周期内只存在同一个实例。
在使用的过程中,我对其生命周期的理解是这样的,Andorid作为嵌入式系统,系统资源永远都是宝贵的,我们需要准确的控制对象的创建和回收,才能保证资源被合理的使用。因此对组件的作用域有足够的了解有助于对资源的准确控制,当然了,这一切的控制我们都交给了Hitl组件,我们只需要掌握这些注解即可。
这篇文章会随着工作的需要、对Hilt的理解逐步更新,可能还只是使用到它的一部分功能,对很多额外的功能还不是特别的理解。用到的时候,再回来填坑吧。
d. 资料
- https://developer.android.com/training/dependency-injection/hilt-android#predefined-qualifiers。
|