一、啥是依赖注入

在咱们开发 Android 应用的时候,经常会遇到这样的情况:一个类需要用到另一个类的实例。比如说,有个UserService类,它要使用UserRepository类的实例来获取用户数据。传统的做法呢,就是在UserService类里面直接创建UserRepository的实例,就像下面这样(Java 技术栈):

// Java 技术栈示例
public class UserService {
    private UserRepository userRepository;

    public UserService() {
        // 直接在构造函数里创建 UserRepository 实例
        this.userRepository = new UserRepository(); 
    }

    public User getUserById(int id) {
        return userRepository.getUserById(id);
    }
}

这种做法有个问题,就是UserServiceUserRepository紧紧绑在一起了。要是以后UserRepository的创建方式变了,或者要换个实现类,那UserService也得跟着改。这就好比你把两台机器焊死在一起,一台机器出问题,另一台也得受影响。

而依赖注入呢,就是把创建依赖对象的控制权从类的内部转移到类的外部。这样,类就只负责使用依赖对象,而不用管它是怎么创建的。还是上面那个例子,用依赖注入的方式改写一下:

// Java 技术栈示例
public class UserService {
    private UserRepository userRepository;

    // 通过构造函数注入 UserRepository 实例
    public UserService(UserRepository userRepository) { 
        this.userRepository = userRepository;
    }

    public User getUserById(int id) {
        return userRepository.getUserById(id);
    }
}

这样,UserService就和UserRepository解耦了,以后要是UserRepository有变化,UserService不用改代码。

二、Hilt 登场

Hilt 是 Android Jetpack 里的一个依赖注入框架,它能让我们更轻松地在 Android 应用里实现依赖注入。Hilt 帮我们处理了很多复杂的依赖注入逻辑,让我们可以专注于业务代码的编写。

1. 配置 Hilt

首先,得在项目里配置 Hilt。在项目的build.gradle文件里添加 Hilt 的依赖:

// Groovy 技术栈示例
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44'
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

然后在 app 模块的build.gradle文件里应用 Hilt 插件,并添加 Hilt 的依赖:

// Groovy 技术栈示例
apply plugin: 'com.google.dagger.hilt.android'

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

2. 简单示例

下面来个简单的例子,看看 Hilt 怎么用。假设有个Logger类,用来记录日志,还有个MainActivity要使用这个Logger

先定义Logger类:

// Java 技术栈示例
import javax.inject.Inject;

public class Logger {
    @Inject
    public Logger() {
        // 构造函数
    }

    public void log(String message) {
        System.out.println("Log: " + message);
    }
}

这里的@Inject注解告诉 Hilt 这个类可以被注入。

然后在MainActivity里使用Logger

// Java 技术栈示例
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import javax.inject.Inject;

import dagger.hilt.android.AndroidEntryPoint;

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

    @Inject
    Logger logger;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        logger.log("Activity created");
    }
}

@AndroidEntryPoint注解告诉 Hilt 这个 Activity 可以使用依赖注入。@Inject注解标记的logger字段会由 Hilt 自动注入。

三、Hilt 的原理

Hilt 的核心原理是利用 Dagger 这个依赖注入框架。Dagger 会在编译时生成一些代码,这些代码负责创建和管理依赖对象。

1. 组件和作用域

Hilt 有不同的组件,每个组件有不同的作用域。比如说,SingletonComponent是全局单例的,ActivityComponent是和 Activity 生命周期绑定的。

当我们在一个类里使用@Inject注解标记一个字段时,Hilt 会根据这个类所在的组件和作用域来创建和注入依赖对象。

2. 编译时生成代码

Hilt 在编译时会生成一些辅助类,这些类负责创建和管理依赖对象。比如上面的MainActivity,Hilt 会生成一个MainActivity_HiltComponents类,这个类里包含了创建和注入Logger实例的代码。

四、应用场景

1. 解耦组件

在大型 Android 应用里,各个组件之间的依赖关系很复杂。使用 Hilt 可以把这些组件解耦,让代码更易于维护和测试。比如说,一个 Activity 依赖多个服务,通过 Hilt 注入这些服务,Activity 就不用关心这些服务是怎么创建的。

2. 单元测试

在单元测试时,我们可以很方便地替换依赖对象。比如在测试UserService时,可以注入一个模拟的UserRepository,这样就可以独立测试UserService的逻辑,不受UserRepository实现的影响。

五、技术优缺点

1. 优点

  • 提高代码可维护性:通过解耦组件,代码的结构更清晰,修改和扩展都更容易。
  • 便于测试:可以方便地替换依赖对象,进行单元测试。
  • 编译时检查:Hilt 在编译时就会检查依赖注入的正确性,避免运行时出现错误。

2. 缺点

  • 学习成本:对于初学者来说,Hilt 的概念和使用方法可能有点难理解。
  • 代码生成:编译时生成的代码会增加项目的编译时间和 APK 的大小。

六、注意事项

1. 作用域问题

要注意不同组件的作用域,避免出现内存泄漏。比如,不要在ActivityComponent里注入一个全局单例的对象,这样会导致 Activity 销毁时对象无法释放。

2. 依赖冲突

如果项目里有多个依赖注入框架,可能会出现依赖冲突的问题。要确保只使用一个依赖注入框架,或者妥善处理冲突。

七、最佳实践

1. 模块化设计

把应用拆分成多个模块,每个模块有自己的依赖注入逻辑。这样可以提高代码的可复用性和可维护性。

2. 合理使用作用域

根据不同的需求,选择合适的组件和作用域。比如,全局单例的对象使用SingletonComponent,和 Activity 生命周期绑定的对象使用ActivityComponent

3. 测试驱动开发

在编写代码时,先写测试用例,然后再实现功能。这样可以确保代码的可测试性,并且可以及时发现问题。

八、文章总结

Hilt 是 Android Jetpack 里一个非常实用的依赖注入框架,它能帮助我们解决 Android 应用里的依赖管理问题。通过依赖注入,我们可以把组件解耦,提高代码的可维护性和可测试性。虽然 Hilt 有一些学习成本和代码生成的问题,但只要我们掌握了它的原理和最佳实践,就能在开发中发挥它的优势。在实际开发中,要注意作用域和依赖冲突的问题,合理使用 Hilt 来构建高质量的 Android 应用。