Android 模块化之路 模块间通信

一、背景

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 接口,并声明了 getCartCountaddToCart(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 中如下方法调用:

跨模块调用服务

bindServiceserviceConnection 还是熟悉的味道,但最上面的Intent 本应该 intent = new Intent(this,ShopcartService.class) , 但ShopcartService类定义在 module_shopcart里面,所以我们新增了:getServiceIntent() 并把类名传递进去,然后通过getPackageManager().queryIntentServices(intent, 0); 查询出action=com.ssevening.www.service.shopcart.IShopcartServiceService,取第一个封装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,这个连例子都不用写了。大家都懂的。

原文发布于微信公众号 - 刘望舒(liuwangshuAndroid)

原文发表时间:2018-01-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Timhbw博客

关于模仿网站的一些心得

2016-05-0417:47:47 发表评论 580℃热度 最近在做一个公司网站(学习为主),发现大部分企业网站都是静态网页,用php的少之又少,大部分都是...

3407
来自专栏竹清助手

深入理解什么是RESTful API ?

越来越多的人开始意识到,网站即软件,而且是一种新型的软件。   这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(hig...

622
来自专栏Flutter入门到实战

深入理解什么是RESTful API ?

越来越多的人开始意识到,网站即软件,而且是一种新型的软件。   这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(hi...

1382
来自专栏Java3y

图书管理系统【总结】

感想 该项目是目前为止,我写过代码量最多的项目了.....虽然清楚是没有含金量的【跟着视频来写的】,但感觉自己也在进步中...... 写的过程中,出了不少的问题...

3555
来自专栏CSDN技术头条

皮一下,给自己做个打卡系统

2018 年微信小程序开发者逐渐多了起来,微信平台也推出了很多红利,鼓励开发者参与到小程序开发中。

1634
来自专栏高性能服务器开发

(八)高性能服务器架构设计总结4——以flamigo服务器代码为例

一个项目的服务器端往往由很多服务组成,就算单个服务在性能上做到极致,支持的并发数量也是有限的。举个简单的例子,假如一个聊天服务器,每个用户的信息是1k,那对于一...

925
来自专栏FreeBuf

Python渗透工具的架构探讨

本文原创作者:VillanCh,本文属FreeBuf原创奖励计划,未经许可禁止转载 在最近忙活的一些事情中,体会到:如果你写的不是一个脚本,那么作为一个有命令行...

1795
来自专栏java达人

Percona5.6.15线程池压力测试

http://www.mysqlperformanceblog.com/2013/03/16/simcity-outages-traffic-control-a...

19810
来自专栏北京马哥教育

【译】5个对Linux新手来说最好的包管理器

译者按:作为Linux新手来说,选择一个Linux发行版、熟悉Linux系统,无论做什么都离不开软件的安装与卸载。那么,软件包管理器的相关知识就显得非常重要了。...

3724
来自专栏华仔的技术笔记

iOS应用架构谈 本地持久化方案及动态部署

嗯,你们要的大招。跟着这篇文章一起也发布了CTPersistance和CTJSBridge这两个库,希望大家在实际使用的时候如果遇到问题,就给我提issue或者...

3577

扫码关注云+社区