-
Notifications
You must be signed in to change notification settings - Fork 1
1、模块暴露
项目地址:https://github.com/JailedBird/ModuleExpose
Android模块化必须要解决的问题是 如何实现模块间通信 ?而模块之间通信往往需要获取相同的实体类和接口,造成部分涉及模块通信的接口和实体类被迫下沉到基础模块,导致 基础模块代码膨胀、模块代码分散和不便维护等问题;
ModuleExpose方案使用模块暴露&依赖注入框架Hilt的方式,实现模块间通信:
- 使用模块暴露(模块api化)解决基础模块下沉问题
- 使用依赖注入框架Hilt实现基于接口的模块解耦方案
ModuleExpose,是将module内部需要暴露的代码通过脚本自动暴露出来;不同于手动形式的接口下沉,ModuleExpose是直接将module中需要暴露的代码完整拷贝到module_expose模块,而module_expose模块的生成、拷贝和配置是由ModuleExpose脚本自动完成,并保证编译时两者代码的完全同步;
最终,工程中包含如下几类核心模块:
-
基础模块:基础代码封装,可供任何业务模块使用;
-
业务模块:包含业务功能,业务模块可以依赖基础模块,但无法依赖其他业务模块(避免循环依赖);
-
暴露模块:由脚本基于业务模块或基础模块自动拷贝生成,业务模块可依赖其他暴露模块(通过compileOnly方式,只参与编译不参与打包)
如下图所示:
P1:模块化开发常见项目架构;问题:feature业务模块之间无法直接通信;
P2:通常采用代码下沉的方式,实现模块直接的通信;缺陷:模块通信所需的接口、数据实体类下沉到基础模块,造成基础模块膨胀、业务模块核心类分散到基础模块、难以维护等问题;
P3: MoudleExpose方案,脚本自动暴露业务模块的通信代码,其他模块可compileOnly依赖暴露模块;小缺陷:单模块2~50毫秒级额外耗时(和暴露文件成正比,完全可接受),优势:自动管理无侵入、轻松打破模块隔离、且能保证编译时类型安全,避免了模块下沉的问题;
注意这种方案并非原创,原创出处如下:
思路原创:微信Android模块化架构重构实践
先寻找代码膨胀的原因。
翻开基础工程的代码,我们看到除了符合设计初衷的存储、网络等支持组件外,还有相当多的业务相关代码。这些代码是膨胀的来源。但代码怎么来的,非要放这?一切不合理皆有背后的逻辑。在之前的架构中,我们大量使用Event事件总线作为模块间通信的方式,也基本是唯一的方式。使用Event作为通信的媒介,自然要有定义它的地方,好让模块之间都能知道Event结构是怎样的。这时候基础工程好像就成了存放Event的唯一选择——Event定义被放在基础工程中;接着,遇到某个模块A想使用模块B的数据结构类,怎么办?把类下沉到基础工程;遇到模块A想用模块B的某个接口返回个数据,Event好像不太适合?那就把代码下沉到基础工程吧……
就这样越来越多的代码很“自然的”被下沉到基础工程中。
implementation工程提供逻辑的实现。api工程提供对外的接口和数据结构。library工程,则提供该模块的一些工具类。
项目原创: github/tyhjh/module_api
如果每次有一个模块要使用另一个模块的接口都把接口和相关文件放到公共模块里面,那么公共模块会越来越大,而且每个模块都依赖了公共模块,都依赖了一大堆可能不需要的东西;
所以我们可以提取出每个模块提供api的文件放到各种单独的模块里面;比如user模块,我们把公共模块里面的User和UserInfoService放到新的user-api模块里面,这样其他模块使用的时候可以单独依赖于这个专门提供接口的模块,以此解决公共模块膨胀的问题
本人工作:
- 使用kts和nio重写脚本,基于性能的考量,对暴露规则和生成方式进行改进;详见 关于性能
- 将nowinandroid项目编译脚本系统、Ksp版本的Hilt依赖注入框架、示例工程三者结合起来,完善基于 模块暴露&依赖注入框架 的模块解耦示例工程;
- 将api改名expose(PS:因内部项目使用过之前的api方案,为避免冲突所以改名,也避免和大佬项目名字冲突😘 脚本中亦可自定义关键词)
术语说明:
- 部分博客中称这种方式为模块api化,我觉得这是合理的;本文的语境中的expose和api是等价的意思;
目前很多项目使用Arouter等路由组件实现模块间通信;以Arouter为例和Module Expose方案对比吧;
1、 便捷性
Arouter等路由组件除提供路由跳转等功能之外,还提供拦截器等功能;而ModuleExpose方案可以简单理解为自动暴露接口的工具,本身不提供其他能力;因此在便捷性方面是不如这些组件的;
2、 兼容性
Arouter等技术栈几乎都是基于注解处理器和Transform插桩方式;前者,很少有支持kotlin注解处理器ksp的(使用kapt会严重影响项目编译),而后者,很多并未适配AGP8,导致无法升级gradle8;更尴尬的是Arouter 很早就停止维护了,如果你想升级,那么很抱歉官方不支持(三方社区有一些方案),如果你想移除,因为Arouter的开发模式和代码侵入性等原因,改动会非常大,几乎不可能(根据能跑就不要动的原则,问就是不想背锅🤣);
ModuleExpose则是最为朴素的方案,不会对代码造成任何侵入;他甚至没提供具体的模块化通信方案,开发者可以自行根据情况选择 例如hilt、koin等依赖注入(基于接口),甚至你可以使用单例🤣🤣🤣
兼容性方案,他涉及kts和groovy的脚本开发(和Transform等插件开发还是存在相当大的区别),主要操作是NIO文件操作和project路径相关操作,代码行约在400以内,任何三年经验老鸟都可自行DIY,完全绕过Gradle不兼容的坑;
3、 性能
性能问题,文档上会有详细的描述,项目中也有benchmark测试模块;单个模块,暴露内容不多的情况下,额外损耗可以控制在10ms以内,并且不会破坏其他如Transform等增量编译机制;相比而言,优势非常明显;
4、 关于维护?
脚本几百行,主要涉及文件操作,谁都可以轻松维护😘 文档方面,已经发布到wiki;
1、思路原创:微信Android模块化架构重构实践
2、项目原创:github/tyhjh/module_api
3、脚本迁移:将 build 配置从 Groovy 迁移到 KTS
4、参考文章:Android模块化设计方案之接口API化
5、Nowinandroid:https://github.com/android/nowinandroid
6、Dagger项目:https://github.com/google/dagger
7、Hilt官方教程:https://developer.android.com/training/dependency-injection/hilt-android
基于模块暴露和Hilt的安卓模块通信方案