基于模块暴露和Hilt的Android模块化方案

ModuleExpose

项目地址:https://github.com/JailedBird/ModuleExpose

序言

Android模块化必须要解决的问题是 如何实现模块间通信 ?而模块之间通信往往需要获取相同的实体类和接口,造成部分涉及模块通信的接口和实体类被迫下沉到基础模块,导致 基础模块代码膨胀、模块代码分散和不便维护等问题;

ModuleExpose方案使用模块暴露&依赖注入框架Hilt的方式,实现模块间通信:

  • 使用模块暴露(模块api化)解决基础模块下沉问题
  • 使用依赖注入框架Hilt实现基于接口的模块解耦方案

简介

ModuleExpose,是将module内部需要暴露的代码通过脚本自动暴露出来;不同于手动形式的接口下沉,ModuleExpose是直接将module中需要暴露的代码完整拷贝到module_expose模块,而module_expose模块的生成、拷贝和配置是由ModuleExpose脚本自动完成,并保证编译时两者代码的完全同步;

最终,工程中包含如下几类核心模块:

  • 基础模块:基础代码封装,可供任何业务模块使用;

  • 业务模块:包含业务功能,业务模块可以依赖基础模块,但无法依赖其他业务模块(避免循环依赖);

  • 暴露模块:由脚本基于业务模块或基础模块自动拷贝生成,业务模块可依赖其他暴露模块(通过compileOnly方式,只参与编译不参与打包),避免模块通信所需的接口、数据实体类下沉到基础模块,造成基础模块膨胀、业务模块核心类分散到基础模块等问题;

注意这种方案并非原创,原创出处如下:

思路原创:微信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是等价的意思;

模块暴露

1、项目启用kts配置

因为脚本使用kts编写,因此需要在项目中启用kts配置;如因为gradle版本过低等原因导致无法接入kts,那应该是无法使用的;后续默认都开启kts,并使用kts语法脚本;

2、导入脚本到gradle目录&修改模板

请拷贝示例工程gradle/expose目录到个人项目gradle目录,拷贝后目录如下:

Path
ModuleExpose\gradlegradle
│  libs.versions.toml
├─expose
│      build_gradle_template_android
│      build_gradle_template_java
│      build_gradle_template_expose
│      expose.gradle.kts
└─wrappergradle-wrapper.jargradle-wrapper.properties

其中:expose.gradle.kts是模块暴露的核心脚本,包含若干函数和配置参数;

其中:build_gradle_template_android和build_gradle_template_java脚本模板因项目不同而有所不同,需要自行根据项目修改,否则无法编译;

  • build_gradle_template_android,生成Android模块的脚本模板,注意高版本gradle必须配置namespace,因此最好保留如下的配置(细则见脚本如何处理的):

    android {namespace = "%s"
    }
    
  • build_gradle_template_java, 生成Java模块的脚本模板,配置较为简单;

  • includeWithApi函数使用build_gradle_template_android模板生成Android Library模块

  • includeWithJavaApi函数使用build_gradle_template_java模板生成Java Library模块

  • build_gradle_template_expose,不同于build_gradle_template_android、build_gradle_template_java的模板形式的配置,使用includeWithApi、includeWithJavaApi时,会优先检查模块根目录是否存在build_gradle_template_expose,如果存在则优先、直接将build_gradle_template_expose内容拷贝到module_expose, 作为build.gradle.kts ! 保留这个配置的原因在于:如果需要暴露的类,引用三方类如gson、但不便将三方库implementation到build_gradle_template_android,这会导致module_expose编译报错,因此为解决这样的问题,最好使用自定义module_expose脚本(拷贝module的配置、稍加修改即可)

    PS:注意这几个模板都是无后缀的,kts后缀文件会被IDE提示一大堆东西;

注意: Java模块编译更快,但是缺少Activity、Context等Android环境,请灵活使用;当然最灵活的方式是为每个module_expose单独配置build_gradle_template_expose (稍微麻烦一点);另外,如果不用includeWithJavaApi,其实build_gradle_template_java也是不需要的;

3、settings.gradle.kts导入脚本函数

