回归初心:极简 Android 组件化方案 — AppJoint

本文详细介绍了Android组件化方案AppJoint,旨在解决项目大、编译慢及跨模块调用难题。通过创建独立的Application模块,实现业务模块的单独编译和运行,避免了Gradle Sync的时间浪费。AppJoint通过简单的API实现模块间的通信,同时保持低学习成本,允许项目沿用现有开发方式。此外,文章还讨论了如何处理不同模块的初始化、跨模块方法调用和Activity启动等问题,强调了组件化过程的渐进性和代码的可维护性。
摘要由CSDN通过智能技术生成

Android 组件化的概念大概从两年前开始有人讨论,到目前为止,技术已经慢慢沉淀下来,越来越多团队开源了自己组件化框架。本人所在团队从去年开始调研组件化框架,在了解社区众多组件化方案之后,决定自研组件化方案。为什么明明已经有很多轮子可以用了,却还是决定要自己造个新轮子呢?

主要的原因是在调研了诸多组件化方案之后,发现尽管它们都有各自的优点,但是依然有一些地方不是令人十分满意。而其中最重要的一个因素就是引入组件化方案成本较高,对已有项目改造过大。我想这一点应该很多人都有相同的体会,很多时候 我们对于项目的重构是需要与新需求的迭代同步进行的 ,几乎很难停下来只做项目的组件化。

另外一点,我不太希望自己的项目和某一款组件化框架 强耦合。 Activity 的路由方案也好,跨模块的同步或异步方法调用也好,我希望能够沿用项目已有的调用方式,而不是使用某款组件化框架自己特定的调用方式。例如某个接口已经基于 RxJava 封装为了 Observable 的接口,我就不太希望因为组件化的关系,这个接口位于另一个模块之后,我就不得不用这个组件化框架定义的方式去调用,我还是希望以 RxJava 的方式去调用。

PS :有兴趣的加入Android工程师交流QQ群:752016839 主要针对Android开发人员提升自己,突破瓶颈,
相信你来学习,会有提升和收获。

回归初心

我认为目前想要进行组件化的项目应该可以分为两类:

  • 包含有一个 application 模块,以及一些技术组件的 library 模块(业务无关)。
  • 除了 application 模块以外,已经存在若干包含业务的 library 模块和技术的 library 模块。

无论是哪种类型的项目,面临的问题应该都是类似的,那就是项目大起来以后,编译实在是太慢了

除此以外,就是 跨模块的功能调用非常不便 ,这个问题主要体现在上面列举的第二种类型的项目。本人所在的项目在组件化之前就是上面列举的第二种类型的项目,application 模块最早用来承载业务逻辑代码,随着业务发展,大概是某位开发人员觉得, “不行,这样下去 application 模块代码数量会失控的”,于是后续新的业务模块都会新开一个 library 模块进行开发,就这样断断续续目前有了大概 20+ 个 library 模块(业务相关模块,技术模块不包含在内)。

这种做法是符合软件工程思想的,但是也带来了一些棘手的问题,由于 application 模块里的业务功能和 library 模块里的业务功能在逻辑地位上是平等的,所以难免会有互相调用的情况,但是它们在项目依赖层次上却不是处于相等的地位,application 调用 library 倒没事,但是反过来调用就成了问题。另外,剩下这 20 + 个 library 模块在依赖层次中也不全是属于同一层次的,library 模块之间互相依赖也很复杂。

所以我期望的组件化方案要求解决的问题很简单:

  • 业务模块单独编译,单独运行,而不是耗费大量时间全量编译整个 App
  • 跨模块的调用应该优雅,无论两个模块在依赖树中处于什么样的位置,都可以很简单的互相调用
  • 不要有太多的学习成本,沿用目前已有的开发方式,避免代码和具体的组件化框架绑定
  • 组件化的过程可以是渐进的,立即拆分代码不是组件化的前置条件
  • 轻量级,不要引入过多中间层次(例如序列化反序列化)导致不必要的性能开销以及维护复杂度

基于上述的思想,我们开发了 AppJoint 这个框架用来帮助我们实现组件化。

 

 

 

AppJoint 是一个非常简单有效的方案,引入 AppJoint 进行组件化所有的 API 只包含 3 个注解,加 1 个方法,这可能是目前最简单的组件化方案了,我们的框架不追求功能要多么复杂强大,只专注于框架本身实用、简单与高效。而且整体实现也非常简单,核心源码 不到500行

模块独立运行遇到的问题

本人接触最早的组件化方案是 DDComponentForAndroid,学习这个方案给了我很多启发,在这个方案中,作者提出,可以在 gradle.properties 中新增一个变量 isRunAlone=true ,用来控制某个业务模块是 以 library 模块集成到 App 的全量编译中 还是 以 application 模块独立编译启动 。不知道是不是很多人也受了相同的启发,后面很多的组件化框架都是使用类似的方案:

if (isRunAlone.toBoolean()) {    
    apply plugin: 'com.android.application'
} else {  
    apply plugin: 'com.android.library'
}
复制代码

根据我本人的实践,这种方式有一些缺点。首先有一些开源框架在 library 模块中和在 application 模块中使用方法是不一样的,例如 ButterKinfe , 在 application 中使用 R.id.xxx,在 library 模块中使用 R2.id.xxx ,如果想组件化,代码必须保证在两种情况下都可用,所以基本只能抛弃 ButterKnife 了,这会给项目带来巨大的改造成本。

除此以外,还有一些开源框架是只能在 application 模块中配置的,配置完以后对整个项目的所有 library 模块都生效的,例如一些字节码修改的框架(比如 AOP 一类的),这是一种情况。还有一种情况,如果原先项目已经是多模块的情况下,可能多个模块的初始化都是放在 application 模块里,因为 application 模块是 上帝模块,他可以访问到项目中任意一块代码,所以在这里做初始化是最省事的。但是现在拆分为模块之后,因为每个模块需要独立运行,所以模块需要负责自身的初始化,可是有时候这个模块的初始化是只能在 application 模块里才可以做的,我们把这段逻辑下放到 library 之后,如何初始化就成了问题。

这两种情况,如果我们使用 gradle.properties 中的变量来切换 applicationlibrary 的话,我们势必需要在这个模块中维护两套逻辑,一套是在 application 模式下的启动逻辑,一套是在 library 模式下的启动逻辑。原先这个模块是专注自己本身的业务逻辑的,现在不得不为了能够独立作为 application 启动,而加入许多其他代码。一方面 build.gradle 文件中会充满很多 if - else,另一方面 Java 源码中也会加入许多判断是否独立运行的逻辑。

最终 Release App 打包时,这些模块是作为 library 存在的,但是我们为了组件化已经在这个模块中加入了很多帮助该模块独立运行(以 application 模式)的代码(例如模块需要单独运行,需要一个属于这个模块的 Laucher Activity),虽然这些代码在线上不会生效,可是从洁癖的角度来讲,这些代码其实不应该被打包进去。其实说了这么多无非就是想说明,如果我们希望通过某个变量来控制模块以 application 形式还是以 library 形式存在,那么我们肯定要在这个模块中加入维护两者的差异的代码,而且可能代码量还不少,最后代码呈现的状态可能是不太优雅的。

此外模块中的 AndroidManifest.xml 也需要维护两份:

if (isRunAlone.toBoolean()) {
    manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
} else {
    manifest.srcFile 'src/main/AndroidManifest.xml'
}
复制代码

但是 xml 毕竟不是代码,没有封装继承这些面向对象的特性,所以每当我们增加、修改、删除四大组件的时候,都需要记得要在两个 AndroidManifest.xml 都做对应的修改。除了 AndroidManifest.xml 以外,资源文件也存在这个问题,虽然工作量不至于特别巨大,但这样的做法其实已经违背了面向对象的设计原则。

最后还有一个问题,每当模块在 application 模式和 library 模式之间进行切换的时候,都需要重新 Gradle Sync 一次,我想既然是需要组件化的项目那肯定已经是那种编译速度极慢的项目了,即使是 Gradle Sync 也需要等待不少时间,这点也是我们不太能接收的。

创建多个 Application 模块

我们最后是如何解决模块的单独编译运行这个问题的呢?答案是 为每个模块新建一个对应的 application 模块 。也许你会对此表示怀疑:如果为每个业务模块配一个用于独立启动的 application 模块,那模块会显得特别多,项目看起来会非常的乱的。但是其实我们可以把所有用于独立启动业务模块的 application 模块收录到一个目录中:

projectRoot
  +--app
  +--module1
  +--module2
  +--standalone
  |  +--module1Standalone
  |  +--module2Standalone   
复制代码

在上面这个项目结构图中,app 模块是全量编译的 application 模块入口,module1module2 是两个业务 library 模块, module1Standalonemodule2Standalone 是分别使用来独立启动 module1module2 的 2 个 application 模块,这两个模块都被收录在 standalone 文件夹下面。事实上,standalone 目录下的模块很少需要修改,所以这个目录大多数情况下是属于折叠状态,不会影响整个项目结构的美观。

这样一来,在项目根目录下的 settings.gradle 里的代码是这样的:

// main app
include ':app'
// library modules
include ':module1'
include ':module2'
// for standalone modules
include ':standalone:module1Standalone'
include ':s
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值