依赖注入框架

依赖注入是用于实现控制反转的方式之一,另外一种实现控制反转的方式是依赖查找

  • 基于<依赖查找 / 服务发现>
public class Man {
    Car car = ServiceManager.get(Car);
    public Man() { }
}

常见的依赖注入的实现方式

  • 基于构造方法 或 setter 方法
public class Man {
    Car car;
    public Man(Car car) {
        this.car = car;
    }
}

public class Man {
    public void setCar(Car car) {
        this.car = car;
    }
}
  • 基于注解
public class Man {
    @Inject
    Car car;
}

对比上述三种方式:方案一将 Car 的构建放到了外部,问题并没有解决,只是进行了转移。方案二由框架来帮助构建实例,和依赖查找的主要区别是依赖查找需要目标对象主动去获取,而依赖注入无需目标对象感知,由框架主动赋值

依赖注入在软件开发中被广泛应用,比较知名的有 Spring、Guice 等。依赖注入在工程上带来的好处主要有:

  • 代码复用性更好
  • 更容易重构
  • 更容易测试

本文要介绍的 Dagger 2 是专门为 Android 开发设计的一款依赖注入框架。在设计上遵循 JSR 330 标准,通过注解生成代码的方式来实现依赖注入。一方面避免了用户创建大量的模版代码,另一面避免了运行时通过反射的方式调用带来的性能问题。同时编译期间的依赖检查,使得问题能够提前暴露,易于追踪

基本用法

  • 使用 @Inject 标注需要注入的变量或方法

  • 使用 @Inject 标注构造函数来完成实例的创建
  • 使用 @Module 来完成实例的创建
  • @Component可以标注接口或抽象类,Component 桥梁可以完成依赖注入过程

Lazy & Provider

Lazy<T>类 可以实现延迟注入,降低内存开销(DoubleCheck,缓存 Instance 实例),只在第一次调用时创建实例,但由于 Instance 实例不是静态的,所以不是单例模式

class Monkey @Inject constructor() {

    @Inject
    lateinit var stick1: Lazy<Stick>

    @Inject
    lateinit var stick2: Lazy<Stick>

    @Test
    fun compare() {
        assert(stick1.get() == stick2.get()) // false
    }
}

Provider<T> 类每次调用都会创建新的实例

Provider 和 Lazy 结合使用:Provider<Lazy<Stick>>,这样既可以保证效率,也可以保证能获取到多个实例

Qualifier

对于同一类型有多个实例需要注入的情况,通过限定符 @Qualifier 可以防止依赖迷失。系统提供了默认的 @Name 限定符,但是不是强类型的,我们也可以通过 Qualifier 来生成自定义的限定符

Scope

相当于单例模式,对于同一个 Component,每次调用 Component.inject 或 Component.getXxx 方法内部维护的都是同一个对象。这引出了 Component 分层的概念,Component 有两种分层方式:继承、依赖(如果不想全部暴露,可以选择依赖关系)

依赖关系 vs 继承关系

  • 相同点
    • 两者都能复用其他 Component 的依赖
    • 有依赖关系和继承关系的 Component 不能有相同的 Scope
  • 不同点
    • 依赖关系中被依赖的 Component 必须显式地提供公开依赖实例的接口,而 SubComponent 默认继承 parent Component 的依赖
    • 依赖关系会生成两个独立的 DaggerXXComponent 类,而在继承关系中,SubComponent 需要声明 @Subcomponent.Builder 标注的 builder 方法,SubComponent 不会生成独立的 DaggerXXComponent 类
// 依赖关系

@CustomScope
@Component(modules = [AnimalModule::class], dependencies = [BaseComponent::class])
interface ActivityComponent {

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun bindCow(cow: Cow): Builder
        fun bindBaseComponent(baseComponent: BaseComponent): Builder
        fun build(): ActivityComponent
    }

    fun injectDaggerActivity(activity: DaggerActivity)
}

@BaseScope
@Component(modules = [RealPeople::class, PeopleModule::class])
interface BaseComponent {
    fun man(): Man
    fun woman(): Woman
    fun people(): People
}

// 继承关系

@Module(subcomponents = SonComponent.class)
public class CarModule {
    @Provides
    @ManScope
    static Car provideCar() {
        return new Car();
    }
}

@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
    void inject(Man man);
}

@SonScope
@SubComponent(modules = BikeModule.class)
public interface SonComponent {
    void inject(Son son);
    @Subcomponent.Builder
    interface Builder {
        SonComponent build();
    }
}

@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
    void injectMan(Man man);
    SonComponent.Builder sonComponent(); // 用来创建 Subcomponent
}

手动注入

class Monkey {

    @Inject
    lateinit var stick: Stick
}

