顺丰控股昨日早上继续涨停,后来下午经过市场振荡之后最终上涨4.79%,而此前顺丰控股已经连续四个交易日涨停。目前顺丰控股的总市值达到了2929亿,而顺丰老总王卫的身家也是水涨船高,达到了1890亿元,已经超过了马云,离超过王健林还差一个涨停。 本篇是 Spiny 的第二篇 投稿,详细地分享了随着项目的发展,不断升级的架构之路。感兴趣的朋友要仔细阅读一下啦。 Spiny 的博客地址:
关于模块化(组件化)这个问题,我想每个开发者可能都认真的思考过。随着项目的开发,业务不断壮大,业务模块越来越多,各个模块间相互引用,耦合越来越严重,同时有些项目(比如我们公司)还伴随着子应用单独包装推广,影子应用单独发布等等需求,重新调整架构迫在眉睫。今天,我们就来聊聊模块化(组件化),这篇文章同时也是我这几年,对项目架构的理解。
当我们最开始做Android项目的时候,大多数人都是没考虑项目架构的,我们先上一张图: 这个分包结构有没有很熟悉,各种组件都码在一个包里,完全没有层级结构,业务、界面、逻辑都耦合在一起。这是我12年底刚开始入门Android的时候开发的一个小项目,半年后,来了个小伙伴,然后我们一起开发,然后天天因为谁修改了谁的代码打的不可开交。
再后来开发App,人员比之前多了,所以不能按照以前那样了,必须得重构。于是我把公用的代码提取出来制作成SDK基础库,把单独的功能封装成Library包,不同业务通过分包结构分到不同module下,组内每人开发自己的module。刚开始都还轻松加愉快,并行开发啥的,一片融洽的场景,如下图。 刚刚重构之后的架构 随着时间推移,我们的App迭代了几个版本,这几个版本也没什么别的,大体来讲就是三件事情:
很惭愧,就做了一些微小的工作,但是架构就变成下图这样: 做了几件微小工作后 可以看到,随着几个版本业务的增加,各个业务某块之间耦合愈发严重,导致代码很难维护,更新,更别说写测试代码了。虽然后期引入统一广播系统,一定程度改善了模块间相互引用的问题,但是局限性和耦合性还是很高,没办法根治这个问题。这个架构做到最后,扩展性和可维护性都是很差,并且难以测试,所以最终被历史的进程所抛弃。
时间很快就来到了2015年,这一年动态加载、热修复很火,360、阿里等大公司先后开源了自己的解决方案,如droidplugin、andfix等。在研究了一圈发现,这些技术对架构升级有一定的帮助,尤其是droidplugin的加载apk的思想,能很好地解决耦合度高、方法数超过65535、动态修复bug等问题,不过由于项目本身不是很大,并且没有专门的人来维护架构,所以最后放弃了功能强大、但是问题也同样多的插件化,退而求其次,选择了利用路由机制来实现组件化解耦。 关于路由机制,熟悉iOS开发的朋友可能并不陌生,在iOS上有很多架构方案都是采用路由机制来时间模块之间的解耦的,比如VIPER(View Interactor Presenter Entity Routing)思想等等。其实思路都是相同的,Android上面组件化也是通过公用的路由,来实现模块与模块之间的隔离。 实现原理 我们先来看下路由架构图: 通过上图可以看到,我们在最基础的 Common 库中,创建了一个路由Router,中间有n个模块Module,这个Module实际上就是 Android Studio 中的 module,这些Module都是Android Library Module,最上面的 Module Main 是可运行的 Android Application Module。 这几个Module都引用了 Common库,同时 Main Module 还引用了A、B、N这几个Module,经过这样的处理之后,所有的Module之间的相互调用就都消失了,耦合性降低,所有的通信统一都交给 Router 来处理分发,而注册工作则交由 Main Module 去进行初始化。这个架构思想其实和 Binder 的思想很类似,采用C/S模式,模块之间隔离,数据通过共享区域进行传递。模块与模块之间只暴露对外开放的 Action,所以也具备面向接口编程思想。 图中的红色矩形代表的是行动Action,Action是具体的执行类,其内部的invoke方法 是具体执行的代码逻辑。如果涉及到并发操作的话,可以在invoke方法 内加入锁,或者直接在 invoke方法 上加上 synchronized 描述。 图中的黄色矩形代表的是供应商Provider,每个Provider中包含1个或多个Action,其内部的数据结构以HashMap来存储 Action。首先 HashMap 查询的时间复杂度是O(1),符合我们对调用速度上的要求,其次,由于我们是统一进行注册,所以在写入时并不存在并发线程并发问题,在读取时,并发问题则交由 Action 的 invoke 去具体处理。在每一个Module内都会有1个或多个供应商Provider(如果不包含Provider,那么这个Module将无法为其他Module提供服务)。 途中蓝色矩形代表的是路由Router,每个Router中包含多个Provider,其内部的数据结构也是以HashMap来存储Provider,原理也和Provider是一样的。之所以用了两次 HashMap,有两点原因,一个是因为这样做,不容易导致Action的重名,另一个是因为在注册的时候,只注册Provider会减少注册代码,更易读。并且由于 HashMap 的查询时间复杂度是O(1),所以两次查找不会浪费太多时间。当查找不到对应Action的时候,Router 会生成一个ErrorAction,会告之调用者没有找到对应的Action,由调用者来决定接下来如何处理。 一次请求流程 通过 Router 调用的具体流程是这样的: Router时序图 1. 任意代码创建一个RouterRequest,包含Provider和Action信息,向Router进行请求。 2. Router接到请求,通过RouterRequest的Provider信息,在内部的 HashMap 中查找对应的Provider。 3. Provider接到请求,在内部的 HashMap 中查找到对应的Action信息。 4. Action调用 invoke方法。 5. 返回 invoke方法 生成的ActionResult。 6. 将Result封装成RouterResponse,返回给调用者。 耦合降低 所有的Module之间的相互依赖没有了,我们可以在 主app 中,取消任意的Module引用而不影响整体App的编译及运行。 取消对 Module N的依赖 如图所示,我们取消了对Module N的依赖,整体应用依然可以稳定运行,遇到调用Module N的地方,会返回Not Found提示,实际开发中可以根据需求做具体的处理。 可测试性增强 由于每个Module并不依赖其他的Module,所以在开发过程中,我们只针对自己的模块进行开发,并可以建一个 测试App 来进行白盒测试。 测试Module A 复用性增强 关于复用性这块。作者所处的行业是招商投资这块,这个行业需要围绕主业务开发很多影子APP,将覆盖面扩大(有点类似58- |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|