Android Hilt:强大的依赖注入框架,高级传参解个耦?

2,529 阅读13分钟

一、Hilt 干依赖注入的

Hilt是干嘛的

Hilt,一个依赖注入框架,谷歌搞出来的,基于Dagger,但是更加简洁易用。

什么是依赖注入 (Dependency Injection)

依赖注入是一种设计模式。主要就是解耦!解耦!解耦!。

依赖注入是一种设计模式,它通过在对象之间传递依赖关系,使得对象能够更灵活、可测试和可维护。在Android开发中,依赖注入可以帮助我们管理和解耦应用程序中的各种组件,如Activity、Fragment、ViewModel、Repository等。

既生Dagger 何生 Hilt ?

安卓中的Hilt是一种用于依赖注入(Dependency Injection)的库。Hilt是由Google开发的,它是基于Dagger的一个扩展库,旨在简化Android应用程序中的依赖注入过程。

Hilt提供了一套注解和生成代码,用于在Android应用程序中自动完成依赖注入的配置。它简化了Dagger的使用,减少了样板代码的编写,提高了开发效率。

Hilt还提供了一些特定于Android的功能,如对Android组件的注入支持,以及在不同生命周期中管理依赖关系的能力

Hilt 大概怎么用

  • Application 注解下
  • 在需要的标记为依赖注入的Activity、Fragment注解下 @AndroidEntryPoint

使用Hilt进行依赖注入时,首先需要在应用程序的Application类上添加@HiltAndroidApp注解,以告知Hilt该应用程序将使用Hilt进行依赖注入。然后,可以在其他组件(如Activity、Fragment)中使用@AndroidEntryPoint注解来标记需要进行依赖注入的地方。Hilt会根据注解生成必要的代码,在运行时完成依赖注入。

Hilt还提供了一些其他的注解,用于标记和配置依赖关系。例如,

  • 可以使用@Inject注解来标记需要注入的依赖项,
  • 使用@Module注解来标记用于提供依赖项的模块类,
  • 使用@Provides注解来标记提供依赖项的方法等。

二、什么是TM的依赖注入,给个例子

比如我们创建一辆汽车,汽车需要引擎。

  • 如果不用依赖注入,创建汽车,汽车内部还要创建引擎,这样就 耦合 耦合 偶尔
  • 如果使用依赖注入,创建汽车不需要创建引擎,而是从外部将Engine对象传递Car类,这样就 解耦 解耦 解耦

不使用依赖注入,汽车内部创建引擎,耦合耦合耦合,

class Engine {
    void start() {
        System.out.println("Engine started");
    }
}
class Car {
    private Engine engine;

    Car() {
        engine = new Engine();  // Car类自己创建Engine对象
    }

    void drive() {
        engine.start();
        System.out.println("Car is driving");
    }
}

使用依赖注入,传递引擎对象给汽车,汽车内部不创建引擎对象,解耦解耦解耦

class Car {
    private Engine engine;

    Car(Engine engine) {  // Engine对象通过构造函数注入
        this.engine = engine;
    }

    void drive() {
        engine.start();
        System.out.println("Car is driving");
    }
}

现在,我们可以在创建Car对象时,把一个Engine对象传递给它:

Engine engine = new Engine();
Car car = new Car(engine);
car.drive();

上面的对比。虽然简单,但是是个挺好的例子

"Engine对象通过构造函数注入"是指我们通过Car类的构造函数将Engine对象传递给Car类。这是一种依赖注入的方式,被称为构造函数注入。

在这个例子中,Car类需要一个Engine对象来工作。在没有使用依赖注入的情况下,Car类会自己创建一个Engine对象。但这样做的问题是,Car类与Engine类紧密耦合在一起,如果我们想要更换一个不同类型的Engine,或者我们想要在测试时使用一个模拟的Engine,我们就需要修改Car类的代码。

当我们使用构造函数注入时,我们不再在Car类内部创建Engine对象,而是在创建Car对象时,从外部将Engine对象传递给Car类。这样,Car类就不再依赖于Engine类的具体实现,我们可以很容易地更换不同类型的Engine,或者在测试时使用一个模拟的Engine,而不需要修改Car类的代码。

也许你会说,不就是传个参吗,说得这么麻烦???? 某种程度上,你可以这么说,当时,这个玩意,能放大了玩。


