依赖解析基础:依赖注入、依赖图与解析原理

好,咱们正式开始啃这块硬骨头。依赖解析,说白了就是帮你理清楚“谁需要谁”。在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 依赖 NavigationModeController
  • NavigationModeController 依赖 DeviceProvisionedController
  • CommandQueue 被多个面板依赖

为什么依赖图必须是无环的? 因为如果A依赖B,B又依赖A,那就成了死循环,谁都没法先创建。

记得我在一次重构中,发现两个服务互相依赖,编译通过了但运行时栈溢出。查了半天,最后用依赖图画出来才发现那个环。

警告:在依赖图中发现环,一定要立即处理。不要想着“运行时可能不会触发”。你永远不知道什么时机触发这个环。

好,接下来我们展示一下依赖解析的核心逻辑。这是本章唯一的SVG图,画的是从被依赖者到最终对象的完整路径。

依赖解析核心流程 被依赖者 依赖者 依赖容器 依赖图 环检测 (拓扑排序) 依赖解析 最终对象 箭头方向:谁依赖谁 依赖者 → 被依赖者 环检测:确保图是DAG 拓扑排序:确定创建顺序

理解这张图:箭头表示“谁依赖谁”。依赖容器负责收集所有依赖关系,构建成图,再进行环检测。只有无环图才能进行后续的解析。

3. 依赖解析原理

依赖解析,就是根据依赖图,按正确的顺序创建所有对象。核心原理其实不复杂,就是拓扑排序。

假设我们有三个组件:A依赖B,B依赖C。解析顺序就是C→B→A。先创建没有依赖的C,再创建依赖C的B,最后创建依赖B的A。

这个过程中,容器需要做两件事:

  1. 分析依赖图:找出所有组件的依赖关系
  2. 确定创建顺序:用拓扑排序得到无环的线性序列

我参与过Android系统的依赖注入框架开发,当时遇到一个棘手的问题:部分服务是懒加载的,不在启动时创建。这时候依赖图就不完整了。

解决方案:把懒加载的依赖标记为“可选”,解析时单独处理。这样既保证了启动速度,又不会遗漏依赖。

关键概念:依赖解析的完整度取决于构建依赖图时的信息量。只依赖越多,解析越准确,但启动越慢。这是一个需要权衡的地方。

依赖解析有两个常用模式:

模式 特点 适用场景
全量解析 启动时一次性解析所有依赖 系统启动核心服务(如SystemUI的初始化)
懒加载解析 用到时才解析该分支的依赖 非核心服务或用户触发才会出现的组件

我曾经踩过的坑:有一个面板做了懒加载,但它的依赖里有另一个懒加载组件,两者形成了隐式循环。运行功能时,两个组件互相等待对方创建,界面卡死。从那以后,我坚持在单元测试中构建完整的依赖图并进行环检测。

嗯,到这里你应该明白了。依赖解析不是什么高深技术,核心就是先理清关系,再按序创建。重点在于:依赖图的完整度、环检测的严谨性、创建顺序的正确性。把这三样抓好了,你的依赖解析体系就稳了。