Android Dagger2笔记

一、什么是 Dagger 2?为什么要用它?

Dagger 2 是一个完全在编译阶段生成依赖注入代码的框架 ,全程不使用反射,因此非常适合对运行时性能敏感的 Android 开发 。它的名字 "Dagger" 其实是 "DAG-er" 的谐音,代表着其核心数据结构------有向无环图 (Directed Acyclic Graph) 。

在没有依赖注入框架时,我们经常需要手动 new 出依赖对象,比如 new A(new B(new C())),这使得代码耦合度高、难以测试和维护 。Dagger 2 旨在解决这些问题,它的主要优势包括:

  • 解耦:将对象的创建和使用分离,类不再关心依赖如何被创建,只需声明"我需要什么" 。
  • 可测试性:在单元测试中,可以轻松地使用 Mock 对象替换真实依赖 。
  • 生命周期管理 :通过自定义 @Scope 注解,可以精确控制对象在特定范围内的复用(如整个应用生命周期或某个 Activity 的生命周期)。
  • 编译时校验:如果依赖关系图存在问题(例如,某个依赖找不到),会在编译时直接报错,而不是等到运行时才崩溃 。

二、Dagger 2 的核心概念与实战

Dagger 2 的使用主要围绕几个核心注解展开。为了更好地理解,我们可以把它想象成一个网购过程 :

  • 商品 :你需要使用的依赖对象。
  • 包裹(@Module):负责打包商品,告诉快递员如何准备商品。
  • 快递员(@Component):负责将包裹中的商品送到你手上,是连接提供方和使用方的桥梁。
  • 收货人(@Inject):你,在需要商品的地方签收。

下面我们通过一个具体的 UserRepository 示例来走一遍流程。

1. 添加依赖

首先,在模块的 build.gradle 文件中添加 Dagger 2 的依赖和注解处理器 。

groovy 复制代码
dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x' // Kotlin 项目使用 kapt,Java 项目使用 annotationProcessor
}

2. 定义依赖:@Inject 构造方法

这是最简单、最优先推荐的方式。在目标类的构造方法上添加 @Inject 注解,告诉 Dagger 2 如何创建该类的实例 。

kotlin 复制代码
// UserLocalDataSource.kt
class UserLocalDataSource @Inject constructor() {
    fun getUserData(): String = "Local User Data"
}

// UserRemoteDataSource.kt
class UserRemoteDataSource @Inject constructor() {
    fun getUserData(): String = "Remote User Data"
}

现在,我们有一个 UserRepository,它依赖于上述两个数据源。

kotlin 复制代码
// UserRepository.kt
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) {
    fun fetchData(): String {
        return "${localDataSource.getUserData()} and ${remoteDataSource.getUserData()}"
    }
}

3. 定义连接桥梁:@Component

@Component 注解一个接口,Dagger 2 会在编译时自动生成这个接口的实现类(通常以 Dagger 开头,如 DaggerAppComponent)。它负责将依赖注入到目标对象中 。

kotlin 复制代码
// AppComponent.kt
@Component
interface AppComponent {
    // 定义注入方法,参数指定将依赖注入到哪个类的实例中
    fun inject(activity: MainActivity)
}

4. 执行注入:在目标类中使用

在需要依赖的地方(如 MainActivity),使用 @Inject 注解标记字段,并在合适的时机(如 onCreate)调用 DaggerAppComponent 进行注入 。

kotlin 复制代码
// MainActivity.kt
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var userRepository: UserRepository

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

        // DaggerAppComponent 是编译后自动生成的类
        DaggerAppComponent.create().inject(this)

        // 现在 userRepository 已经被成功注入,可以直接使用
        println(userRepository.fetchData())
    }
}

完成以上步骤后,运行项目,你会发现 userRepository 已经被成功实例化,并且它依赖的两个 DataSource 也被自动注入。这一切都得益于我们在构造方法上添加的 @Inject。

5. 处理第三方依赖和复杂情况:@Module 与 @Provides

当我们需要注入的类是我们无法修改源码的第三方库(如 Retrofit、OkHttpClient),或者构造方法需要动态参数时,@Inject 就无能为力了。这时就需要 @Module 和 @Provides 出场 。

假设我们需要提供一个 Retrofit 实例。

首先,创建一个 @Module 注解的类,并在其中定义 @Provides 注解的方法,方法的返回值就是我们要提供的依赖类型 。

kotlin 复制代码
// AppModule.kt
@Module
class AppModule {

    @Provides
    fun provideBaseUrl(): String = "https://api.example.com/"

    @Provides
    fun provideRetrofit(baseUrl: String): Retrofit {
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .build()
    }
}

然后,在 @Component 中声明使用这个 Module。

kotlin 复制代码
// AppComponent.kt
@Component(modules = [AppModule::class]) // 声明使用 AppModule
interface AppComponent {
    fun inject(activity: MainActivity)
}

Dagger 2 会从 AppModule 中查找所有 @Provides 方法,并利用它们来满足依赖需求。

6. 处理同类型多实例:@Named 或自定义 @Qualifier

如果一个类型有多个不同的实现(比如开发环境和生产环境的 ApiService),我们需要告诉 Dagger 2 如何区分它们。这时可以使用 @Named 注解 。

kotlin 复制代码
// AppModule.kt
@Module
class AppModule {

