Android Hilt 框架基础依赖注入模块深入剖析(一)

Android Hilt 框架基础依赖注入模块深入剖析

本人掘金号,欢迎点击关注:掘金号地址

本人公众号,欢迎点击关注:公众号地址

一、引言

在 Android 开发中,依赖注入(Dependency Injection,简称 DI)是一种至关重要的设计模式,它能够显著提升代码的可维护性、可测试性和可扩展性。Android Hilt 框架作为 Google 推出的一款依赖注入库,在 Dagger 的基础上进行了简化和优化,为 Android 开发者提供了更加便捷、高效的依赖注入解决方案。

Hilt 的基础依赖注入模块是整个框架的核心组成部分,它包含了依赖注入的基本概念、核心注解以及实现依赖注入的具体机制。深入理解这个模块,对于开发者充分利用 Hilt 框架来构建高质量的 Android 应用至关重要。

本文将对 Android Hilt 框架的基础依赖注入模块进行全面且深入的源码级别分析。我们将从依赖注入的基本概念入手,逐步介绍 Hilt 中常用的注解,详细解读这些注解背后的源码实现,以及它们如何协同工作来实现依赖注入。同时,我们还会通过具体的示例代码来展示如何在实际项目中使用这些注解和机制。

二、依赖注入基础概念

2.1 什么是依赖注入

依赖注入是一种设计模式,它允许对象在创建时接收其依赖项,而不是在对象内部自行创建这些依赖项。简单来说,就是将对象之间的依赖关系的控制权从对象本身转移到外部。

例如,假设有一个 UserService 类,它依赖于一个 UserRepository 类来获取用户数据。在没有使用依赖注入的情况下,UserService 类可能会在内部自行创建 UserRepository 实例:

kotlin

// UserRepository 类,负责获取用户数据
class UserRepository {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// UserService 类,依赖于 UserRepository
class UserService {
    
    
    // 在内部创建 UserRepository 实例
    private val userRepository = UserRepository()

    fun getUserInfo(): String {
    
    
        return userRepository.getUserData()
    }
}

在上述代码中,UserService 类与 UserRepository 类紧密耦合,这使得代码的可测试性和可维护性变差。如果我们想要替换 UserRepository 的实现,就需要修改 UserService 类的代码。

而使用依赖注入,我们可以将 UserRepository 实例作为参数传递给 UserService 类的构造函数:

kotlin

// UserRepository 类,负责获取用户数据
class UserRepository {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// UserService 类,依赖于 UserRepository
class UserService(private val userRepository: UserRepository) {
    
    
    fun getUserInfo(): String {
    
    
        return userRepository.getUserData()
    }
}

// 使用依赖注入创建 UserService 实例
val userRepository = UserRepository()
val userService = UserService(userRepository)

通过这种方式,UserService 类与 UserRepository 类的耦合度降低,代码的可测试性和可维护性得到了提高。

2.2 依赖注入的好处

  • 提高可测试性:在测试时,可以轻松地替换依赖项的实现,使用模拟对象来进行测试,从而隔离测试目标对象,提高测试的准确性和效率。
  • 增强可维护性:将依赖关系的控制权从对象内部转移到外部,使得代码结构更加清晰,修改依赖项的实现时不需要修改依赖该对象的代码。
  • 促进代码复用:依赖项可以被多个对象共享和复用,减少了代码的重复。

三、Hilt 基础依赖注入模块概述

3.1 Hilt 简介

Hilt 是一个基于 Dagger 的依赖注入库,它简化了在 Android 应用中使用依赖注入的过程。Hilt 通过注解和编译时生成代码的方式,自动处理依赖注入的细节,使得开发者可以更加专注于业务逻辑的实现。

3.2 基础依赖注入模块的作用

Hilt 的基础依赖注入模块提供了实现依赖注入的基本机制和工具,包括定义依赖项、提供依赖项的实例以及将依赖项注入到目标对象中。通过使用这个模块,开发者可以方便地在 Android 应用中实现依赖注入。

3.3 常用注解介绍

3.3.1 @Inject 注解

@Inject 注解用于标记一个类的构造函数、字段或方法,表示这些元素需要进行依赖注入。

