第2章 Dagger基础组件详解:@Module、@Provides、@Component、@Inject

本章核心:掌握 Dagger 四个基础注解的职责、使用场景与协作关系,为 SystemUI 模块化注入打下牢固基础。

2.1 四个注解的角色定位

注解 使用位置 核心职责 典型场景
@Module 类声明 声明依赖提供者的容器 封装第三方库对象、系统服务、Builder 模式
@Provides 方法声明 标注模块内提供依赖实例的方法 创建无法直接 @Inject 构造的对象
@Component 接口声明 连接注入请求与提供者的桥梁 定义全局/子组件作用域
@Inject 构造/字段/方法 标记需要注入的依赖或可注入的构造器 Activity、Fragment、ViewModel 成员注入

2.2 @Inject — 依赖注入的起点

@Inject 是 Dagger 识别依赖关系的最直接方式。当它标注在构造函数上时,Dagger 会将该类视为可自动提供的实例;当标注在字段上时,表示该字段需要被注入。

// 可注入构造 — Dagger 可直接创建 KeyguardSecurityContainerController
public class KeyguardSecurityContainerController {
    @Inject
    public KeyguardSecurityContainerController(
            KeyguardSecurityModel securityModel,
            KeyguardUpdateMonitor updateMonitor) {
        // 参数将自动从组件图中获取
    }
}
// 字段注入 — 在 Activity 或 Fragment 中声明
public class KeyguardHostView extends FrameLayout {
    @Inject
    KeyguardSecurityContainerController controller;

    @Override
    protected void onFinishInflate() {
        // 需要先调用 component.inject(this)
    }
}
注意:字段注入的类必须经过 Component 的 inject() 方法调用后,字段才可用。SystemUI 中大量使用字段注入来解耦 View 与控制器的直接创建。

2.3 @Module + @Provides — 外部依赖的适配器

当依赖的构造函数无法标注 @Inject(如第三方库、系统服务、Builder 模式)时,通过 @Module 封装提供方法,并用 @Provides 标注。每个 @Provides 方法就是一条依赖提供声明。

@Module
public class SystemServicesModule {
    @Provides
    @Singleton
    public PowerManager providePowerManager(Context context) {
        // PowerManager 是系统服务,无法在构造上加 @Inject
        return (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    }

    @Provides
    public NotificationManager provideNotificationManager(Context context) {
        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    }
}
最佳实践:SystemUI 中将每个系统服务独立为一个 @Provides 方法,便于测试时通过 Mock 模块替换。避免在一个 Module 中堆积超过 10 个提供方法,可考虑按功能拆分为 NetworkModule、DisplayModule 等。

2.4 @Component — 注入桥接器

@Component 注解的接口定义了 Dagger 生成的注入器入口。它声明了三个关键信息:注入目标(哪些类需要注入)、依赖来源(依赖哪些 Module)、作用域(是否全局单例)。

@Singleton
@Component(modules = {
    SystemServicesModule.class,
    KeyguardModule.class,
    NotificationModule.class
})
public interface SystemUIComponent {
    // 定义 inject 方法,每个需要字段注入的类提供一个重载
    void inject(KeyguardHostView keyguardHostView);
    void inject(NotificationPanelView notificationPanel);

    // 暴露依赖给子组件
    KeyguardSecurityModel keyguardSecurityModel();
}
组件图构成:Component 内部通过 Dagger 生成的 DaggerSystemUIComponent 类,将 @Inject 构造类与 @Module 提供的方法连接成一个依赖图。

2.5 四个注解的协作流程

为便于直观理解四个注解如何组合成完整注入链路,请参考下方的依赖注入组件协作图。

Dagger 基础组件协作流程 @Component @Module @Provides PowerManager @Inject 构造 KeyguardController @Inject 字段 controller Component 聚合 Module 与 @Inject 构造器,生成依赖图。 当字段上 @Inject 请求达到时,Component 从其图内查找对应的提供者(@Provides 或 @Inject 构造)。

2.6 调试与验证技巧

  • 编译时校验:Dagger 在编译期生成 DaggerXXXComponent 类,若某个依赖无法解析,编译直接报错并提示缺失的 Binding。
  • 依赖图导出:在 build.gradle 中添加参数 -Adagger.fullGraphDebug,可在 build/generated/source/apt/debug/ 中看到依赖图文本文件。
  • 重复绑定错误:如果 @Module 与 @Inject 构造同时提供了同一类型,Dagger 会报 DuplicateBindings 错误。优先保留 @Module 提供的方法,移除 @Inject 构造上的标注。
常见陷阱:@Module 类如果没有被任何 Component 引用,Dagger 不会处理其中的 @Provides 方法,运行时出现 java.lang.IllegalArgumentException: No injector factory bound for Class。务必确认 Module 已加入 Component 的 modules 列表。

2.7 性能与稳定性优化建议

优化方向 具体措施 效果
作用域控制 全局服务使用 @Singleton;局部生命周期使用 @Reusable 或自定 Scope 减少单例池大小,降低内存占用
惰性注入 字段类型声明为 Lazy<T> 或 Provider<T> 延迟实例化,缩短 Application 启动时间
模块拆分 按功能域(Keyguard/Notification/Navigation)拆分 @Module 编译期增量处理,加快构建速度
禁止循环依赖 使用 dagger.internal.DaggerCollections 工具在调试阶段检测 避免运行时栈溢出
SystemUI 实战经验:在 Android 14+ 中,SystemUI 开始使用 dagger.hilt (基于 Dagger) 作为主注入方案。但核心注解 @Module/@Provides/@Component/@Inject 的语义与手动 Dagger 完全一致,理解这四个注解是迁移至 Hilt 的基础。