查看原文
其他

Android 模块化之路 模块间通信

潘辰星 刘望舒 2022-06-30

一、背景

Android 开发,从最初的一个人团队,我的地盘我做主,随着团队和业务逐渐变大,单App开发慢慢跟不上业务发展步伐。

  • 代码复用性: 再牛X的代码,不能给其他团队使用,其他团队无法使用,也不牛X。

  • 业务稳定性:代码改动不可控,测试回归不可控,业务不稳定。

  • 快速发射小卫星:业务要发布新App,一切从头开始,没有现成组件或模块可共用,刘欢唱起:大不了从头再来?

所以就走上Android 模块化之路。

二、小App向模块化App演变过程

模块化架构改造:

初期架构到后期架构

  1. 把原App中的网络、图片、UIKIT都放到MAVEN仓库。

  2. 抽离BaseActivity 和 公共资源到Common中

  3. 逐渐下沉业务Module,做到业务隔离。

做到第3步时,就面临着模块间跳转和路由的选型了。所以路由的选型, 传送门 : Android路由选型

搞定路由后,可以达到如下图的基础架构

模块化通用架构

然后新的问题也跟着来了!模块间通信和模块调用。

在前期抽业务模块过程中,如产品模块用到:获取购物车数量,或添加到购物车,就把这两个功能也下沉到Common中,慢慢就有形成一个万能的Common。

所以面临着模块间的通信和调用的选型,问题如下:三个飘着的气球,要把这几个气球给落地。

要落地的气球

  • Big Common : 随着模块拆解越来越多,下沉的业务逻辑也会越来越多,大 Common 吾不想要!

  • Muti Module Call: 多模块间调用,不让下沉到Common,那就面临多模块间调用,就要寻找模块间调用的方案。

  • Be On Cloud: 在云端,上云,App上云的概念,第一步是让仓库代码可以被大家依赖调用到。而不是说:哥,你把我这代码拷走吧!把我的BigCommon拷走!(人家才不拷,你在依赖别人库时,多一个功能你都不想要呢)

三、模块间调用思路与方案

模块间调用思路

如果是WEB开发,对外声明接口,自己业务去实现接口,再暴露接口去调用具体业务的实现。

如上图所示,购物车模块对外提供两个服务:getCartCount 和 addToCart ,产品页面需要展示当前用户购物车的数量,以及把产品添加到购物车,那产品模块因为依赖了 ServiceCart,可以得到CartService的接口声明定义。

但怎么获取CartService的实例呢?

我相信CartService的实例一定存在世界某个地方,等我去发现。

现在模块化架构就变成如下图:

增加Service层

如上图,我们增加了一个Service层,这个Service层虽然画在了Common层上面,但并不依赖Common。

此时,我们要解决的就是 CartService 之我要找到你了!

四、CartService 之我要找到你

如果是服务端开发的话,会把CartService的实现集中注册到 Dubbo 或者 Spring中,通过某个Key得到一个Service的实例。

通过一个Key获得一个实例

前面路由的方案是:通过一个Key获取到一个路径。有点意思。经过比较,ARouter 和 自己通过AIDL来实现两套方案,对比如下:

选型标准

ARouter 使用运行时注解强大的功能,自建Map把 path 和 Service具体实例建立关联,跨模块可以轻轻松松地按照自定义路径取出实例调用。一项项来说明:

  • 稳定性:大厂出品,稳定性都OK。

  • 通用技术:第三方注解自建维护Map 和 Android AIDL服务调用,AIDL更通用。

  • 跨进程: ARouter 不支持,而AIDL专为跨进程为生。

  • 易适配:第三方App接入学习成本而言,ARouter还要学习下下,AIDL是Android开发必备技能,不用二次学习。

  • 第三方App调用:系统其他App调用服务定义,ARouter不支持,AIDL 通过Service export= true 的情况下,还是可以支持的。

所以,我们考虑用AIDL Service的方式来实现。

这个时候,你怒了,哥,我裤子都脱了,你就给我看这个?

快 Show Me The Fucking Code!

五、代码秀

有请24位漂亮的女生登场!

漂亮女生登场

模块结构如下图:


示例代码结构截图

app: 客户端启动入口
module_product : 产品模块
module_shopcart: 购物车模块
service_shopcart: 购物车对外开放服务,通过上面截图可以看到已通过AIDL 声明了 IShopcartService 接口,并声明了 getCartCount 和 addToCart(String productId) 的能力。

看接口的实现如下:

AIDL的实现

上面的是不是最AIDL经典实现?还是原来的配方!还是原来的味道!红茶,我只要王老吉?

并在 AndroidManifest.xml 中声明Service如下:

屏幕快照 2017-12-01 17.53.37.png

同时,注明<intent-filter> 为:com.ssevening.www.service.shopcart.IShopcartService,这个就是查找这个Service的Key,也是IShopcartService 的类路径。 在 module_product中的 ProductActivity 中如下方法调用:

跨模块调用服务

bindService 和 serviceConnection 还是熟悉的味道,但最上面的Intent 本应该 intent = new Intent(this,ShopcartService.class) , 但ShopcartService类定义在 module_shopcart里面,所以我们新增了:getServiceIntent() 并把类名传递进去,然后通过getPackageManager().queryIntentServices(intent, 0); 查询出action=com.ssevening.www.service.shopcart.IShopcartService 的Service,取第一个封装Intent对象,就可以正常调用AIDL服务了,还是原来的配方,还是原来的味道!

运行截图如下:

运行截图

购物车数量和添加到购物车的结果都已显示出来,同时通过网络调用把 www.baidu.com源码也成功返回。

至此,模块间调用基本方案敲定,余下的就是getServiceIntent() 方法的增强优化和下沉抽到Maven仓库的工作了。

最后,上次路由被坑了一次,详见:Android M queryIntentActivities return null list 蹲坑记 ,用户在App管理界面,可以关掉AppLinks的事,我还记得,所以这次特别去看一下context.getPackageManager().queryIntentServices(intent, 0);

代码截图如下:

queryIntentServices

,通过看源码,我们发现并没有用户关掉相关的过滤选项,这样我们就放心了。

我们接着说模块间事件传递。 比如 登陆状态的变化;在金币频道点击签到按钮,跳转到签到模块签到,签到完成后,回到金币模块签到成功的事情传递。

六、模块间事件传递

事件嘛,那肯定要对比 EventBus 和 广播,对比如下:

EventBus 和 广播对比

这样一对比,不用接第三方库就能实现,并且是Android通用方案,后期第三方接入和使用都方便,所以模块间事件传递就用:BroadcastReceiver,这个连例子都不用写了。大家都懂的。




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存