依赖注入,可不仅仅是高级的传参 (李姐会理解)

人家,可传参,牛逼多了。

    1. Activity和Fragment:在Android开发中,Activity和Fragment经常需要访问一些共享的资源或服务,例如网络请求Retrofit、数据库访问、ViewModel等。如果没有依赖注入,我们可能需要在每个Activity或Fragment中手动创建这些对象,这会导致代码重复,而且使得Activity和Fragment与这些服务紧密耦合,难以进行单元测试。通过使用依赖注入,我们可以在一个统一的地方配置这些服务,然后在需要的地方自动注入,这使得代码更加清晰,更容易测试。
    1. 主App和Module:在一个模块化的应用中,不同的模块可能需要访问一些共享的服务。如果没有依赖注入,我们可能需要通过一些复杂的方式来让这些模块获取到这些服务,这会使得代码变得复杂,而且难以管理。通过使用依赖注入,我们可以在主App中配置这些服务,然后在各个模块中自动注入,这使得代码更加清晰,更容易管理。
  • 3、管理单例依赖项:在 Android 应用程序中,有一些依赖项是单例的,如数据库、网络客户端等。使用 Hilt 可以更轻松地管理这些单例依赖项,同时避免了手动管理单例依赖项的复杂性。

这个说是不是不会觉得是简答传参了!

依赖注入使得我们的代码更加模块化,每个类都只关注自己的职责,不需要关心其依赖对象的创建和管理。这使得我们的代码更容易重用。


三、Hilt的一些常见注解

在开始app例子之前,还需要需要将一些注解前面说明一下。

Hilt使用了一系列的注解来简化依赖注入的过程。以下是一些最常用的Hilt注解:

@HiltAndroidApp

  1. @HiltAndroidApp: 这个注解用于Application类,它会触发Hilt的代码生成,包括一个应用级别的组件,这个组件可以为其他Hilt组件(如Activity组件)提供依赖。
@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint

  1. @AndroidEntryPoint: 这个注解用于Android组件,如Activity、Fragment、Service等,它告诉Hilt这些组件可以接收依赖注入。
@AndroidEntryPoint class MainActivity : AppCompatActivity() { @Inject lateinit var someClass: SomeClass }

@Inject

  1. @Inject: 这个注解用于字段、构造函数或方法,告诉Hilt需要注入依赖。对于字段,Hilt会自动注入相应的依赖;对于构造函数,Hilt会使用它来创建类的实例;对于方法,Hilt会在注入后调用它。
class SomeClass @Inject constructor(private val someDependency: SomeDependency)

@Module

  1. @Module: 这个注解用于对象,这些对象提供了一系列的依赖提供方法。这些方法用@Provides注解标记,Hilt会在需要时调用它们。

@Provides

  1. @Provides: 这个注解用于在@Module注解的类中的方法,这些方法提供了依赖的实例。Hilt会在需要时调用这些方法。
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun provideSomeDependency(): SomeDependency {
        return SomeDependency()
    }
}

@InstallIn

  1. @InstallIn: 这个注解用于@Module注解的类,指定这个模块安装在哪个Hilt组件中。

@Singleton

  1. @Singleton: 这个注解用于@Provides注解的方法或@Inject注解的构造函数,告诉Hilt提供的依赖是单例的。
@Singleton
class SomeSingletonClass @Inject constructor()

@ViewModelInject

  1. @ViewModelInject: 这个注解用于ViewModel的构造函数,告诉Hilt如何创建ViewModel的实例。
class MyViewModel @ViewModelInject constructor(private val repository: Repository) : ViewModel()

@Assisted

  1. @Assisted:用于标记 ViewModel 的构造函数参数,以便在使用 assisted injection 时注入这些参数。

@AssistedInject

  1. @AssistedInject:用于标记使用 assisted injection 创建的 ViewModel 的构造函数。

这些注解使得Hilt能够自动处理依赖的创建和注入,大大简化了依赖注入的过程。

