模块化通讯方式对比

前面介绍了一个自定义的简单Router组件,是使用了组件化的思想,那么咱们常常说到的组件化和模块化的区别是什么呢?其实这两个概念很相近,能够这么说组件化是模块化,可是模块化不必定是组件化。java

组件化的核心是角色的转换。 在打包时是library,而在调试时是application,能够这么理解,组件是能够单独运行的模块,具体的转换方式在以前的文章已经介绍过了。那么在抽出好模块之后各模块之间应该如何进行通讯呢?有人说想用哪一个模块就直接引用,这是很是很差的方式,做者很是不推荐这种作法。由于若是其余项目须要使用当前项目的某一个模块的时候,若是该模块有依赖其余模块就必须把该模块依赖的全部模块都复制过去,就出现了所谓的"拖油瓶"现象,灵活性太差,因此咱们应该避免出现这种杂乱无章的循环依赖。mysql


今天咱们是在模块被app依赖的前提下,也就是各组件是library的状况,讲解一下各个模块之间的通讯方式。做者能想到的使用较多的方式至少有三种,分别是EventBus、SPI、ARouter,固然还有其余的方式,这里就不一一介绍了。接下来做者带领你们一块儿分析一下这3种方式的优缺点,若是有更好的方式也但愿你们分享出来。git

EventBus
github

EventBus可以简化各组件间的通讯,有效的分离事件发送方和接收方(解耦),避免复杂和容易出错的依赖性和生命周期问题。而且最重要的是使用起来很是简单,具体的使用方式就不详细介绍了,网上关于它的使用方式很是多,这里就简单贴个图吧。sql

缺点:EventBus能够发送消息到其余模块,可是并不能直接传值回来。这里若是必定要实现传值也不是没有办法,好比再反发射一个事件回去,又或者经过接口回调的形式来实现。就拿接口回调来举例子吧,每个事件都要写一个回调接口,而不是直接获取返回值,这样就会以为用起来很不舒服。更重要的是若是要发送多个消息的话,每一个消息须要定义一个event类,致使用起来就更不爽了。并且eventbus是发送给全部的订阅者,而不是一对一,若是要实现一对一还要编写更多额外代码,整个代码框架将会很冗余。初次以外,EventBus发送的地方若是要寻找到使用的地方,或者使用的地方想要了解发送方,只能经过代码全局搜索的方式,整个架构很是乱很影响阅读效率。数据库


SPI技术api

Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它能够用来启用框架扩展和替换组件。好比JDBC就是经过SPI机制来加载不一样的数据库(mysql、oracle等)驱动,大体的机制以下图。SPI分为服务提供方和调用方,服务提供方须要提供好调用方须要的接口和调用路径,另外须要在公共模块定义好一个接口用来衔接两个模块。另外须要在base层定义一个接口做为桥梁,一个写一个读。bash


接下来咱们以A模块须要调用B模块的数据为例来说解一下整个过程,首先须要在B模块中提供好A模块须要的数据的返回方法以及接口实现类的调用路径。注意调用路径的提供方式是在src/main目录下新建resources文件夹,而后再新建META-INF.services文件夹,而后在其中新建一个文件,注意这个文件名不能随便取,必定要和base中定义的接口路径如出一辙微信



而后在base模块中定义一个接口,接口路径和名称和上面的文件名保持一致,接口中内容为约定好的数据传输方法。架构



最后在A模块中调用便可,注意这里接口实现类是一个迭代器,说明能够接收到多个实现的方法,全部实现了TestService接口,而且在META-INF下的文件里写入了的类都会被迭代器给遍历出来,执行里面的接口实现方法


