Android 官方模块化计划解读
前言
前不久整理下 Now In Android 项目是如何做模块化的(Android 官方项目是怎么做模块化的?快来学习下),没想到官方不久前也在官方文档中更新了模块化相关的章节,下面就一同看一下官方文档中是如何描绘 Android App 模块化的。
概述
首要思考下,为什么要做模块化或许说假如不做模块化会有什么问题?
模块化处理了什么问题?
跟着项目以及事务的不断迭代,整个项目中代码数量会不断的增加,在这个进程中代码的可扩展性、可读性都会跟着时刻的推移而下降。
处理这个问题办法大致有两种,一种是定期 Review 代码架构并做一些防劣化办法,然后保证项目的质量不会跟着项目的增加而下降。可是这种办法在需求快速迭代的团队中因为工期及人力投入的原因是很难被执行的。别的便是需求团队中有能够敏锐发现代码劣化倾向的人,然后发起 Review,这个人物一般由技能专家或许是架构师承当。这种办法可操作性并不高。
别的一种处理思路便是将杂乱问题拆解成多个小的、简略问题,而对简略问题的处理一般并不需求特别依靠高级人才。这种办法便是分而治之,将大型、杂乱问题拆解成一个个小的、简略问题,然后能够做到各个击破。这种办法对应的工程手法之一便是模块化。
什么是模块化?
模块化简略讲便是把多功用、高耦合的代码逻辑拆散成多个功用单一、责任清晰的模块(module)。一个项目模块化后的全体架构大致如下:
注:
:app:phone
是模块名,app表示是子目录的办法,详细能够参阅我给 Now In Android 提交的PR 241。
模块化有哪些长处?
模块化的长处有很多,主要集中表现在提高代码库的可保护性和全体质量上。下表总结了主要优势。
长处 | 归纳 |
---|---|
多 App复用 | 模块化是在多 App 开发中复用代码逻辑的根底。每个模块都是一个独立有效的构建单元。 |
严厉的拜访权限 | 模块能够很好做操控代码的可拜访性,模块内部私有的逻辑增加internal 或许private 修饰。防止代码被其他模块引证而导致的过度耦合。 |
可定制的交给 | 能够运用动态下发(Play Feature Delivery)功用(注:国内运用商店根本不可用) 。 |
上述长处只能经过模块化才能完成。以下是不运用模块化也能完成的长处,但模块化能够更好地完成。
长处 | 归纳 |
---|---|
可扩展性 | 在代码紧密耦合的代码仓库中,一个微小的更改都有或许导致牵一发动全身。一个好的模块化的项目会做到代码解耦(关注点别离准则),然后规避了上述问题。 |
担任人 | 一个模块能够有一个专门的担任人,担任保护代码、修正过错、增加测验和 CodeReview 。方便代码与人员的两层办理。 |
封装 | 封装意味着代码的每一部分都应该对其他部分有尽或许少的了解(最少知道准则)。孤立的代码更简单阅读和理解。 |
可测验性 | 可测验性描绘了测验代码的难易程度。可测验代码是能够轻松独立测验组件的代码。小类总比大型类简单测验,依靠少的类总比依靠多的类简单测验。 |
构建时刻 | 模块化能够提升编译速度,例如增量构建、构建缓存或并行构建。 |
模块化常见的误区
任何一项技能都有好坏,模块化也是如此。对模块化运用不当,或许也会引进一些问题。一些常见的问题如下:
-
太细粒度:太细粒度就意味着项目中会有很多模块,而多模块又会导致编译耗时以及多模块装备同步的问题;
-
太粗粒度:太粗粒度就意味着项目中会有很少模块,根本不能完全发挥出模块化的长处;当做这是一个按部就班的进程,太粗粒度能够是一个开端不应该是一个结束;
-
太杂乱:将项目模块化并不总是有意义的。假如在可预见的未来项目的增加并不清晰,坚持现状也是一种不错的挑选。
高内聚低耦合准则
没有合适所有项目的模块化计划。下面讲下模块化开发进程中能够采用的一些一般规矩和常见办法。
高内聚低耦合是模块化项目的一种特点。耦合衡量模块彼此依靠的程度,内聚衡量单个模块的元素在功用上的相关性。应该争夺低耦合和高内聚:
-
低耦合模块不应该了解其他模块的内部工作原理,这意味着模块应该尽或许地彼此独立,以便对一个模块的更改对其他模块的影响为零或最小。
-
高内聚意味着模块应该仅包括相关性的代码。在一个示例电子书运用程序,将书本和支付的代码混合在同一个模块中或许是不合适的,因为它们是两个不同的功用范畴。
假如两个模块严峻依靠彼此,那么它们实际上应该作为一个体系运转。相反,假如一个模块的两个部分不常常交互,它们或许应该是独自的模块。
小结
模块化便是一种将杂乱问题拆解成多个简略问题的工程化计划。所以假如你觉得项目还没有那么杂乱,引进模块化的收益将没有那么显着。这儿的杂乱性包括多 App 复用、严厉的代码可见性以及 Google Paly 的动态下发(Play Feature Delivery
)。当然,假如期望在可扩展性、所有权、封装或构建时刻中获益,那么模块化是值得考虑的工作。
模块的类型
App 模块
运用程序模块是运用程序的进口点。它们依靠于特性(feature)模块,一般供给导航才能。运用多渠道打包计划,单个运用程序模块能够编译为许多不同的二进制文件。
如,依据运用用处能够分为正式版别 App、 测验 Demo App,其中正式版别 App 依据其发布平台又能够分为 智能手机、轿车、电视、可穿戴设备等,其依靠关系大致如下:
特性(Feature)模块
特性是 App 中功用相对独立的部分,一般对包括一个页面或一系列密切相关的页面,例如注册或结帐流程。假如您的运用具有底部栏导航,则很或许每个目的地都是一项功用。
特性模块中一般会包括页面或路由(destinations)。因而,在模块内部需求处理 UI Layer 中相关的内容。特性模块中不用局限于单个页面或导航目的地,能够包括多个页面。特性模块依靠于数据模块。
数据(Data)模块
数据模块一般包括Repository、DataSource和实体类。数据模块主要有三个责任:
- 封装某个范畴的所有数据和事务逻辑:每个数据模块应该担任处理代表某个范畴的数据。它能够处理多种相关类型的数据。
- 将 Repository 公开为外部API:数据模块的公共 API 应该是 Repository,因为它们担任将数据公开给 App 的其余部分。
-
对外隐藏所有完成细节和 DataSource:DataSource 只能由同一模块的 Repository 拜访,对外是隐藏的状况。能够经过运用 Kotlin 的
private
或许internal
关键字来强制执行此操作。
公共(Common)模块
公共模块,也称为中心模块或许根底模块,包括其他模块常常运用的代码。以下是常用模块的示例:
- 根底UI模块:假如 App 中运用自定义 View 和样式(style),应该考虑将他们统一封装到一个模块中,以便能够复用。也便是我们一般所说的 UI 规范库,这能够使 UI 在不同特性模块之间坚持共同。
- 打点统计模块:打点统计模块,一般是运用市面上现有的 SDK,当然也有自研的。取决于项目需求。
- 网络模块:网络库模块,一般是对三方网络库(如 OhHttp)的封装,简化自定义装备时,削减不用要的重复代码。
- 东西模块:东西类,也称为协助类,一般是在运用程序中重用的小段代码。如文件读写、电子邮件验证器或自定义运算符等。
App 模块化全体汇总办法大致如下:
\
模块间通讯
注:此部分结合本身经历以及官方文档整合而得,请批判性观看。
项目尽管采用了模块化办法进行开发,削减了代码之间的耦合,可是模块间的通讯仍是不可避免的工作。模块间彼此依靠的办法在工程上并不可行,Android 项目并不答应模块间的彼此依靠。一般的做法便是引进第三个中介模块,模块间经过中介模块来进行通讯。
中介模块在依靠办法上有能够分为两种,一种是向下抽象,抽离出两个模块共有的数据层逻辑,模块经过回调或许是数据流的办法监听逻辑的改变;另一种办法是抽象,在宿主 App 模块中组合拼装两个模块的逻辑。前者是下沉逻辑,后者是操控回转。
下面我以一个简略的事务场景举例:在购书本列表页面,挑选特定的一本书并下单购买。
抽离根底模块
大致流程如下:
- 分别在
:feature:home
与:feature:checkout
设置对根底依靠模块的初始化操作,如接口完成、回调监听等;
- 在
:feature:home
模块内经过依靠的:data:books
模块,调用其navigate()
办法跳转至:feature:checkout
模块;
- 在
:feature:books
模块内将跳转事情经过onNavigationBook
分发出去,由:feature:checkout
模块模块完成; -
:feature:home
模块经过:data:books
模块供给的onPaymentCanceled()
回调来监听对应的结果;
这种通讯办法跟着事务的迭代,底层通用的数据模块会不断膨胀,耦合也会越加严峻,所以并不建议运用此办法。官方文档中示例办法则是交由调用者处理,各自模块也相对内聚。
依靠调用者模块
这种办法一般是依靠 app 模块来组装各个事务模块的事务逻辑,也便是相同意义上的胶水代码。大致办法如下:
-
:app
模块调用:feature:home
供给的 navigate 函数跳转至home
页面,并经过 onCheckout 函数将对应的结果回调出去; -
:app
模块监听到onCheckout()
回调后,调用:feature:checkout
模块供给 navigate 函数进行跳转,并经过onPaymentCanceled()
回调将结果抛出;
此种办法使得各事务模块的逻辑愈加内聚,尽管这种办法的结果及事情也能很好的露出出去。可是假如这种办法在大型项目中运用时会导致产生大量的胶水代码(频频的初始化以及 Callback 设置),不利于项目中后续迭代。为了处理胶水代码问题,能够在项目中引进依靠办理的结构。
依靠办理结构
依靠办理不仅能够很好地处理对象繁琐的初始化逻辑,还能够很好的施行操控回转的编码思想。目前干流的依靠办理的计划有两种,分别为依靠注入与服务查找:
-
依靠注入:依靠注入使类能够定义其依靠项而不结构它们。在运转时,另一个类担任供给这些依靠项。一般是运用注解办法,合适大中型项目,如Hilt;
-
服务查找:服务查找的办法一般是保护一个注册表,所需的依靠都能够在这个注册表中查找;一般是运用相对简略,合适中小型项目,如koin;
官方引荐运用Hilt
来进行依靠办理,假如你的项目中在运用其他的依靠办理办法,而且没有遇到问题的话,那么持续运用当前的结构即可。
依靠办理的办法不仅能够运用模块间通讯,在模块内部通讯也是一种很好的解耦与复用的手法,只是在模块间通讯会流程变得愈加杂乱,也更能杰出依靠办理的重要性。整个依靠办理在模块化全体架构大致如下图:
以服务查找办法完成,其大致流程(忽略模块内通讯)如下:
- 数据层能够将对应的数据注入到 DI 容器中;
- 在特性模块中能够获取到数据层供给的数据,一起也能够将本身的数据注入到 DI 中;
- 在 app 模块获取所需的数据或特性功用;
小结
其实整个模块间的通讯能够按照其行为办法分为两大类,一种是不同模块间页面直接的跳转,另一种则是不同模块间的数据交互。
关于前者有各种路由结构,Android 官方也供给了 Navigation 库;关于后者也有不少结构,如 Dagger2、Koin,Android 官方也供给了 hilt 库;当然社区中也有两者都能满足的库,如阿里的ARouter。
官方文档中只是提到了比较原始的办法,也是对初学者比较友爱的办法。我们能够依据自己项目中的现状挑选合适自己的即可。
最佳实践
尽管开发模块化 App 没有唯一正确的办法,可是以下的一些建议仍能够使代码更具可读性、可保护性和可测验性。
坚持装备共同
每个模块都会引进装备开支。当模块数量到达某个阈值,则办理共同的装备将成为一项应战。下面的装备能够削减这部分的工作量:
- 运用version catalog来来统一各模块中依靠的版别号;
- 运用约好插件在模块之间同享
build.gradle
中的构建逻辑。
尽量少露出
模块的公共接口应该是最小的,而且仅仅只公开必需公开的。它不应该在外面露出任何完成细节。尽或许的缩小外部调用者的可拜访规模,运用 Kotlin 的private
或internal
能够很好的做到这一点。在模块中声明依靠项时引荐运用implementation
,而非api
。implementation
不会透传依靠,然后能够做到缩短构建时刻。
尽量运用 Kotlin 和 Java 模块
Android Studio 支持三种根本类型的模块:
-
运用程序模块
AndroidManifest.xml
是您的运用程序的进口点。它们能够包括源代码、资源、财物和. 运用模块的输出是一个 Android App Bundle (AAB) 或一个 Android 运用程序包 (APK)。
- 库模块 依靠项与运用程序模块具有相同的内容。它们被其他 Android 模块用作依靠项。库模块的输出是一个 Android Archive (AAR),在结构上与运用程序模块相同,但它们被编译为一个 Android Archive (AAR) 文件,以后能够被其他模块用作。库模块能够在许多运用程序模块中封装和重用相同的逻辑和资源。
- Kotlin 和 Java 库不包括任何 Android 资源、财物或清单文件。
因为 Android 模块会带来开支,因而您最好尽或许运用 Kotlin 或 Java 类型。
总结
以上内容是依据官方文档整理而得,对部分内容做了结构调整、从头绘制了 UML 图以及增加了些自己的经历感悟。对原文整理会存在忽略遗漏的部分,请我们到官方文档中检查,做到“交叉验证”。
假如你想快速建立一个全新的 Android 模块化项目,能够到 architecture-templates 仓库中 clone,能够说是非常便捷了。
尽管模块化技能在国内并算不上什么新技能了,可是我仍然看到了一些积极的影响:
- 关于初学者而言,有一套相对详细指导文档而且完好示例的项目(Now in Android)能够参阅,然后能够快速建立模块化的项目;
- 关于已经实践过模块化项目团队,我相信仍能从官方文章中学习到一些新思路及办法,以复盘的视角审视自己团队中的模块化计划的优劣;
当然,模块化本身并不是结尾。模块化之后还有组件化,组件化之后还有壳工程和动态化。每个技能阶段对应到团队开展的阶段,那些合适目前团队现状的技能才是”好“技能。