注解描述
@HiltAndroidApp用于Application类,触发Hilt的代码生成,包括一个应用级别的组件,这个组件可以为其他Hilt组件(如Activity组件)提供依赖。
@AndroidEntryPoint用于Android组件,如Activity、Fragment、Service等,告诉Hilt这些组件可以接收依赖注入。
@Inject用于字段构造函数方法,告诉Hilt需要注入依赖。对于字段,Hilt会自动注入相应的依赖对于构造函数,Hilt会使用它来创建类的实例对于方法,Hilt会在注入后调用它
@Module用于对象,这些对象提供了一系列的依赖提供方法。这些方法用@Provides注解标记,Hilt会在需要时调用它们。
@Provides用于在@Module注解的类中的方法,这些方法提供了依赖的实例。Hilt会在需要时调用这些方法。
@InstallIn用于@Module注解的类,指定这个模块安装在哪个Hilt组件中。
@Singleton用于@Provides注解的方法或@Inject注解的构造函数,告诉Hilt提供的依赖是单例的。
@ViewModelInject用于ViewModel的构造函数,告诉Hilt如何创建ViewModel的实例。
  • @Module@Provides:这两个注解通常一起使用,定义在一个类中,这个类提供了一系列的依赖提供方法。这些方法用@Provides注解标记,Hilt会在需要时调用它们。

四、上个App

四、1、hilt准备

app目录下 build.gradle


plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    // 添加kapt注解处理器
    id 'kotlin-kapt'
    // hilt所需
    id 'com.google.dagger.hilt.android'
}




dependencies {
    implementation 'com.google.dagger:hilt-android:2.44'
    kapt 'com.google.dagger:hilt-android-compiler:2.44'
}

项目级别build

plugins {
    id 'com.android.application' version '8.0.0' apply false
    id 'com.android.library' version '8.0.0' apply false
    id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
    // hilt
    id "com.google.dagger.hilt.android"  version "2.44" apply false
}

这里需要说明一下,比较新的版本是如上这么写。

如果是比较旧的android studio,则是classpath的写法。比如:

buildscript {
    dependencies {

        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44'
    }
}

以上,配置完成。

四.2、汽车和引擎的例子

// 使用 @HiltAndroidApp 注解标记一个应用,这是使用 Hilt 进行依赖注入的入口点。
// Hilt 需要一个入口点来提供依赖,这通常是 AndroidApplication 类。
// 这个注解会触发 Hilt 的代码生成,生成的代码包括一个应用级别的组件,这个组件可以为其他 Hilt 组件(如 Activity 组件)提供依赖。
@HiltAndroidApp
class MyApplication : Application() {
    // 这个类通常不需要写任何代码,除非你需要在应用启动时执行一些操作。
}

android:name=".MyApplication"

Engine


// 使用 @Inject 注解标记构造函数,告诉 Hilt 如何创建这个类的实例。
// 当 Hilt 需要提供一个 Engine 实例时,它会调用这个构造函数。
// 在这个例子中,Engine 类没有任何依赖,所以构造函数没有任何参数。
class Engine @Inject constructor() {
    fun start() {
        println("Engine started")
    }
}

Car

// 使用 @Inject 注解标记构造函数,告诉 Hilt 如何创建这个类的实例。
// 当 Hilt 需要提供一个 Car 实例时,它会调用这个构造函数。
// 在这个例子中,Car 类依赖于 Engine 类,所以构造函数有一个 Engine 类型的参数。
// Hilt 会查找提供 Engine 实例的方法(在这个例子中,就是 Engine 类的构造函数),然后用提供的 Engine 实例来创建 Car 实例。
class Car @Inject constructor(private val engine: Engine) {
    fun drive() {
        engine.start()
        println("Car is driving")
    }
}

Activity

/**
 *
 *  使用 @AndroidEntryPoint 注解标记一个 Android 组件,告诉 Hilt 这个组件可以接收依赖注入。
 *  Hilt 支持多种 Android 组件,包括 Activity、Fragment、View、Service、BroadcastReceiver 等。
 *  当一个组件被标记为 @AndroidEntryPoint,Hilt 会为这个组件生成一个组件类(在这个例子中,是 MainActivityComponent)。
 *  这个组件类是用来提供依赖的,它是由 Hilt 自动生成的,你无需手动创建。
 */



@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var car: Car

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Use the car
        car.drive()
    }
}

运行结果:

System.out: Engine started
System.out: Car is driving

嗯。如果只是这么用,那么确实平平无奇,仿佛是一个高级传参。 但是,如果只是这样,那我们还H个什么H。

但是,这是一个挺好的小例子。


