Android Hilt 使用

Posted microhex

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Hilt 使用相关的知识,希望对你有一定的参考价值。

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的创建依赖了BB的创建同时依赖了CD对象。 这还是一个比较直观简单的依赖关系,在随着我们项目功能的越来越多,越来越复杂,那么对象之间的相互的依赖就会越来越复杂,为了解决这个问题,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日),至于其它的像什么 OKHttpGlidePhotoView啥的,这里都能查找最新的版本.

在根级目录完成之后,在 app/build.gradle 下添加插件依赖

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"   //kotlin
	
    // implementation "com.google.dagger:hilt-android:2.28-alpha"  
    // annotationProcessor "com.google.dagger:hilt-android-compiler:2.28-alpha"   //java
	
}

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 注解
    @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提供以下信息:

  1. 函数返回类型会告之 Hilt 函数提供哪个接口的实例;
  2. 函数参数会告之Hilt要提供哪种实现。

此时如果存在两个实现,MyServiceImpl1MyServiceImpl2 分别要注入到 Activity中,应该如何区分和对应呢?

@AndroidEntryPoint
public class HiltUI extends AppCompatActivity {

    @Inject MyService myService1;   // 初始化对应 MyServiceImpl1

	@Inject MyService myService2;   // 初始化对应 MyServiceImpl2
}

这里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;   // 初始化对应 MyServiceImpl1 对应 SimpleServiceQualifier

	@Inject 
	@AdvanceServiceQualifier
	MyService myService2;   // 初始化对应 MyServiceImpl2  对应 AdvanceServiceQualifier
}

4. 如果注入的对象不能new

这个应该很常见了,我们在Android开发中我们常用的RetrofitOkHttpClientRoom数据库都是通过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组件注入面向的对象创建时机销毁时机作用域
ApplicationComponentApplicationApplication#onCreate()Application#onDestory()@Singleton
ActivityRetainedComponentViewModelActivity#onCreate()Activity#onDestroy()@ActivityRetainScoped
ActivityComponentActivityActivity#onCreate()Activity#onDestroy()@ActivityScoped
FragmentComponentFragmentFragment#onAttach()Fragment#onDestory()@FragmentScoped
ViewComponentViewView#super()视图销毁时@ViewScoped
带有@withFragmentBindingsViewWithFragmentComponentView#super()视图销毁时@ViewScoped
ServiceComponentServiceComponentService#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. 资料

  1. https://developer.android.com/training/dependency-injection/hilt-android#predefined-qualifiers。

以上是关于Android Hilt 使用的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 中使用 Hilt 的开发实践

带 Hilt 的活动片段通信

Hilt 稳定版发布 | 更便捷的 Android 依赖项注入

在 Android 中通过 Hilt 进行依赖项注入

Android Hilt 使用

Android Hilt 使用