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

news2024/11/18 1:29:11

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 Mybatis 的 TypeHandler 存取 Postgresql jsonb 类型

文章目录 使用 TypeHandler 存取 Postgresql jsonb 类型常见错误column "" is of type jsonb but expression is of type character varying 使用 TypeHandler 存取 Postgresql jsonb 类型 首先在数据库表中定义 jsonb 类型: create table tb_user_info…

Unity中Shader的BRDF解析(四)

文章目录 前言一、BRDF 中的 IBL二、解析一下其中的参数1、光照衰减系数 :surfaceReduction2、GI镜面反射在不同角度下的强弱 :gi.specular * FresnelLerp (specColor, grazingTerm, nv);在BRDF中,IBL(Image Based Light&#xff…

Shell编程基础 – for循环

Shell编程基础 – for循环 Shell Scripting Essentials - for Loop 大多数编程语言都有循环的概念和语句。如果想重复一个任务数十次,无论是输入数十次,还是输出数十次,对用户来说都不现实。 因此,我们考虑如何用好Bash Shell编…

一个人撸码!之vue3+vite+element-plus后台管理(标签页组件)

一个后台管理常常需要一个标签页来管理已经打开的页面,这里我们单独写一个组件来展示标签页数组。 该标签页组件只做展示不涉及操作数据。标签页数组可记录已打开的数组,还能定义什么页面需要缓存,是一个重要的功能呢。 首先,建立…

【华为OD题库-043】二维伞的雨滴效应-java

题目 普通的伞在二维平面世界中,左右两侧均有一条边,而两侧伞边最下面各有一个伞坠子,雨滴落到伞面,逐步流到伞坠处,会将伞坠的信息携带并落到地面,随着日积月累,地面会呈现伞坠的信息。 1、为了…

FlowJo 10 v10.4(流式细胞分析软件)

FlowJo是一款广泛应用的流式细胞数据分析软件,它功能强大,简单易用,是流式领域最受推荐的一款专业分析软件,也是各高影响力科学期刊使用最多的软件,已经成了行业的一个标准。 FlowJo具有全面专业的分析功能&#xff0…

解析直播第三方美颜SDK:技术原理与应用

时下,直播平台和主播们纷纷引入美颜技术,以提升视觉效果和用户体验。而在众多美颜技术中,直播第三方美颜SDK成为许多开发者和平台的首选,因其灵活性和高效性而备受推崇。 一、技术原理:美颜算法的精髓 第三方美颜SDK…

Keil5 debug

目录 debug调试功能 基本功能: 程序复位:Reset 运行:Run 停止:Stop 断点调试(Breakpoint Debugging) 单步调试: 单步调试:Step 单步跳过调试:Step Over: 单步返…

不同路径 II(力扣LeetCode)动态规划

不同路径 II 题目描述 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。 现在考虑网格中有障碍物。…

Centos7安装配置nginx

快捷查看指令 ctrlf 进行搜索会直接定位到需要的知识点和命令讲解(如有不正确的地方欢迎各位小伙伴在评论区提意见,小编会及时修改) Centos7安装配置nginx Nginx介绍 Nginx (engine x) 是一个高性能的 HTTP 和 反向代理 服务,也…

运营商网络性能测试-Y.1564

前言 在网络部署之后和业务开展之前,运营商迫切希望了解当前网络的性能状态,以便为商业规划和业务推广提供必要的基础数据支持。因此,高可靠性和高精确度的性能测试方法对于运营商评判网络性能的优劣,显得尤为重要,而…

InnoSetupCompiler打包程序

修改默认的安装路径 因为程序可能需要在安装路径中写日志,默认的安装路径C:\Program Files (x86),这个路径好像是受保护还是啥,如果使用默认的打开会报错。 修改方法: DefaultDirName{autopf}\{#MyAppName} {autopf}改成…

【安卓】安卓xTS之Media模块 学习笔记(1) xTS介绍

1.背景 Media的安卓xTS相关测试和功能修复已经进行了一段时间了。 在此整理总结下xTS工作总结,留待后续查阅整理。 2. xTS介绍 - 什么是xTS 谷歌的xTS是对谷歌发布的CTS/GTS/VTS/STS/BTS/CTS-on-GSI等一系列测试的统称。 因为安卓系统比较庞大,模块多…

.mat格式文件是什么?及将png,jpg,bmp,gif,tiff,psd等格式图片转为.mat格式(附代码)

很多深度学习网络的输入要求为.mat格式,当然也可以直接修改输入数据的代码,比如修改为使用OpenCV读取图片等,但有些网络修改起来比较麻烦,且.mat数据有很多优势,所以部分网络最好还是用默认的.mat格式数据 目录 一、.…

jekins CVE-2018-1000861 漏洞复现

jekins CVE-2018-1000861 漏洞复现 ‍ 名称: jenkins 命令执行 (CVE-2018-1000861) 描述: ​Jenkins 可以通过其网页界面轻松设置和配置,其中包括即时错误检查和内置帮助。 插件 通过更新中心中的 1000 多个插件,Jenkins 集成了持续集成和持续交付工具…

PTA-6-48 使用面向对象的思想编写程序描述动物

题目: 使用面向对象的思想编写程序描述动物,说明: (1) 分析兔子和青蛙的共性,定义抽象的动物类,拥有一些动物共有的属性:名字、颜色、类别(哺乳类、非哺乳类)&#xff0c…

三十、elasticsearch集群

目录 一、集群的概念 1、节点 2、索引 3、分片和副本 二、集群的架构 三、集群的部署方式 1、单主节点 2、多主节点 3、安全集群 四、搭建ES集群 1、elasticsearch中集群节点有不同的职责划分 2、elasticsearch中的每个节点角色都有自己不同的职责,因此…

中间件安全:JBoss 反序列化命令执行漏洞.(CVE-2017-12149)

中间件安全:JBoss 反序列化命令执行漏洞.(CVE-2017-12149) JBoss 反序列化漏洞,该漏洞位于 JBoss 的 HttpInvoker 组件中的 ReadOnlyAccessFilter 过滤器中,其 doFilter 方法在没有进行任何安全检查和限制的情况下尝试…

基于ssm的编程技术类博客系统的设计与实现

基于SSM的编程技术类博客系统的设计与实现 摘要:博客是是互联网信息产生的主要来源之一。博客将信息采集与发布最大程度的简单化与快捷化,对个人能力提升也具有极大的帮助。一方面,极大地丰富了网络信息的资源,在时效性、连续流动…