在上面这个例子中,Monkey 需要一个 Stick,但是 Monkey 并没有被 @Inject 注解的构造方法,也没有通过 Module 创建 Monkey 实例,所以 Dagger 框架无法构建 Monkey 实例,这时就需要使用到 Dagger 的手动注入能力

@Component
interface MonkeyComponent {

    fun inject(monkey: Monkey)
}

val component = DaggerMonkeyComponent.create()
val monkey = Monkey()
component.inject(monkey) // 手动注入
val stick = monkey.stick

手动注入适用于 UI 入口类等无法手动创建的实例,但是也存在缺点:

Component 和 Monkey(UI 组件)需要在一起编译,这导致了服务提供方依赖了服务调用方,但在实际场景中,我们是不知道服务调用方的存在,这就引出了手动获取的方式

手动获取

@Component
interface MonkeyComponent {

    fun stick(): Stick
}

val component = DaggerMonkeyComponent.create()
val stick = component.stick
  • 手动注入可以获取到 Dagger 中管理的所有对象,但 Dagger 需要感知到被注入对象的存在
  • 手动获取只能获取到 Dagger 主动暴露的对象,比较安全
  • 手动注入适用于一些内部场景,既是提供方也是使用方。手动获取实例主要用于三方 SDK 对外提供能力,需要限制外部对内部的访问

外部注入

class Monkey {

    @Inject
    lateinit var stick: Stick

    @Inject
    lateinit var companyName: String
}

由于 Monkey 也需要工作,而具体的公司需要外部来指定,这时我们就需要外部注入的功能

@Component
interface MonkeyComponent {

    fun stick(): Stick

    @Component.Builder
    interface Builder {

        @BindInstance
        fun companyName(name: String): Builder

        fun build(): MonkeyComponent
    }
}

MultiBinding

对应两个注解:IntoSet、IntoMap

Hilt

系统提供了默认的注入点 @AndroidEntryPoint,目前支持的有:Application、Activity、Fragment、View、Service、BroadcastReceiver

Hilt 是由 Google 推出的基于 Dagger 2 的依赖注入框架,旨在简化 Android 应用中的依赖注入流程。下面是 Hilt 和 Dagger 2 之间的一些主要区别和使用场景:

  1. 简化配置:

    • Dagger 2 需要开发人员手动编写大量的代码来配置和连接依赖项。这包括编写模块、组件、限定符等。
    • Hilt 提供了注解处理器,可以自动生成大部分 Dagger 2 中需要手动编写的代码,使得配置变得更加简单和直观。
  2. 减少模板代码:

    • 在 Dagger 2 中,需要编写很多模板代码来连接各个依赖项和提供全局依赖项。
    • Hilt 提供了注解和生成器,可以自动生成大部分模板代码,使得开发者只需专注于业务逻辑,而无需编写重复的模板代码。
  3. 注解简化:

    • Dagger 2 的注解相对较复杂,需要开发人员对其有一定的了解才能正确地使用。
    • Hilt 提供了一套更加简化的注解,使得开发者更容易理解和使用依赖注入功能。
  4. Android 特化:

    • Hilt 被设计成更适合在 Android 应用中使用,提供了特定于 Android 的功能,如与 Android 生命周期的集成、支持 ViewModel 的依赖注入等。
    • Dagger 2 是一个通用的依赖注入框架,可以用于任何 Java 或 Android 应用,但是需要开发者自行处理 Android 特定的问题。

使用场景:

  • 如果你是一个 Android 开发者,并且希望简化你的应用中的依赖注入流程,那么可以考虑使用 Hilt。
  • 如果你的项目比较复杂,需要更多的灵活性和控制,或者已经熟悉了 Dagger 2,并且不想引入新的依赖库,那么可以继续使用 Dagger 2。

ARouter & Dagger2

如果你主要关注的是实现组件之间的页面跳转、参数传递以及拦截器等路由功能,那么 ARouter 可能更适合你的需求。ARouter 提供了简单易用的 API,能够方便地实现这些功能,并且在阿里巴巴内部得到了广泛的应用,有着较为成熟的技术支持和社区。

而如果你更关注的是依赖注入方面的功能,例如管理组件之间的依赖关系、提高代码的可测试性和可维护性等,那么 Dagger2 可能更适合你的需求。Dagger2 是一个强大的依赖注入框架,能够帮助你管理复杂的依赖关系,并且通过依赖注入的方式来实现解耦,提高代码质量。

在实际项目中,你也可以同时使用 ARouter 和 Dagger2,根据具体的需求选择合适的工具来解决问题。比如,你可以使用 ARouter 来管理页面跳转和路由功能,同时使用 Dagger2 来管理依赖关系,以此来充分发挥它们各自的优势。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值