第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 四个注解的协作流程
为便于直观理解四个注解如何组合成完整注入链路,请参考下方的依赖注入组件协作图。
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 的基础。