架构决策

有关我们当前架构的概述,请参阅 此文档


这些是在 Fenix 中到目前为止做出的一些主要架构决策。为什么?


概述

一些应用由于没有充分关注合适的 Android 应用架构而遭受了损失。这会导致意大利面条代码、上帝对象和泄漏的抽象,使得测试和维护我们的应用成本更高、更具挑战性。


目标

我们的架构应该

  • 拥有旨在履行单一职责的类,从而分离关注点。

  • 提高开发人员编写有效的自动化单元和 UI 测试的能力。

  • 使大部分应用代码更易于阅读,方便贡献者加入。

  • 提高使用有用的堆栈跟踪调试应用错误的能力。

  • 在需要时易于进行代码重用。

  • 处理 A/B 测试,无需大量代码分支或混乱。


特殊注意事项

作为浏览器,Fenix 需要与 Android 组件和 GeckoView 渲染引擎进行交互。这意味着我们选择的任何架构都不能要求应用的所有组件都以类似的方式实现。此外,我们的应用状态需要与引擎的状态同步,因为由于隐藏的内部细节,两者并不总是完全同步的。


组件架构

背景

布局和 UX 的 A/B 测试是 Fenix 的基本需求。我们有很多假设需要验证,以及关于什么可以使浏览器更好以获取和留住更多用户的实际使用数据。在旧版移动浏览器中做出了许多值得怀疑的决策,这些决策对于移动设备来说似乎并不理想,但我们需要数据来告诉我们我们的假设是否正确。

决策

我们回顾了整个科技行业中公司使用的现代应用架构,并发现了 Netflix 的“组件化”架构。Netflix 特别希望对应用用户界面的许多不同布局进行 A/B 测试,并为此目的构建了其架构。

Netflix 的架构将所有与 UX 相关的代码从活动和片段中移开。相反,片段通过 RxJava 订阅组件,组件自行膨胀。

实际组件的膨胀视图被放入 ConstraintLayouts 中,并通过应用编程约束集连接在一起。ConstraintLayout 和 ConstraintSet 也很好地与新的、功能强大的 MotionLayout 结合,以增强丰富的动画。

以下是 Netflix 的 Juliano Moraes 描述其架构的一些视频

DroidCon NYC 第 1 部分视频

DroidCon SF 第 2 部分视频

这是一个 示例存储库,以非常简单的形式演示了它的功能。

软件架构的目标是最大程度地降低更改成本。此决策可能是降低 Fenix 更改成本的最大因素。它也与 Android 组件项目配合良好,该项目提供了许多将构成此项目的组件。

后果

我们将 UI 打包成可重用且可为 A/B 布局测试重新组合的组件。

我们将所有 UI/UX 代码保留在活动和片段之外,以遵守 单一职责原则。这些类将存在于处理操作系统业务逻辑和绑定组件。

这样做的一个缺点是,让贡献者避免直接从活动和片段中进行 UI/UX 更改的额外成本。另一个是编程布局与预览布局不兼容。我们都需要学习如何有效地使用 ConstraintLayout 和 ConstraintSet。


本地化、MVI (模型-视图-意图) 单向架构

背景

竞争条件是 Android 应用无处不在的祸根。它们通常发生是因为多个系统无法就状态达成一致。

决策

最佳解决方案是让所有状态更改以功能性、反应性的方式从单个真相源流出,并在需要时进行仔细的线程锁定。此解决方案对于任何使用过 Redux 或 Flux 的人都很熟悉。

在每个组件中,我们希望状态更改以循环方式发生。用户界面内容通过将初始 ViewState 显示到屏幕上进行呈现。当用户与应用交互时,Change 对象将传递给状态 reducer。reducer 函数使用请求的更改复制当前不变的状态,并将其传递给模型。视图订阅这些状态更改并以反应方式更新自身。

通过这种方式,应用的 UX 仅从真相源单向流动。由于所有更改都发生在序列化 RxKotlin Observable 中,因此它们将按发生的顺序应用。

但是,与某些 MVI 架构不同,我们不会专注于保留包含所有状态的全局 ViewState。有时我们希望操作系统调用、NDK 调用和第三方组件调用成为真相源。与其尝试创建包含所有状态的单个 ViewState,不如观察所有操作和状态更改的合并可观察对象。这对于调试应用将非常宝贵。

这是一个 MVI 架构的状态图

后果

我们将尝试使用 MVI 单向原则编写新组件。在组件之间传递数据时,我们需要小心,不要覆盖更权威的真相源。

由于所有更改都可以由单个合并和序列化的 Observable 或 Flowable 表示,因此我们应该能够将其用于调试。所有 ViewState、Change 和 Action/Intent 都可以轻松记录以观察状态问题的起因。