  • 构造函数注入:当 @Inject 注解用于构造函数时,Hilt 会自动创建该类的实例,并将其依赖项注入到构造函数中。

kotlin

// 标记构造函数需要进行依赖注入
class UserRepository @Inject constructor() {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// 标记构造函数需要进行依赖注入,依赖于 UserRepository
class UserService @Inject constructor(private val userRepository: UserRepository) {
    
    
    fun getUserInfo(): String {
    
    
        return userRepository.getUserData()
    }
}

在上述代码中,UserRepositoryUserService 的构造函数都使用了 @Inject 注解,Hilt 会自动创建 UserRepository 实例,并将其注入到 UserService 的构造函数中。

  • 字段注入:当 @Inject 注解用于字段时,Hilt 会在对象创建后,将依赖项注入到该字段中。

kotlin

// 标记构造函数需要进行依赖注入
class UserRepository @Inject constructor() {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// 使用字段注入,依赖于 UserRepository
class UserService {
    
    
    // 标记字段需要进行依赖注入
    @Inject lateinit var userRepository: UserRepository

    fun getUserInfo(): String {
    
    
        return userRepository.getUserData()
    }
}

在上述代码中,UserService 类的 userRepository 字段使用了 @Inject 注解,Hilt 会在 UserService 对象创建后,将 UserRepository 实例注入到该字段中。

3.3.2 @Module 注解

@Module 注解用于定义一个模块,模块是一组提供依赖项的方法的集合。当某些依赖项无法通过 @Inject 注解的构造函数来创建时,可以使用模块来提供这些依赖项的实例。

kotlin

import dagger.Module
import dagger.Provides

// 定义一个模块,用于提供依赖项
@Module
class AppModule {
    
    
    // 提供 UserRepository 实例的方法
    @Provides
    fun provideUserRepository(): UserRepository {
    
    
        return UserRepository()
    }
}

在上述代码中,AppModule 类使用了 @Module 注解,其中的 provideUserRepository 方法使用了 @Provides 注解,用于提供 UserRepository 实例。

3.3.3 @Provides 注解

@Provides 注解用于标记模块中的方法,表示该方法用于提供一个依赖项的实例。方法的返回类型即为所提供的依赖项的类型。

kotlin

import dagger.Module
import dagger.Provides

// 定义一个模块,用于提供依赖项
@Module
class AppModule {
    
    
    // 提供 UserRepository 实例的方法
    @Provides
    fun provideUserRepository(): UserRepository {
    
    
        return UserRepository()
    }
}

在上述代码中,provideUserRepository 方法使用了 @Provides 注解,该方法返回一个 UserRepository 实例,Hilt 会使用这个方法来提供 UserRepository 依赖项。

3.3.4 @Singleton 注解

@Singleton 注解用于标记一个类或提供依赖项的方法,表示该类的实例或方法提供的依赖项实例在整个应用的生命周期内是单例的。

kotlin

import javax.inject.Singleton
import dagger.Module
import dagger.Provides

// 标记 UserRepository 为单例
@Singleton
class UserRepository @Inject constructor() {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// 定义一个模块,用于提供依赖项
@Module
class AppModule {
    
    
    // 提供单例的 UserRepository 实例的方法
    @Provides
    @Singleton
    fun provideUserRepository(): UserRepository {
    
    
        return UserRepository()
    }
}

在上述代码中,UserRepository 类和 provideUserRepository 方法都使用了 @Singleton 注解,这意味着 UserRepository 实例在整个应用的生命周期内只会创建一次。

四、@Inject 注解源码分析

4.1 @Inject 注解的定义

@Inject 注解的定义位于 javax.inject 包中,其源码如下:

java

package javax.inject;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个构造函数、字段或方法,表示这些元素需要进行依赖注入。
 */
@Target({
    
     CONSTRUCTOR, FIELD, METHOD })
@Retention(RUNTIME)
@Documented
public @interface Inject {
    
    }

从源码可以看出,@Inject 注解可以应用于构造函数、字段和方法,并且其保留策略为 RUNTIME,这意味着在运行时可以通过反射获取该注解的信息。

4.2 构造函数注入的实现原理

@Inject 注解用于构造函数时,Hilt 在编译时会生成相应的代码来创建该类的实例,并将其依赖项注入到构造函数中。下面我们通过一个简单的示例来分析其实现原理。

假设我们有以下两个类:

kotlin

// 标记构造函数需要进行依赖注入
class UserRepository @Inject constructor() {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// 标记构造函数需要进行依赖注入,依赖于 UserRepository
class UserService @Inject constructor(private val userRepository: UserRepository) {
    
    
    fun getUserInfo(): String {
    
    
        return userRepository.getUserData()
    }
}

在编译时,Hilt 会生成一个工厂类来创建 UserService 实例。这个工厂类的大致结构如下:

java

// 生成的 UserService 工厂类
public final class UserService_Factory implements Factory<UserService> {
    
    
    // 保存 UserRepository 的提供者
    private final Provider<UserRepository> userRepositoryProvider;

