依赖解析基础:依赖注入、依赖图与解析原理
好,咱们正式开始啃这块硬骨头。依赖解析,说白了就是帮你理清楚“谁需要谁”。在SystemUI这个庞大的系统里,一个面板可能需要多个服务,一个服务又可能依赖一堆底层组件。如果不把这些关系理清楚,代码就跟一团乱麻似的。
1. 理解依赖注入
我在项目中遇到过几次“硬编码依赖”的坑。举个例子,一个NotificationPanelView直接new了一个StatusBarManager,结果后来要换一个mock实例做测试,就得改源码。这种事发生几次后,你就会明白依赖注入有多重要。
依赖注入(Dependency Injection,DI),核心思想就八个字:别自己找,等人送给你。
看一个对比:
// 硬编码依赖(反面教材)
public class NotificationPanel {
private StatusBarManager mStatusBarManager;
public NotificationPanel() {
mStatusBarManager = new StatusBarManager(); // 我自己创建
}
}
// 依赖注入(好的实践)
public class NotificationPanel {
private StatusBarManager mStatusBarManager;
public NotificationPanel(StatusBarManager manager) {
mStatusBarManager = manager; // 别人送给我
}
}
关键区别:第一种写法下,你要测试NotificationPanel非常难。第二种写法,你只需传入一个mock对象即可。
依赖注入有三种常见方式:
| 方式 | 说明 | 个人喜好 |
|---|---|---|
| 构造器注入 | 通过构造函数传入依赖 | 我最推荐,依赖明确,不可变 |
| Setter注入 | 通过set方法传入依赖 | 灵活,但容易漏掉 |
| 字段注入 | 直接注解到成员变量 | 方便,但隐藏了依赖关系 |
小技巧:我习惯对核心组件用构造器注入,对可选组件用Setter注入。这样既保证了核心依赖的存在,又保留了灵活性。
2. 依赖图——依赖关系的可视化
你想想看,当依赖多了以后,光靠人脑是记不住的。这时候就需要依赖图出场了。
依赖图是一个有向图。每个节点表示一个组件或服务,每条有向边表示“A依赖B”。
举个例子,SystemUI中的部分依赖关系:
NavigationBarFragment依赖NavigationModeControllerNavigationModeController依赖DeviceProvisionedControllerCommandQueue被多个面板依赖
为什么依赖图必须是无环的? 因为如果A依赖B,B又依赖A,那就成了死循环,谁都没法先创建。
记得我在一次重构中,发现两个服务互相依赖,编译通过了但运行时栈溢出。查了半天,最后用依赖图画出来才发现那个环。
警告:在依赖图中发现环,一定要立即处理。不要想着“运行时可能不会触发”。你永远不知道什么时机触发这个环。
好,接下来我们展示一下依赖解析的核心逻辑。这是本章唯一的SVG图,画的是从被依赖者到最终对象的完整路径。
理解这张图:箭头表示“谁依赖谁”。依赖容器负责收集所有依赖关系,构建成图,再进行环检测。只有无环图才能进行后续的解析。
3. 依赖解析原理
依赖解析,就是根据依赖图,按正确的顺序创建所有对象。核心原理其实不复杂,就是拓扑排序。
假设我们有三个组件:A依赖B,B依赖C。解析顺序就是C→B→A。先创建没有依赖的C,再创建依赖C的B,最后创建依赖B的A。
这个过程中,容器需要做两件事:
- 分析依赖图:找出所有组件的依赖关系
- 确定创建顺序:用拓扑排序得到无环的线性序列
我参与过Android系统的依赖注入框架开发,当时遇到一个棘手的问题:部分服务是懒加载的,不在启动时创建。这时候依赖图就不完整了。
解决方案:把懒加载的依赖标记为“可选”,解析时单独处理。这样既保证了启动速度,又不会遗漏依赖。
关键概念:依赖解析的完整度取决于构建依赖图时的信息量。只依赖越多,解析越准确,但启动越慢。这是一个需要权衡的地方。
依赖解析有两个常用模式:
| 模式 | 特点 | 适用场景 |
|---|---|---|
| 全量解析 | 启动时一次性解析所有依赖 | 系统启动核心服务(如SystemUI的初始化) |
| 懒加载解析 | 用到时才解析该分支的依赖 | 非核心服务或用户触发才会出现的组件 |
我曾经踩过的坑:有一个面板做了懒加载,但它的依赖里有另一个懒加载组件,两者形成了隐式循环。运行功能时,两个组件互相等待对方创建,界面卡死。从那以后,我坚持在单元测试中构建完整的依赖图并进行环检测。
嗯,到这里你应该明白了。依赖解析不是什么高深技术,核心就是先理清关系,再按序创建。重点在于:依赖图的完整度、环检测的严谨性、创建顺序的正确性。把这三样抓好了,你的依赖解析体系就稳了。