五、实际开发中一些常用的场景

SharedPreferences的注入

// 创建一个Hilt模块,用于提供SharedPreferences实例
@Module
@InstallIn(SingletonComponent::class)
object SharedPreferencesModule {

    @Provides
    fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("pref_name", Context.MODE_PRIVATE)
    }
}

// 在Activity中,你可以使用@Inject注解来请求一个SharedPreferences实例。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var sharedPreferences: SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 使用SharedPreferences
        val editor = sharedPreferences.edit()
        editor.putString("key", "value")
        editor.apply()
    }
}

上述代码中,@AndroidEntryPoint是一个Hilt注解,用于指示Hilt应该为这个Activity提供依赖。然后,通过在sharedPreferences字段上添加@Inject注解,Hilt就会知道它需要为这个字段提供一个SharedPreferences实例。这个实例是由SharedPreferencesModule模块中的provideSharedPreferences方法提供的。

注意,你可以随时使用sharedPreferences字段,Hilt会确保在onCreate方法调用时,它已经被正确初始化。

你也可以将这种模式应用于其他的例子,例如网络服务、视图模型、数据库和数据仓库等。

多模块项目

在多模块项目中,Hilt可以帮助你更好地管理跨模块的依赖。例如,假设你有一个data模块和一个app模块,data模块提供了一个Repository类,app模块需要使用这个Repository

首先,在data模块中,你定义了一个Repository类,并用@Inject注解标记其构造函数:

// 在 data 模块中
class Repository @Inject constructor() {
    // ...
}

然后,在app模块中,你可以直接在需要的地方注入Repository

// 在 app 模块中
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject lateinit var repository: Repository

    // ...
}

在这个例子中,你不需要在app模块中手动创建Repository的实例,Hilt会自动为你处理。


再来一个依赖管理的例子

以为简化的Retrofit为例子

假设我们有一个NetworkModule,它提供了一个Retrofit实例:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Singleton
    @Provides
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()
    }
}

在这个例子中,NetworkModule是一个Hilt模块,它在应用级别的组件(SingletonComponent)中提供了一个Retrofit实例。这个Retrofit实例是一个单例,因为我们使用了@Singleton注解。

然后,我们有一个Repository类,它需要这个Retrofit实例来发起网络请求:

class Repository @Inject constructor(private val retrofit: Retrofit) {
    // ...
}

在这个例子中,Repository类通过构造函数注入获取了Retrofit实例。我们不需要手动创建Retrofit实例,Hilt会自动为我们处理。

最后,我们有一个ViewModel,它需要这个Repository来获取数据:

class MyViewModel @ViewModelInject constructor(private val repository: Repository) : ViewModel() {
    // ...
}

在这个例子中,MyViewModel通过@ViewModelInject注解获取了Repository实例。我们不需要手动创建Repository实例,Hilt会自动为我们处理。

这就是一个典型的依赖链:MyViewModel依赖于RepositoryRepository依赖于Retrofit。通过Hilt,我们可以轻松地管理这个依赖链,而无需手动创建和管理每个依赖。这使得代码更加清晰和直观,也使得新成员更容易理解项目的结构。


ViewModel注入

// Hilt提供了一个HiltViewModel注解,它允许你在ViewModel的构造函数中使用@Inject。
@HiltViewModel
class MyViewModel @Inject constructor(private val userRepository: UserRepository) : ViewModel() {
    // ...
}

// 在Activity或Fragment中,你可以使用由Hilt提供的ViewModel实例。
@AndroidEntryPoint
class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Use viewModel here
    }
}

数据库的注入

// 创建一个Hilt模块,用于提供Room数据库实例
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    @Provides
    fun provideDatabase(@ApplicationContext context: Context): MyDatabase {
        return Room.databaseBuilder(
            context,
            MyDatabase::class.java, "database-name"
        ).build()
    }

    @Provides
    fun provideUserDao(database: MyDatabase): UserDao {
        return database.userDao()
    }
}

// 在需要UserDao的地方,使用@Inject注解来请求一个UserDao实例。
class UserRepository @Inject constructor(private val userDao: UserDao) {
    // ...
}


不是每一个Activity都需要依赖注入,如果这个activity只在当前页面使用,那么没必要依赖注入。


Hilt篇,大概就到这里吧。