    // 构造函数,接收 UserRepository 的提供者
    public UserService_Factory(Provider<UserRepository> userRepositoryProvider) {
    
    
        this.userRepositoryProvider = userRepositoryProvider;
    }

    // 创建 UserService 实例的方法
    @Override
    public UserService get() {
    
    
        // 通过提供者获取 UserRepository 实例,并创建 UserService 实例
        return new UserService(userRepositoryProvider.get());
    }

    // 创建工厂实例的方法
    public static UserService_Factory create(Provider<UserRepository> userRepositoryProvider) {
    
    
        return new UserService_Factory(userRepositoryProvider);
    }
}

在上述代码中,UserService_Factory 类实现了 Factory 接口,用于创建 UserService 实例。userRepositoryProvider 是一个 Provider 对象,用于提供 UserRepository 实例。在 get 方法中,通过 userRepositoryProvider.get() 获取 UserRepository 实例,并将其传递给 UserService 的构造函数来创建 UserService 实例。

4.3 字段注入的实现原理

@Inject 注解用于字段时,Hilt 在编译时会生成相应的代码来将依赖项注入到该字段中。下面我们通过一个示例来分析其实现原理。

假设我们有以下类:

kotlin

// 标记构造函数需要进行依赖注入
class UserRepository @Inject constructor() {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// 使用字段注入,依赖于 UserRepository
class UserService {
    
    
    // 标记字段需要进行依赖注入
    @Inject lateinit var userRepository: UserRepository

    fun getUserInfo(): String {
    
    
        return userRepository.getUserData()
    }
}

在编译时,Hilt 会生成一个注入器类来进行字段注入。这个注入器类的大致结构如下:

java

// 生成的 UserService 注入器类
public final class UserService_GeneratedInjector {
    
    
    // 注入 UserService 实例的方法
    public static void injectUserRepository(UserService instance, Provider<UserRepository> userRepositoryProvider) {
    
    
        // 通过提供者获取 UserRepository 实例,并注入到 UserService 的字段中
        instance.userRepository = userRepositoryProvider.get();
    }
}

在上述代码中,UserService_GeneratedInjector 类的 injectUserRepository 方法用于将 UserRepository 实例注入到 UserServiceuserRepository 字段中。通过 userRepositoryProvider.get() 获取 UserRepository 实例,并将其赋值给 UserServiceuserRepository 字段。

五、@Module@Provides 注解源码分析

5.1 @Module 注解的定义

@Module 注解的定义位于 dagger 包中,其源码如下:

java

package dagger;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个类为模块,模块是一组提供依赖项的方法的集合。
 */
@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface Module {
    
    
    /**
     * 定义模块的子模块。
     */
    Class<?>[] includes() default {
    
    };

    /**
     * 定义模块是否为静态模块。
     */
    boolean includesStatic() default false;
}

从源码可以看出,@Module 注解可以应用于类,并且其保留策略为 RUNTIMEincludes 属性用于定义模块的子模块,includesStatic 属性用于定义模块是否为静态模块。

5.2 @Provides 注解的定义

@Provides 注解的定义位于 dagger 包中,其源码如下:

java

package dagger;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记模块中的方法,表示该方法用于提供一个依赖项的实例。
 */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface Provides {
    
    }

从源码可以看出,@Provides 注解可以应用于方法,并且其保留策略为 RUNTIME

5.3 模块和提供方法的实现原理

当使用 @Module@Provides 注解时,Hilt 在编译时会生成相应的代码来处理模块和提供方法。下面我们通过一个示例来分析其实现原理。

假设我们有以下模块类:

kotlin

import dagger.Module
import dagger.Provides

// 定义一个模块,用于提供依赖项
@Module
class AppModule {
    
    
    // 提供 UserRepository 实例的方法
    @Provides
    fun provideUserRepository(): UserRepository {
    
    
        return UserRepository()
    }
}

在编译时,Hilt 会生成一个组件类来处理模块中的提供方法。这个组件类的大致结构如下:

java

// 生成的 AppModule 组件类
public final class AppModule_ProvideUserRepositoryFactory implements Factory<UserRepository> {
    
    
    // 保存 AppModule 实例
    private final AppModule module;

