- 原文地址: Patchwork Plaid — A modularization story
- 原文作者: Ben Weiss
- 译文出自: 掘金翻译计划
- 本文永久链接: github.com/xitu/gold-m…
- 译者: snpmyn
插图来自 Virginia Poltrack
我们为什么以及如何进行模块化,模块化后会发生什么?
这篇文章深入探讨了 Restitching Plaid 模块化部分。
在这篇文章中,我将全面介绍如何将一个整体的、庞大的、普通的应用转化为一个模块化应用束。以下是我们已取得的成果:
- 整体体积减少超过 60%
- 极大地增强代码健壮性
- 支持动态交付、按需打包代码
我们做的所有事情,都不会影响用户体验。
Plaid 初印象
导航 Plaid
Plaid 是一个具有令人感到愉悦的 UI 的应用。它的主屏幕显示的新闻来自多个来源。 这些新闻被点击后展示详情,从而出现分屏效果。 该应用同时具有搜索功能和一个关于模块。基于这些已经存在的特征,我们选择一些进行模块化。
新闻来源(Designer News 和 Dribbble)成为了它自己拥有的动态功能模块。关于和搜索特征同样被模块化为动态功能。
动态功能 允许在不直接于基础应用包含代码情况下提供代码。正因为此,通过连续步骤可实现按需下载功能。
接下来介绍 Plaid 结构
如许多安卓应用一样,Plaid 最初是作为普通应用构建的单一模块。它的安装体积仅 7MB 一下。然而许多数据并未在运行时用到。
代码结构
从代码角度来看,Plaid 基于包从而有明确边界定义。但随大量代码库的出现,这些边界会被跨越且依赖会潜入其中。模块化要求我们更加严格地限定这些边界,从而提高和改善代码分离。
本地库
最大未用到的数据块来自 Bypass ,一个我们用来在 Plaid 呈现标记的库。它包括用于多核 CPU 体系架构的本地库,这些本地库最终在普通应用占大约 4MB 左右。应用束允许仅交付设备架构所需的库,将所需体积减少1MB左右。
可提取资源
许多应用使用栅格化资产。它们与密度有关且通常占应用文件体积很大一部分。应用可从配置应用中受益匪浅,配置应用中每个显示密度都被放在一个独立应用中,允许设备定制安装,也大大减少下载和体积。
Plaid 显示图形资源时,很大程度依赖于 vector drawables 。因这些与密度无关且已保存许多文件,故此处数据节省对我们并非太有影响。
拼贴起来
在模块化中,我们最初把
./gradlew assemble
替换为
./gradlew bundle
。Gradle 现在将生成一个
Android App Bundle
(aab),替换生成应用。一个安卓应用束需用到动态功能 Gradle 插件,我们稍后介绍。
安卓应用束
相对单个应用,安卓应用束生成许多小的配置应用。这些应用可根据用户设备定制,从而在发送过程和磁盘上保存数据。应用束也是动态功能模块先决条件。
在 Google Play 上传应用束后,可生成配置应用。随着 应用束 成为 开放规范 ,其它应用商店也可实现该交付机制。为 Google Play 生成并签署应用,应用必须注册到 由 Google Play 签名的应用程序 。
优势
这种封装改变给我们带来了什么?
Plaid 现在设备减少 60% 以上体积,等同大约 4MB 数据。
这意味每一位用户都能为其它应用预留更多空间。 同时下载时间也因文件大小缩小而改善。
无需修改任何一行代码即可实现这一大幅度改进。
实现模块化
我们为实现模块化所选的方法:
- 将所有代码和资源块移动到核心模块中。
- 识别可模块化功能。
- 将相关代码和资源移动到功能模块中。
绿色:动态功能 | 深灰色:应用模块 | 浅灰色:库
上面图表向我们展示了 Plaid 模块化现状:
-
旁路模块
和外部分享依赖
包含在核心模块当中 -
应用
依赖于核心模块
-
动态功能模块依赖于
应用
应用模块
应用
模块基本上是现存的
应用
,被用来创建应用束且向我们展示 Plaid。许多用来运行 Plaid 的代码没必要必须包含在该模块中,而是可移至其它任何地方。
Plaid 的
核心模块
为开始重构,我们将所有代码和资源都移动至一个
com.android.library
模块。进一步重构后,我们的
核心模块
仅包含各个功能模块间共享所需要代码和资源。这将使得更加清晰地分离依赖项。
外部库
通过
旁路模块
将一个第三方依赖库包含在核心模块中。此外通过 gradle
api
依赖关键字,将所有其它 gradle 依赖从
应用
移动至
核心模块
。
Gradle 依赖声明:api vs implementation_
通过
api
代替
implementation
可在整个程序中共享依赖项。这将减少每一个功能模块体积大小,因本例
核心模块
中依赖项仅需包含在单一模块中。此外还使我们的依赖关系更加易于维护,因为它们被声明在一个单一文件而非在多个
build.gradle
文件间传播。
动态功能模块
上面我提到了我们识别的可被重构为 com.android.dynamic-feature 的模块。它们是:
:about
:designernews
:dribbble
:search
复制代码
动态功能介绍
一个动态功能模块本质上是一个 gradle 模块,可从基础应用模块被独立下载。它包含代码、资源、依赖,就如同其它 gradle 模块一样。虽然我们还没在 Plaid 中使用动态交付,但我们希望将来可减少最初下载体积。
伟大的功能改革
将所有东西都移动至核心模块后,我们将“关于”页面标记为具有最少依赖项的功能,故我们将其重构为一个新的
关于
模块。这包括 Activties、Views、代码仅用于该功能的内容。同样,我们把所有资源例如 drawables、strings 和动画移动至一个新模块。
我们对每个功能模块进行重复操作,有时需要分解依赖项。
最后,核心模块包含大部分共享代码和主要功能。由于主要功能仅显示于应用模块中,我们把相关代码和资源移回
应用
。
功能结构剖析
编译后代码可在包中进行结构优化。强烈建议在将代码分解成不同编译单元前,将代码移动至与功能对应包中。幸运的是我们不用必须重构,因为 Plaid 已很好地对应了功能。
功能和核心模块以及各自体系结构层级
正如我提到的,Plaid 许多功能都通过新闻源提供。它们由远程和本地 data 资源、 domain 、 UI 这些层级组成。
数据源不但显示在主要功能提示中,也显示在与对应功能模块本身相关详情页中。域名层级在一个单一包中唯一。它必须分为两部分:一部分在应用中共享,另一部分仅用在一个功能模块中。
可复用部分被保存在核心模块,其它所有内容都在各自功能模块。数据层和大部分域名层至少与其它一个模块共享,并且同时也保存在核心模块。