根目录settings.gradle.kts配置如下:

apply(from = "$rootDir/gradle/expose/expose.gradle.kts")
val includeWithApi: (projectPaths: String) -> Unit by extra
val includeWithJavaApi: (projectPaths: String) -> Unit by extra

(PS:只要正确启用kts,settings.gradle应该也是可以导入includeWithApi的,但是我没尝试;其次老项目针对ModuleExpose改造kts时,可以渐进式改造,即只改settings.gradle.kts即可)

4、模块配置

将需要暴露的模块,在settings.gradle.kts 使用includeWithApi(或includeWithJavaApi)导入;

includeWithApi(":feature:settings")
includeWithApi(":feature:search")

即可自动生成新模块 ${module_expose};然后在模块源码目录下创建名为expose的目录,将需要暴露的文件放在expose目录下, expose目录下的文件即可在新模块中自动拷贝生成;

生成细则:

1、 模块支持多个expose目录(递归、含子目录)同时暴露,这可以避免将实体类,接口等全部放在单个expose,看着很乱

2、 expose内部的文件,默认全部复制,但脚本提供了开关,可以自行更改并配置基于文件名的拷贝过滤;

5、使用module_expose模块

请使用 compileOnly 导入项目,如下:

compileOnly(project(mapOf("path" to ":feature:search_expose")))

错误:会导致资源冲突

implementation(project(mapOf("path" to ":feature:search_expose")))

原理解释:compileOnly只参与编译,不会被打包;implementation参与编译和打包;

因此search_expose只能使用compileOnly导入,确保解耦的模块之间可以访问到类引用,但不会造成打包时2个类相同的冲突问题;

依赖注入

基于模块暴露的相关接口,可以使用依赖注入框架Hilt实现基于接口的解耦; 当然如果大家不使用Hilt技术栈的话,这节可以跳过;

本节内容会以业务模块search和settings为例,通过代码展示:

  • search模块跳转到settings模块,打开SettingsActivity
  • settings模块跳转到search模块,打开SearchActivity

PS:关于Hilt的配置和导入,本项目直接沿用nowinandroid工程中build-logic的配置,具体配置和使用请参考本项目和nowinandroid项目;

1、 基本配置&工程结构:

在这里插入图片描述

导入脚本之后,使用includeWithApi导入三个业务模块,各自生成对应的module_expose;

注意,请将*_expose/添加到gitignore,避免expose模块提交到git

2、 业务模块接口暴露&实现

settings模块expose目录下暴露SettingExpose接口, 脚本会自动将其同步拷贝到settings_expose中对应expose目录

在这里插入图片描述

exposeimpl/SettingExposeImpl实现SettingExpose接口的具体功能,完善跳转功能

class SettingExposeImpl @Inject constructor() : SettingExpose {override fun startSettingActivity(context: Context) {SettingsActivity.start(context)}
}

3、 Hilt添加注入接口绑定

使用Hilt绑定全局单例SettingExpose接口实现,其对应实现为SettingExposeImpl

在这里插入图片描述

4、 search模块compileOnly导入settings_expose

compileOnly(projects.feature.settingsExpose)

注意,模块暴露依赖只能使用compileOnly,保证编译时候能找到对应文件即可;另外projects.feature.settingsExpose这种项目导入方式,需要在settings.gradle.kts启用project类型安全配置;

 enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

5、 search注入并使用SettingExpose

@AndroidEntryPoint
class SearchActivity : AppCompatActivity() {@Injectlateinit var settingExpose: SettingExposeprivate val listener = object : AppSettingsPopWindow.Listener {override fun settings() {settingExpose.startSettingActivity(this@SearchActivity)}}
}

6、 实现解耦

最终实现【search模块跳转到settings模块,打开SettingsActivity】, 至于【settings模块跳转到search模块,打开SearchActivity】的操作完全一致,不重复叙述了;

参考资料

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/229634.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣hot100 最大子数组和 动态规划 分治 无后效性 子问题划分