    // 构造函数,接收 AppModule 实例
    public AppModule_ProvideUserRepositoryFactory(AppModule module) {
    
    
        this.module = module;
    }

    // 创建 UserRepository 实例的方法
    @Override
    public UserRepository get() {
    
    
        // 调用 AppModule 的 provideUserRepository 方法创建 UserRepository 实例
        return provideUserRepository(module);
    }

    // 调用 AppModule 的 provideUserRepository 方法的静态方法
    public static UserRepository provideUserRepository(AppModule instance) {
    
    
        return instance.provideUserRepository();
    }

    // 创建工厂实例的方法
    public static AppModule_ProvideUserRepositoryFactory create(AppModule module) {
    
    
        return new AppModule_ProvideUserRepositoryFactory(module);
    }
}

在上述代码中,AppModule_ProvideUserRepositoryFactory 类实现了 Factory 接口,用于创建 UserRepository 实例。moduleAppModule 的实例,在 get 方法中,调用 AppModuleprovideUserRepository 方法来创建 UserRepository 实例。

六、@Singleton 注解源码分析

6.1 @Singleton 注解的定义

@Singleton 注解的定义位于 javax.inject 包中,其源码如下:

java

package javax.inject;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 标记一个类或提供依赖项的方法,表示该类的实例或方法提供的依赖项实例在整个应用的生命周期内是单例的。
 */
@Target({
    
     TYPE, METHOD })
@Retention(RUNTIME)
@Documented
public @interface Singleton {
    
    }

从源码可以看出,@Singleton 注解可以应用于类和方法,并且其保留策略为 RUNTIME

6.2 单例实现的原理

当使用 @Singleton 注解时,Hilt 在编译时会生成相应的代码来确保依赖项实例在整个应用的生命周期内是单例的。下面我们通过一个示例来分析其实现原理。

假设我们有以下类和模块:

kotlin

import javax.inject.Singleton
import dagger.Module
import dagger.Provides

// 标记 UserRepository 为单例
@Singleton
class UserRepository @Inject constructor() {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

// 定义一个模块,用于提供依赖项
@Module
class AppModule {
    
    
    // 提供单例的 UserRepository 实例的方法
    @Provides
    @Singleton
    fun provideUserRepository(): UserRepository {
    
    
        return UserRepository()
    }
}

在编译时,Hilt 会生成一个单例组件类来管理单例实例。这个单例组件类的大致结构如下:

java

// 生成的单例组件类
public final class SingletonComponent {
    
    
    // 保存 UserRepository 单例实例
    private static final UserRepository userRepositoryInstance;

    static {
    
    
        // 创建 UserRepository 单例实例
        userRepositoryInstance = AppModule_ProvideUserRepositoryFactory.create(new AppModule()).get();
    }

