第 1 章 依赖注入核心概念
依赖注入(Dependency Injection, DI)是 Android SystemUI 模块化架构的基石。SystemUI 包含几十个子系统(状态栏、导航栏、锁屏、通知、快速设置等),它们之间的依赖关系复杂且运行在同一个长期存在的进程中。没有 DI,手动管理构造参数和生命周期将导致代码耦合高、测试困难、内存泄漏频发。本章深入剖析 DI 的四个核心概念:控制反转、DI 容器、依赖图、作用域定义,并给出在 SystemUI 中的具体应用映射。
1.1 控制反转(IoC)
控制反转是 DI 的根本思想:将对象创建和依赖绑定的控制权从调用方转移到容器或框架。传统模式下,类内部通过 new 直接实例化依赖;IoC 后,类只声明需要什么,由外部注入。
传统 vs IoC 对比
// 传统方式:类内部创建依赖
public class NotificationController {
private final NotificationManager nm = new NotificationManager();
private final KeyguardManager km = new KeyguardManager();
}
// IoC 方式:依赖由外部注入
public class NotificationController {
private final NotificationManager nm;
private final KeyguardManager km;
// 构造注入
public NotificationController(NotificationManager nm, KeyguardManager km) {
this.nm = nm;
this.km = km;
}
}
在 SystemUI 源码(如 SystemUIApplication、SystemUIFactory)中,大量使用 Dagger Hilt 或 Koin 实现 IoC,避免在 onCreate 中手动初始化数十个组件。
1.2 DI 容器
DI 容器是 IoC 的具体实现,负责:
- 注册依赖的生产方式(工厂、单例、懒加载)。
- 解析依赖图并自动构造所有对象。
- 管理实例生命周期(单例、作用域绑定)。
常见 DI 容器包括 Dagger / Hilt(编译时,代码生成)、Koin(运行时,轻量 DSL),以及 Service Locator(简化版容器,但非严格 DI)。在 SystemUI 中,官方推荐使用 Hilt + 自定义组件来细分作用域。
| 容器类型 | 生成时机 | 性能 | SystemUI 适用性 |
|---|---|---|---|
| Dagger / Hilt | 编译时 | 高(零反射) | 适用于生产,代码量大 |
| Koin | 运行时 | 中(反射/代理) | 快速原型,调试友好 |
| Service Locator | 运行时 | 低(全局查找) | 仅临时构造,不推荐长期使用 |
1.3 依赖图(Dependency Graph)
依赖图是容器内部所有模块、组件、依赖关系构成的有向图。每个节点代表一个可注入的对象(实例或工厂),每条边代表“依赖关系”。容器在启动时通过拓扑排序构建该图,并检测循环依赖。
SystemUI 依赖图示例(Koin DSL)
val notificationModule = module {
// 单例:整个应用生命周期只有一份
single { NotificationManagerService() }
// 工厂:每次注入都创建新实例
factory { NotificationController(get()) }
// 作用域:绑定到特定子图(例如锁屏作用域)
scope(named("LOCKSCREEN")) {
scoped { LockScreenViewController(get()) }
scoped { LockScreenNotifier(get()) }
}
}
正确构建依赖图可以避免 饥饿死锁(循环依赖)、重复对象(本应单例却创建多次)、生命周期不一致(例如 Activity 级别对象被 Fragment 级别持有导致泄漏)。
1.4 作用域定义(Scope)
作用域决定了注入对象的存活时间。在 SystemUI 中常见的作用域包括:
- 单例:进程级别,随 SystemUI 进程启动而创建,进程销毁而销毁(如
NotificationManager、StatusBarManager的底层服务)。 - 子图作用域:绑定到特定 UI 或功能模块,例如“锁屏作用域”、“通知面板作用域”、“设置面板作用域”。
- 瞬时:每次注入都创建新实例,用后即弃。
| 作用域 | 生命周期 | 典型对象 | 风险 |
|---|---|---|---|
| @Singleton | 进程存活 | SystemUIApplication、DependencyController | 内存强引用,需确保不含 Activity 引用 |
| @Scoped(“LockScreen”) | 锁屏显示到消失 | LockScreenViewController、KeyguardHostView | 需要显式关闭作用域,否则内存泄漏 |
| @Factory | 每次注入时创建 | Runnable、短生命周期辅助类 | 频繁创建可能导致 GC 抖动 |
1.5 核心概念关系图
1.6 调试与常见错误
- 循环依赖:容器在构建依赖图时会报错。比如
A → B → A。解法:使用Lazy或引入中间层。 - 作用域泄漏:子图作用域未关闭导致对象被全局引用。SystemUI 中常用
onDismiss()时销毁作用域。 - 类型重复:同一个接口有多个实现,未使用
named或qualifier导致注入失败。
性能优化提示:避免在 UI 线程中首次构造重量级依赖(如跨进程 binder)。使用 lazy 或 provider 延迟注入,降低启动延迟。
1.7 在 SystemUI 中的部署结构
典型 SystemUI 模块(基于 Hilt)的目录布局示意:
SystemUI/
├── app/
│ ├── di/
│ │ ├── SystemUIModule.kt // 全局单例模块
│ │ ├── LockScreenModule.kt // 锁屏作用域模块
│ │ └── NotificationModule.kt // 通知作用域模块
│ ├── lockscreen/
│ │ ├── LockScreenController.kt
│ │ └── LockScreenNotifier.kt
│ ├── notification/
│ │ ├── NotificationController.kt
│ │ └── NotificationManager.kt
│ └── SystemUIApplication.kt // @HiltAndroidApp
每个模块使用 @Module / @InstallIn 注解标明安装到哪个组件,作用域通过 @Scoped 或 @Singleton 定义。