专栏名称: Android_开发者
目录
相关文章推荐
51好读  ›  专栏  ›  Android_开发者

[译] 格子拼贴 — 关于模块化的故事

Android_开发者  · 掘金  · android  · 2019-03-06 12:19

正文

阅读 21

[译] 格子拼贴 — 关于模块化的故事

插图来自 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 数据。

这意味每一位用户都能为其它应用预留更多空间。 同时下载时间也因文件大小缩小而改善。

无需修改任何一行代码即可实现这一大幅度改进。

实现模块化

我们为实现模块化所选的方法:

  1. 将所有代码和资源块移动到核心模块中。
  2. 识别可模块化功能。
  3. 将相关代码和资源移动到功能模块中。

绿色:动态功能 | 深灰色:应用模块 | 浅灰色:库

上面图表向我们展示了 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 这些层级组成。

数据源不但显示在主要功能提示中,也显示在与对应功能模块本身相关详情页中。域名层级在一个单一包中唯一。它必须分为两部分:一部分在应用中共享,另一部分仅用在一个功能模块中。

可复用部分被保存在核心模块,其它所有内容都在各自功能模块。数据层和大部分域名层至少与其它一个模块共享,并且同时也保存在核心模块。







请到「今天看啥」查看全文