至此SPI实现模块间通讯已经实现完成,估计你们也发现了,使用SPI来实现是很是麻烦的一件事情,每传一类值就要新建接口映射,并且使用处还要进行解析,若是业务大了须要传的值太多的状况下,这种方式同样将成为传值噩梦。还有接口须要定义在base层,这其实有违base的建立初衷。base层的初衷是一些固定的代码,在稳定后不须要修改,也和业务代码没有任何关系。SPI方式来传值每次都须要在base建立接口,就意味着base层一直是须要修改的,并且将会愈来愈臃肿,这不是咱们想要看到的,因此做者也不推荐使用这种方式来进行模块间传值。那么是否有其余的更优秀的传值框架呢?这就是咱们的终极武器——ARouter了,固然还有其余大厂的Router框架,这里就以ARouter为例来说解一下ARouter来实现模块传值。


ARouter

如上图,因为其余模块须要调用用户模块的数据,因此须要对本属于用户模块的AccountService进行接口下沉,将其放入base中。用户模块实现AccountService,其余模块因为拥有base的权限,因此能够调用用户模块中的实现类代码。咱们假设在一个模块化项目中,module1须要调用module2中的方法获取到数据,只须要按如下步骤。

步骤一 引入ARouter包

在使用注解的module2中按照ARouter的github文档走一遍就行了,github地址:https://github.com/alibaba/ARouter/blob/master/README_CN.md

api 'com.alibaba:arouter-api:1.4.1'
复制代码
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
复制代码
javaCompileOptions {    
      annotationProcessorOptions {       
          arguments = [AROUTER_MODULE_NAME: project.getName()]    
       }
 }复制代码


步骤二 在base模块定义数据传输接口


注意一下,这里须要继承IProvider,用来引入Arouter的支持,相应的在TransDataInterface实现类中也须要实现init方法来初始化一些数据,没有数据的话也能够无论。


步骤三 在module2中实现该接口

注意,这里须要用@Route(path="")来标注,用来给注解处理器生成类文件,编译后生成的文件以下:


步骤四 其余模块使用该接口获取数据


至此,按照文档简单地使用ARouter传值已完成,可是咱们发现因为须要接口下沉,业务接口须要在base层里进行定义,这样等业务愈来愈多的时候,base层又会愈来愈臃肿,咱们设计base初衷是稳定了就能够不用动了,那么是否有办法能够避免这个问题呢?答案就是建立中介api模块。好比支付建立支付api模块,用户建立用户api模块,建立出来的api模块能够被任何一个大模块引用。接下来以module1须要获取module2中的值为例,使用建立模块的方式来实现模块间传值。

步骤一 建立module2_api模块

新建一个module2_api的module,将数据传值的接口复制到该模块,注意须要在gradle中引入arouter,用以继承provider类

步骤二 让module一、module2全都依赖module2_api模块

步骤三 在module2中引入arouter的支持、arouter注解处理器、arouter类名等,而后须要写好数据传输的实现类方法,使用@router进行注解标记,这样在编译时就会生成类文件了。

步骤四 在module1中调用arouter的方法来获取数据便可


那么分离出api模块的方式是否有缺点呢?答案是确定的,假如一个大项目中有20个模块,那么就须要手动建立20个api模块,模块数量瞬间翻倍了,使得项目变得更复杂。那么微信是如何解决这个问题呢?答案就是微信.api化技术。微信是使用了自定义文件后缀名的方法,将接口的文件名修改成.api(这里其实随便改个名字都行,好比.abc都行),改为.api后编译时经过gradle脚本去读取工程中全部.api文件,读取到以后自动建立module_api工程,而且将接口文件自动复制到建立好的module_api工程中,而且再将名字修改回.java,具体的脚本大体以下:



总结:今天主要是对模块化通讯的几种方式进行了对比,因为eventbus很难维护,因此咱们考虑了使用spi,而后spi又太麻烦,最终使用到了ARouter。使用之后发现每次都要新建api模块进行通讯,因此微信就研究出来.api化技术,经过gradle脚原本操做每一个组件自动生成api模块,这也是目前比较前沿的技术。固然技术没有最好,只有更好,相信通过你们的努力,之后确定会出现更好的模块化通讯框架,让咱们拭目以待。

相关文章
相关标签/搜索