Android 官方应用模块化方案解读!
The following article is from Rethink Android Author madroid
https://developer.android.com/topic/modularization
模块化解决了什么问题?
什么是模块化?
注::app:phone 是模块名,app表示是子目录的形式,具体可以参考我给 Now In Android 提交的 PR。 https://github.com/android/nowinandroid/pull/241
模块化有哪些好处?
模块化常见的误区
太细粒度:太细粒度就意味着项目中会有很多模块,而多模块又会导致编译耗时以及多模块配置同步的问题;
太粗粒度:太粗粒度就意味着项目中会有很少模块,基本不能完全发挥出模块化的好处;当做这是一个循序渐进的过程,太粗粒度可以是一个开始不应该是一个结束; 太复杂:将项目模块化并不总是有意义的。如果在可预见的未来项目的增长并不明确,保持现状也是一种不错的选择。
高内聚低耦合原则
低耦合模块不应该了解其他模块的内部工作原理,这意味着模块应该尽可能地相互独立,以便对一个模块的更改对其他模块的影响为零或最小。
高内聚意味着模块应该仅包含相关性的代码。在一个示例电子书应用程序,将书籍和支付的代码混合在同一个模块中可能是不合适的,因为它们是两个不同的功能领域。
小结
模块化就是一种将复杂问题拆解成多个简单问题的工程化方案。所以如果你觉得项目还没有那么复杂,引入模块化的收益将没有那么明显。这里的复杂性包括多 App 复用、严格的代码可见性以及 Google Paly 的动态下发(Play Feature Delivery)。当然,如果希望在可扩展性、所有权、封装或构建时间中受益,那么模块化是值得考虑的事情。
App 模块
应用程序模块是应用程序的入口点。它们依赖于特性(feature)模块,通常提供导航能力。使用多渠道打包方案,单个应用程序模块可以编译为许多不同的二进制文件。
https://developer.android.com/studio/build/build-variants?hl=zh-cn
特性(Feature)模块
数据(Data)模块
封装某个领域的所有数据和业务逻辑:每个数据模块应该负责处理代表某个领域的数据。它可以处理多种相关类型的数据。
将 Repository 公开为外部 API:数据模块的公共 API 应该是 Repository,因为它们负责将数据公开给 App 的其余部分。
对外隐藏所有实现细节和 DataSource:DataSource 只能由同一模块的 Repository 访问,对外是隐藏的状态。可以通过使用 Kotlin 的 private 或者 internal 关键字来强制执行此操作。
公共(Common)模块
基础 UI 模块:如果 App 中使用自定义 View 和样式(style),应该考虑将他们统一封装到一个模块中,以便可以复用。也就是大家通常所说的 UI 规范库,这可以使 UI 在不同特性模块之间保持一致。 打点统计模块:打点统计模块,一般是使用市面上现有的 SDK,当然也有自研的。取决于项目需要。 网络模块:网络库模块,通常是对三方网络库(如 OhHttp)的封装,简化自定义配置时,减少不必要的重复代码。 工具模块:工具类,也称为帮助类,通常是在应用程序中重用的小段代码。如文件读写、电子邮件验证器或自定义运算符等。
注:此部分结合自身经验以及官方文档整合而得,请批判性观看。
抽离基础模块
分别在 :feature:home 与 :feature:checkout 设置对基础依赖模块的初始化操作,如接口实现、回调监听等;
在 :feature:home 模块内通过依赖的 :data:books模块,调用其 navigate() 方法跳转至 :feature:checkout 模块;
在 :feature:books 模块内将跳转事件通过 onNavigationBook 分发出去,由 :feature:checkout模块模块实现; :feature:home 模块通过 :data:books模块提供的 onPaymentCanceled() 回调来监听对应的结果。
依赖调用者模块
:app 模块调用 :feature:home提供的 navigate 函数跳转至 home 页面,并通过 onCheckout 函数将对应的结果回调出去; :app 模块监听到 onCheckout() 回调后,调用 :feature:checkout模块提供 navigate 函数进行跳转,并通过 onPaymentCanceled() 回调将结果抛出。
依赖管理框架
依赖注入:依赖注入使类能够定义其依赖项而不构造它们。在运行时,另一个类负责提供这些依赖项。一般是使用注解方式,适合大中型项目,如 Hilt;
服务查找:服务查找的方式一般是维护一个注册表,所需的依赖都可以在这个注册表中查找;一般是使用相对简单,适合中小型项目,如 koin;
数据层可以将对应的数据注入到 DI 容器中;
在特性模块中可以获取到数据层提供的数据,同时也可以将自身的数据注入到 DI 中; 在 app 模块获取所需的数据或特性功能。
小结
对于前者有各种路由框架,Android 官方也提供了 Navigation 库;对于后者也有不少框架,如 Dagger2、Koin,Android 官方也提供了 hilt 库;当然社区中也有两者都能满足的库,如阿里的 ARouter。
https://github.com/alibaba/ARouter
官方文档中只是提到了比较原始的方式,也是对初学者比较友好的方式。大家可以根据自己项目中的现状选择适合自己的即可。
保持配置一致
• 使用 version catalog 来来统一各模块中依赖的版本号;
• 使用 约定插件 在模块之间共享 build.gradle 中的构建逻辑。
https://docs.gradle.org/current/samples/sample_convention_plugins.html
尽量少暴露
尽量使用 Kotlin 和 Java 模块
• 库模块 依赖项与应用程序模块具有相同的内容。它们被其他 Android 模块用作依赖项。库模块的输出是一个 Android Archive (AAR),在结构上与应用程序模块相同,但它们被编译为一个 Android Archive (AAR) 文件,以后可以被其他模块用作。库模块可以在许多应用程序模块中封装和重用相同的逻辑和资源。
https://developer.android.com/studio/projects/android-library?hl=zh-cn
https://developer.android.com/studio/build/dependencies?hl=zh-cn
由于 Android 模块会带来开销,因此您最好尽可能使用 Kotlin 或 Java 类型。
以上内容是根据官方文档整理而得,对部分内容做了结构调整、重新绘制了 UML 图以及添加了些自己的经验感悟。对原文整理会存在疏忽遗漏的部分,请大家到官方文档中查看,做到“交叉验证”。
对于初学者而言,有一套相对详细指导文档并且完整示例的项目(Now in Android)可以参考,从而可以快速搭建模块化的项目; 对于已经实践过模块化项目团队,我相信仍能从官方文章中学习到一些新思路及方法,以复盘的视角审视自己团队中的模块化方案的优劣;
当然,模块化本身并不是终点。模块化之后还有组件化,组件化之后还有壳工程和动态化。每个技术阶段对应到团队发展的阶段,那些适合目前团队现状的技术才是”好“技术。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!