Jetpack -Hilt 全面使用和解析

1.概念

Hilt (刀把)是 Android 的依赖项注入库,专门面向Android的依赖注入框架。可减少在项目中执行手动依赖项注入的样板代码。执行手动依赖项注入要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。

Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法

依赖注入的英文名是Dependency Injection,简称DI。DI的最大作用就是解耦。本质其实在开发程序这块模块清晰,底层还是需要代码逻辑来去支撑的,只不过通过这种技术把一些依赖关系给封装了而已。
本文结合众多网上文档,取精华和重点。

1.1 MVVM

目前很多项目使用了众多MVP模式,后期又提出使用MVVM模式来搭建项目。Android在复杂项目中,对象之间的依赖关系也十分可怕,所以Android如果依赖注入也能使项目结构更清晰。
在这里插入图片描述
谷歌推荐MVVM 架构来搭建Android应用。

  • Activity/Fragment属于UI,View层
  • ViewModel属于Viewmodel层,是View 和Model的交互的桥梁。和MVP模式很类似。
  • Reponsitory属于Model层,主要提供数据来源,主要以网络和数据库为主。

1.2 Dagger2

Dagger(匕首)Dagger2也是依赖注入的框架,国内使用的不是太多。Dagger1前期是Square维护,主要以反射技术实现,但是反射很性能方面的问题。后期Google开始维护Dagger2,使用编译时期生成对象之间的各种依赖关系,这样编译期就可以知道报错信息,性能更好。Hilt是基于Dagger2之上做的进一步升级,所以学习Hilt最好学习下Dagger2的内容。

2 使用

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
  }

1、@HiltAndroidApp

所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application 类。它是Hilt的一个启动入口。 Application 记得注册。

@HiltAndroidApp
public class ExampleApplication extends Application {
    
     ... }

Hilt大幅简化了Dagger2的用法,使得我们不用通过@Component注解去编写桥接层的逻辑,但是也因此限定了注入功能只能从几个Android固定的入口点开始。

Hilt一共支持6个入口点,分别是:

Application
Activity
Fragment
View
Service
BroadcastReceiver

2、@AndroidEntryPoint、@Inject

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

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {
    
     ... }

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

  • Hilt 仅支持扩展 ComponentActivity 的 Activity,如 AppCompatActivity。
  • Hilt 仅支持扩展 androidx.Fragment 的 Fragment。
  • Hilt 不支持保留的 Fragment。

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

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {
    
    
	//由 Hilt 注入的字段不能为私有字段
  @Inject
  AnalyticsAdapter analytics;
  ...
}

@AndroidEntryPoint注入的类的基类不能是抽象类。

3、 带参数的@Inject

@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
搞明白他们之间的依赖关系。

3.Hilt 模块 @Module–@InstallIn

有些类型不能通过构造函数注入。如接口

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);
}

结果:
在这里插入图片描述

1.给相同类型注入不同的实例 Qualifier

上面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";
}

结果:
在这里插入图片描述

2. 第三方类的依赖注入

接口不是无法通过构造函数注入类型的唯一一种情况。如果某个类不归您所有(因为它来自外部库,如 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的变量即可。

3.为 Android 类生成的组件

对于您可以从中执行字段注入的每个 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

4.组件生命周期

项目 开始 结束
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()

5.组件作用域

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

6.组件层次结构

在这里插入图片描述
简单来讲,就是对某个类声明了某种作用域注解之后,这个注解的箭头所能指到的地方,都可以对该类进行依赖注入,同时在该范围内共享同一个实例。

比如@Singleton注解的箭头可以指向所有地方。而@ServiceScoped注解的箭头无处可指,所以只能限定在Service自身当中使用。@ActivityScoped注解的箭头可以指向Fragment、View当中。

7.预置Qualifier

有时候我们的对象构造的时候,需要传入Context,上下文环境。它是系统提供的,无法通过Module模块来获取。Hilt已经帮我们提供了默认的几种context.

@ApplicationContext
@ActivityContext

@Singleton
class Driver @Inject constructor(@ApplicationContext val context: Context) {
    
    
}

4 最后

本文结合多个文章,总结而成,官方文档讲解倒是全面,但是逻辑差,不便于理解。看完诸多大神,心中豁然开朗。尤其郭霖大神,佩服至极。

猜你喜欢

转载自blog.csdn.net/chentaishan/article/details/119354735