👨‍🏫 题目地址 无后效性 为了保证计算子问题能够按照顺序、不重复地进行,动态规划要求已经求解的子问题不受后续阶段的影响。这个条件也被叫做「无后效性」。换言之,动态规划对状态空间的遍历构成一张有向无环图,遍…

SpringBoot+网易邮箱登录注册

文章目录 SpringBoot网易邮箱登录注册pom.xmlapplication.ymlsqlUserEmail.javaUserEmailMapper.javaUserEmailMapper.xmlEmailService.javaUserEmailService.javaUserEmailServiceImpl.javaUserEmailController.javaregister1.html 编写前参考 SpringBoot网易邮箱登录注册 po…

vscode注释插件「koroFileHeader」

前言 在vscode上进行前端开发,有几个流行的注释插件: Better CommentsTodo TreekoroFileHeaderDocument ThisAuto Comment Blocks 在上面的插件中我选择 koroFileHeader 做推荐,原因一是使用人数比较多(最多的是 Better Commen…

【Java】文件I/O-文件系统操作

导读 在文件I/O这一节的知识里,对文件的操作主要分为两大类: ☑️针对文件系统进行的操作 ☑️针对文件内容进行的操作 针对文件系统进行的操作主要是对Java中的File类进行操作。这篇文章里,博主就来带大家看看Java中针对文件系统的一些常…

echarts修改tooltip默认的圆点图标为其他样式

业务需求,默认是圆点,需要把线的由圆点改为线 红色线是理论,点是历史理论,绿色线是实际, 点是历史实际,在series里的顺序也是这样排的。 打印出来的params里的marker就是圆点,改这段代码就可以了…

电脑格式化了怎么恢复原来的数据?您可以这样做

电脑是我们日常生活和工作中不可或缺的工具,然而,在一些情况下我们可能需要进行电脑格式化,比如为了清理系统垃圾、解决系统故障等。然而,格式化会导致所有数据被删除,给用户带来不便和困扰。本文将介绍电脑格式化了怎…

开发知识点-CSS样式

CSS样式 fontCSS 外边距 —— 围绕在元素边框的空白区域# linear-gradient() ——创建一个线性渐变的 "图像"# transform ——旋转 元素![在这里插入图片描述](https://img-blog.csdnimg.cn/20191204100321698.png)# rotate() [旋转] # 边框 (border) —— 围绕元素内…

nnUNet保姆级使用教程!从环境配置到训练与推理(新手必看)

文章目录 写在前面nnUNet是什么?一、配置虚拟环境二、安装nnUNet框架1.安装nnUNet这一步我遇到的两个问题: 2.安装隐藏层hiddenlayer(可选) 三、数据集准备nnUNet对于你要训练的数据是有严格要求的,这第一点就体现在我…

第三方发起备份的ORA-00245问题

文章目录 前言一、信息确认共享目录位置控制文件快照位置节点1节点2 二、RAC修改snapshot controlfile 参数三、字典表确认以及测试 前言 在使用 AnyBackup 管理控制台发起 Oracle RAC 数据库备份后,在任务历史记录 > 执行输出中显示如下错误信息: c…

深度视觉目标跟踪进展综述

1 引言 目标跟踪旨在基于初始帧中指定的感兴趣目标( 一般用矩形框表示) ,在后续帧中对该目标进行持续的定位。 基于深度学习的跟踪算法,采用的框架包括相关滤波器、分类式网络、双路网络等。 处理跟踪任务的角度,分为基于匹配思路的双路网…

RFID资产管理系统全功能详解!高效管理从这里开始!

在现代商业环境中,RFID资产管理系统正成为企业管理不可或缺的先进工具。现代企业管理正处于数字化的浪潮中,而RFID资产管理系统正是这场浪潮中的一颗璀璨明珠。在这篇文章中,我们将全方位解析RFID资产管理系统的功能,助您深入了解…

分析:为什么有些pdf打开之后无法编辑?

pdf文件大家应该都经常接触,但是不知道大家会遇到这种情况:有些PDF文件打开之后无法编辑?是什么原因呢?今天我们来分析一下都是那些原因导致的。 首先我们可以考虑一下,PDF文件中的内容是否是图片,如果确认…