    // 获取 UserRepository 单例实例的方法
    public static UserRepository getUserRepository() {
    
    
        return userRepositoryInstance;
    }
}

在上述代码中,SingletonComponent 类使用静态变量 userRepositoryInstance 来保存 UserRepository 单例实例。在静态代码块中,通过 AppModule_ProvideUserRepositoryFactory 类创建 UserRepository 实例,并将其赋值给 userRepositoryInstancegetUserRepository 方法用于获取 UserRepository 单例实例。

七、在 Android 应用中使用 Hilt 基础依赖注入模块

7.1 添加 Hilt 依赖

在项目的 build.gradle 文件中添加 Hilt 依赖:

groovy

// 添加 Hilt 插件
plugins {
    
    
    id 'com.google.dagger.hilt.android' version '2.44' apply true
}

// 添加 Hilt 核心库依赖
implementation 'com.google.dagger:hilt-android:2.44'
// 添加 Hilt 编译器依赖
kapt 'com.google.dagger:hilt-android-compiler:2.44'

7.2 配置应用类

在应用的 Application 类上添加 @HiltAndroidApp 注解:

kotlin

import dagger.hilt.android.HiltAndroidApp
import android.app.Application

// 标记应用类,让 Hilt 初始化
@HiltAndroidApp
class MyApp : Application() {
    
    
    // 应用启动时调用
    override fun onCreate() {
    
    
        super.onCreate()
        // 可以在这里进行一些初始化操作
    }
}

7.3 创建依赖类和模块

创建依赖类和模块,例如:

kotlin

import javax.inject.Inject

// 标记构造函数需要进行依赖注入
class UserRepository @Inject constructor() {
    
    
    fun getUserData(): String {
    
    
        return "User data"
    }
}

import dagger.Module
import dagger.Provides
import javax.inject.Singleton

// 定义一个模块,用于提供依赖项
@Module
@InstallIn(SingletonComponent::class)
class AppModule {
    
    
    // 提供单例的 UserRepository 实例的方法
    @Provides
    @Singleton
    fun provideUserRepository(): UserRepository {
    
    
        return UserRepository()
    }
}

7.4 在 Activity 中进行依赖注入

Activity 中使用 @AndroidEntryPoint 注解,并进行依赖注入:

kotlin

import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

// 标记 Activity,让 Hilt 进行注入
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    
    // 通过字段注入获取 UserRepository 实例
    @Inject lateinit var userRepository: UserRepository

    // Activity 创建时调用
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 使用 UserRepository 获取用户数据
        val userData = userRepository.getUserData()
        // 可以在这里处理用户数据
    }
}

八、总结与展望

8.1 总结

本文深入剖析了 Android Hilt 框架的基础依赖注入模块,详细介绍了依赖注入的基本概念、Hilt 中常用的注解(@Inject@Module@Provides@Singleton)以及它们的源码实现。通过对这些注解的源码分析,我们了解了 Hilt 是如何实现依赖注入的,包括构造函数注入、字段注入、模块和提供方法的处理以及单例的实现。

同时,我们还通过具体的示例代码展示了如何在 Android 应用中使用 Hilt 的基础依赖注入模块,包括添加依赖、配置应用类、创建依赖类和模块以及在 Activity 中进行依赖注入。

8.2 展望

随着 Android 开发技术的不断发展,Hilt 框架也将不断完善和优化。未来,我们可以期待 Hilt 在以下方面取得进一步的发展:

  • 性能优化:进一步优化 Hilt 的编译时生成代码的性能,减少编译时间和生成代码的体积。

  • 更多注解和功能:提供更多的注解和功能,以满足开发者在不同场景下的需求,例如支持更多的作用域和注入方式。

  • 更好的与其他 Android 组件集成:与 Android 生态系统中的其他组件(如 Jetpack Compose、Room 等)更好地集成,提供更加便捷的开发体验。

总之,Hilt 框架的基础依赖注入模块为 Android 开发者提供了一种高效、便捷的方式来实现依赖注入。通过深入理解其原理和使用方法,开发者可以更好地利用 Hilt 框架来构建高质量的 Android 应用。在未来的开发中,我们可以期待 Hilt 框架在依赖注入领域发挥更大的作用。
=【