    @Provides
    @Named("debug") // 使用 @Named 进行标记
    fun provideDebugApiService(): ApiService = DebugApiService()

    @Provides
    @Named("release")
    fun provideReleaseApiService(): ApiService = ReleaseApiService()
}

在需要注入的地方,同样使用 @Named 指定要注入哪一个。

kotlin 复制代码
// SomeClass.kt
class SomeClass @Inject constructor(
    @Named("debug") private val apiService: ApiService // 明确指定需要 debug 版本的 ApiService
) {
    // ...
}

@Named 是 Dagger 2 内置的一种 @Qualifier。你也可以创建自定义的 @Qualifier 注解,使代码更具语义 。

7. 管理对象生命周期:@Scope

默认情况下,Dagger 2 每次注入都会创建一个新的实例。如果我们希望某个对象在整个应用生命周期内是单例,或者在同一个 Activity 内复用,就需要使用 @Scope 注解 。

Dagger 2 内置了一个 @Singleton 注解。要让它生效,需要同时在 @Component 和 @Provides 方法(或 @Inject 构造方法)上使用它。

kotlin 复制代码
// 在 Component 上标记 @Singleton
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
    fun inject(activity: MainActivity)
}

// AppModule.kt
@Module
class AppModule {
    @Provides
    @Singleton // 同样标记 @Singleton,表明提供的是单例
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder().baseUrl("...").build()
    }
}

关键理解 :@Singleton 的作用范围与 @Component 的生命周期绑定。在上面的例子中,只要我们在 Application 中只创建一次 AppComponent,那么 provideRetrofit 方法也只会调用一次,从而保证了 Retrofit 实例在整个应用中唯一 。你也可以自定义 @ActivityScoped、@FragmentScoped 等 @Scope 注解,实现更细粒度的生命周期控制。

三、Dagger 2 的工作原理

Dagger 2 最神奇的地方在于,它没有在运行时使用任何反射 ,所有的依赖查找和实例化代码都是在编译时 生成的 。这得益于 Java 的注解处理器 (Annotation Processor)。

整个流程可以概括为:注解 -> 注解处理器 -> 生成源代码 -> 编译运行。

  1. 编译时扫描 :当我们执行编译(./gradlew build)时,Dagger 2 的注解处理器(dagger-compiler)会扫描项目中所有的 @Inject、@Module、@Component 等注解 。
  2. 构建依赖图 :注解处理器会分析所有被注解的类和方法,构建出一个庞大的有向无环图 (DAG) 。这个图清晰地表达了每个对象的依赖关系:A 依赖于 B,B 依赖于 C 和 D... 如果图中存在循环依赖(如 A 依赖 B,B 又依赖 A),或者某个依赖无法被满足,注解处理器就会在编译时抛出错误。
  3. 生成源代码 :在构建好依赖图后,Dagger 2 会为每个 @Component 接口生成对应的实现类(如 DaggerAppComponent),并为每个被 @Inject 注解构造方法的类生成一个工厂类 (Factory) (如 UserRepository_Factory),为每个被 @Inject 注解字段的类生成一个成员注入器类 (MembersInjector) (如 MainActivity_MembersInjector)。
    • 工厂类 (Factory) :它的职责非常单一,就是负责创建某个类型的实例,并通过 get() 方法返回。UserRepository_Factory 内部会调用 UserRepository 的构造方法,而构造方法所需的参数(如 UserLocalDataSource 和 UserRemoteDataSource)则由它们自己的工厂提供。
    • 成员注入器类 (MembersInjector) :它的职责是负责将依赖注入到目标对象的字段中。MainActivity_MembersInjector 的 injectMembers() 方法内部,会调用 UserRepository_Factory 的 get() 方法来获取实例,并将其赋值给 MainActivity 中被 @Inject 注解的 userRepository 字段。
  4. 运行时调用 :当我们在代码中调用 DaggerAppComponent.create().inject(this) 时,实际上就是在执行 Dagger 2 为我们生成的这些高效、无反射的普通 Java 代码。DaggerAppComponent 内部会组织并调用一系列的 Factory 和 MembersInjector,最终完成依赖的创建和注入 。

简单来说,Dagger 2 将原本需要你手动编写的、繁琐的"new 对象"和"set 对象"的代码,通过注解处理器自动帮你生成了。它就像一个代码生成器,在幕后为你打理好一切,让你能够专注于业务逻辑。

总结与展望

Dagger 2 是一个强大但学习曲线较陡峭的依赖注入框架。它的核心在于编译时代码生成,通过 @Inject、@Module、@Component、@Scope、@Qualifier 等注解,构建一个清晰、可复用的依赖关系图,极大地提升了 Android 应用的可维护性和可测试性 。

虽然现在 Google 官方推荐使用基于 Dagger 2 封装的 Hilt ,它大大简化了 Dagger 2 在 Android 上的使用难度,提供了更简单的 API 和更好的 Android 组件集成 。但深入理解 Dagger 2 的原理和核心概念,依然是掌握 Hilt 的坚实基础。当你了解了背后的 DAG 和编译时生成代码的思想,再看 Hilt 的 @HiltAndroidApp 和 @AndroidEntryPoint,会有一种豁然开朗的感觉。

相关推荐
千里马学框架3 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台3 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone3 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo3 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077003 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen3 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone3 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui