基于模块暴露和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\gradle

gradle
│  libs.versions.toml
├─expose
│      build_gradle_template_android
│      build_gradle_template_java
│      build_gradle_template_expose
│      expose.gradle.kts
└─wrapper
        gradle-wrapper.jar
        gradle-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() {
    @Inject
    lateinit var settingExpose: SettingExpose
    
    private 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nginx性能调优策略

Nginx是一个高性能的Web服务器和反向代理服务器,常用于处理高并发的请求。以下是一些常见的Nginx性能调优策略: 一、调整worker_processes和worker_connections 在Nginx配置文件中,可以通过worker_processes和worker_connections参数来调整w…

vue2.6源码分析

vue相关文档 vue-cli官方文档 vuex官方文档 vue-router 官方文档 vue2.6源码地址 如何调试源码 package.json 添加了--sourcemap "scripts": {"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap" }新增…

InstructDiffusion-多种视觉任务统一框架

论文:《InstructDiffusion: A Generalist Modeling Interface for Vision Tasks》 github:https://github.com/cientgu/InstructDiffusion InstructPix2Pix:参考 文章目录 摘要引言算法视觉任务统一引导训练集重构统一框架 实验训练集关键点检测分割图像…

0x00000709一键修复的解决办法,0x00000709错误的原因

在使用打印机时,你可能会遇到一些错误代码,其中之一是0x00000709。这个错误代码表示无法设置默认打印机。如果你遇到这样的问题不用担心!这篇文章将为你提供0x00000709一键修复的解决办法,帮助你解决0x00000709错误,并…

对于 ` HttpServletResponse ` , ` HttpServletRequest `我们真的学透彻了吗

对于 **HttpServletResponse , HttpServletRequest**我们真的学透彻了吗 问题引入 PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) {ExcelUtil<SysUser> util new ExcelUtil<SysUser>(SysUser.class);uti…

J-Flash工具的使用---擦除、烧录及校验

文章目录 前言一、打开J-Flash工具二、使用步骤1.创建工程&#xff0c;选择MCU&#xff0c;配置端口2.打开要烧录的文件3.连接J-Link4.擦除Flash5. 烧录固件 总结 前言 不使用IDE&#xff08;如keil、Iar&#xff09;如何来烧录固件。当我们的程序需要保密&#xff0c;不需要被…

YOLOv8改进 | 2023 | DWRSeg扩张式残差助力小目标检测 (附修改后的C2f+Bottleneck)

论文地址&#xff1a;官方论文地址 代码地址&#xff1a;该代码目前还未开源&#xff0c;我根据论文内容进行了复现内容在文章末尾。 一、本文介绍 本文内容给大家带来的DWRSeg中的DWR模块来改进YOLOv8中的C2f和Bottleneck模块&#xff0c;主要针对的是小目标检测&#xff0c…

Vite 了解

1、vite 与 create-vite 的区别 2、vite 解决的部分问题 3、vite配置文件的细节 3.1、vite语法提示配置 3.2、环境的处理 3.3、环境变量 上图补充 使用 3.4、vite 识别&#xff0c;vue文件的原理 简单概括就是&#xff0c;我们在运行 npm润dev 的时候&#xff0c;vite 会搭起…

fastReID论文总结

fastReID论文总结 fastReIDReID所面临的挑战提出的背景概念&#xff1a;所谓ReID就是从视频中找出感兴趣的物体&#xff08;人脸、人体、车辆等&#xff09;应用场景&#xff1a;存在的问题&#xff1a;当前的很多ReID任务可复用性差&#xff0c;无法快速落地使用解决方式&…

EMA训练微调

就是取前几个epoch的weight的平均值&#xff0c;可以缓解微调时的灾难性遗忘&#xff08;因为新数据引导&#xff0c;模型权重逐渐&#xff0c;偏离训练时学到的数据分布&#xff0c;忘记之前学好的先验知识&#xff09; class EMA():def __init__(self, model, decay):self.…

RabbitMQ消息模型之Sample

Hello World Hello World是官网给出的第一个模型&#xff0c;使用的交换机类型是直连direct&#xff0c;也是默认的交换机类型。 在上图的模型中&#xff0c;有以下概念&#xff1a; P&#xff1a;生产者&#xff0c;也就是要发送消息的程序C&#xff1a;消费者&#xff1a;消…

机器学习:领域自适应学习

训练一个分类器是小问题 上难度 训练数据和测试数据不一致&#xff0c;比如训练数据是黑白的&#xff0c;测试时彩色的&#xff0c;结果准确率非常低。 训练数据和测试数据有点差距的时候&#xff0c;能不能效果也能好呢&#xff1f;这就用到了领域自使用domain adptation 用一…

Windows 11的新功能不适用于所有人,但对将要使用的人来说非常酷

正如一个新的预览版本所示&#xff0c;Windows 11即将为那些使用手写笔的人添加一些智能功能&#xff0c;以及其他改进。 这是预览版22635.2776&#xff08;也称为KB5032292&#xff09;&#xff0c;已推出Beta频道&#xff0c;这是发布预览版之前的最后一个测试方法&#xff…

速速报名!请查收 2023 龙蜥操作系统大会超全指南

亲爱的小伙伴们&#xff0c;大家好&#xff01;我是大家的老朋友小龙&#xff01;自 2023 龙蜥操作系统大会宣布启动以来&#xff0c;小龙收到了来自四面八方的诸多期待和小心心。首届龙蜥大会正如火如荼地进行中&#xff0c;为表示对关注社区的每一位小伙伴由衷的感谢&#xf…

Ubuntu安装ssh

Ubuntu安装ssh服务器 一、ssh ssh&#xff1a;安全外壳协议(secure shell)的缩写&#xff0c;安全外壳协议&#xff08;安全的shell&#xff09;&#xff0c;是一个计算机网络协议&#xff08;默认端口号为22&#xff09;。通过ssh协议可以在客户端安全&#xff08;提供身份认…

k8s中Pod控制器简介,ReplicaSet、Deployment、HPA三种处理无状态pod应用的控制器介绍

目录 一.Pod控制器简介 二.ReplicaSet&#xff08;简写rs&#xff09; 1.简介 &#xff08;1&#xff09;主要功能 &#xff08;2&#xff09;rs较完整参数解释 2.创建和删除 &#xff08;1&#xff09;创建 &#xff08;2&#xff09;删除 3.扩容和缩容 &#xff08…

【Java SE】带你在String类世界中遨游!!!

&#x1f339;&#x1f339;&#x1f339;我的主页&#x1f339;&#x1f339;&#x1f339; &#x1f339;&#x1f339;&#x1f339;【Java SE 专栏】&#x1f339;&#x1f339;&#x1f339; &#x1f339;&#x1f339;&#x1f339;上一篇文章&#xff1a;带你走近Java的…

【C 语言经典100例】C 练习实例9

题目&#xff1a;要求输出国际象棋棋盘。 程序分析&#xff1a;国际象棋棋盘由64个黑白相间的格子组成&#xff0c;分为8行*8列。用i控制行&#xff0c;j来控制列&#xff0c;根据ij的和的变化来控制输出黑方格&#xff0c;还是白方格。 #include<stdio.h>int main() {…

从 Elasticsearch 到 SelectDB,观测云实现日志存储与分析的 10 倍性价比提升

作者&#xff1a;观测云 CEO 蒋烁淼 & 飞轮科技技术团队 在云计算逐渐成熟的当下&#xff0c;越来越多的企业开始将业务迁移到云端&#xff0c;传统的监控和故障排查方法已经无法满足企业的需求。在可观测理念逐渐深入人心的当下&#xff0c;人们越来越意识到通过多层次、…

YOLOv5小目标检测层

目录 一、原理 二、yaml配置文件 一、原理 小目标检测层,就是增加一个检测头,增加一层锚框,用来检测输入图像中像素较小的目标 二、yaml配置文件 # YOLOv5 🚀 by Ultralytics, GPL-3.0 license# Parameters nc: 3 # number of classes depth_multiple: 0.33 # model…