Android架构思考(模块化、多进程)(转载学习)

原文地址: Android架构思考(模块化、多进程)android

关于模块化(组件化)这个问题,我想每一个开发者可能都认真的思考过。随着项目的开发,业务不断壮大,业务模块愈来愈多,各个模块间相互引用,耦合愈来愈严重,同时有些项目(好比咱们公司)还伴随着子应用单独包装推广,影子应用单独发布等等需求,从新调整架构迫在眉睫。今天,咱们就来聊聊模块化(组件化),这篇文章同时也是我这几年,对项目架构的理解。编程

最初的超小型项目设计模式

当咱们最开始作Android项目的时候,大多数人都是没考虑项目架构的,咱们先上一张图。服务器

这里写图片描述

这个分包结构有没有很熟悉,各类组件都码在一个包里,彻底没有层级结构,业务、界面、
逻辑都耦合在一块儿。这是我12年末刚开始入门Android的时候开发的一个小项目,半年后,来了个小伙伴,而后咱们一块儿开发,而后每天由于谁修改了谁的代码打的不可开交。markdown

架构改进,小型项目数据结构

再后来开发App,人员比以前多了,因此不能按照之前那样了,必须得重构。因而我把公用的代码提取出来制做成SDK基础库,把单独的功能封装成Library包,不一样业务经过分包结构分到不一样module下,组内每人开发本身的module。刚开始都还轻松加愉快,并行开发啥的,一片融洽的场景,以下图。架构

这里写图片描述

刚刚重构以后的架构并发

随着时间推移,咱们的App迭代了几个版本,这几个版本也没什么别的,大致来说就是三件事情:app

扩展了一些新业务模块,同时模块间相互调用也增长了。
修改增长了一些新的库文件,来支持新的业务模块。
对Common SDK进行了扩展、修复。
很惭愧,就作了一些微小的工做,可是架构就变成下图这样。框架

这里写图片描述

作了几件微小的工做以后

能够看到,随着几个版本业务的增长,各个业务某块之间耦合愈发严重,致使代码很难维护,更新,更别说写测试代码了。虽而后期引入统一广播系统,必定程度改善了模块间相互引用的问题,可是局限性和耦合性仍是很高,没办法根治这个问题。这个架构作到最后,扩展性和可维护性都是不好,而且难以测试,因此最终被历史的进程所抛弃。

中小型项目,路由架构

时间很快就来到了2015年,这一年动态加载、热修复很火,360、阿里等大公司前后开源了本身的解决方案,如droidplugin、andfix等。在研究了一圈发现,这些技术对架构升级有必定的帮助,尤为是droidplugin的加载apk的思想,能很好地解决耦合度高、方法数超过6553五、动态修复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时序图

任意代码建立一个RouterRequest,包含Provider和Action信息,向Router进行请求。
Router接到请求,经过RouterRequest的Provider信息,在内部的HashMap中查找对应的Provider。
Provider接到请求,在内部的HashMap中查找到对应的Action信息。
Action调用invoke方法。
返回invoke方法生成的ActionResult。
将Result封装成RouterResponse,返回给调用者。
耦合下降

全部的Module之间的相互依赖没有了,咱们能够在主app中,取消任意的Module引用而不影响总体App的编译及运行。

这里写图片描述

取消对Module N的依赖

如图所示,咱们取消了对Module N的依赖,总体应用依然能够稳定运行,遇到调用Module N的地方,会返回Not Found提示,实际开发中能够根据需求作具体的处理。

可测试性加强

因为每一个Module并不依赖其余的Module,因此在开发过程当中,咱们只针对本身的模块进行开发,并能够建一个测试App来进行白盒测试。

这里写图片描述

测试Module A

复用性加强

关于复用性这块。做者所处的行业是招商投资这块,这个行业须要围绕主业务开发不少影子APP,将覆盖面扩大(有点相似58->58租房、58招聘,美团->美团外卖等)。这个时候,这个架构的复用性就体现出来了,咱们能够把业务进行拆分,而后写一个包装App,就能够生成一个独立的影子APP,这个影子APP用到哪些Module就引用哪些就能够了,开发迅速,而且后期Module业务有变化,也不用更改全部的代码,减小了代码的复制。好比咱们就曾经把IM模块和投资咨询模块单独拿出来,写了一些界面和样式,就生成了“招商经纪人”App。

支持并行开发

整套架构很相似Git的Branch思想,基于主线,分支单独开发,最后再回归主线这种思路。这里只是思路和branch类似,实际的开发过程当中,咱们每一个module能够是一个branch,也能够是一个仓库。每一个模块都须要本身有单独的版本控制,便于问题管理及溯源。主项目对各个模块的引用能够是直接引用,也能够是导出aar引用,或者是上传JCenter Maven等等方式。不过思路是统一的:继承公共->独立开发->主线合并。

基础库

2017.2.20新增

最近有朋友在评论里问公共的类还有共有资源怎么处理,其实很是简单,咱们在Router和Module之间再加一层,加一层CommonBaseLibrary,里面放一些全部项目都会用到的资源文件,Model类,工具类等等,而后CommonBaseLibrary再引入Router便可。

以下图

这里写图片描述

须要注意的是,咱们的Module A,不须要CommonBaseLibrary中的公共资源,因此没有引用CommonBaseLibrary,可是实际其余仍是能够被其余模块所调用,由于它内部有Router。

多进程思考,中型项目

随着项目的不断扩大,App在运行时的内存消耗也在不断增长,并且有时线上的BUG也会致使总体崩溃。为了保证良好的用户体验,减小对系统资源的消耗,咱们开始考虑采起多进程从新架构程序,经过按需加载,及时释放,达到优化的目的。

多进程优点

多进程的优势和使用场景,以前在《Android多进程使用场景》中也作过介绍,大致优势有这么几个:

提升各个进程的稳定性,单一进程崩溃后不影响整个程序。
对于内存的时候更可控,能够经过手工释放进程,达到内存优化目的。
基于独立的JVM,各个模块能够充分解耦。
只保留daemon进程的状况下,会使应用存活时间更长,不容易被回收掉。
潜在问题

可是启用多进程,那就意味着Router系统的失效。Router是JVM级别的单例模式,并不支持跨进程访问。也就是说,你的后台进程的全部Provider、Action,是注册给后台Router的。当你在前台进程调用的时候,根本调用不到其余进程的Action。

解决方案

其实解决的方法也并不复杂。原来的路由系统还能够继续使用,咱们能够把整套架构想象成互联网,如今多个进程有多个路由,咱们只须要把多个路由链接到一块儿,那么整个路由系统仍是能够正常运行的。因此咱们把原有的路由Router称之为本地路由LocalRouter,如今,咱们须要提供一个IPS、DNS供应商,那就建立一个进程,该进程的做用就是注册路由,连接路由,转发报文,咱们称之为广域路由WideRouter。

咱们先来看下路由链接架构图

这里写图片描述

路由链接架构

如图所示,竖直方向上,每一列,表明一个进程,经过虚线隔开,分别有Process WideRouter、Process Main、Process A、···、Process N这些进程。浅黄色的表明WideRouter,深黄色的表明WideRouter的守护Service。浅蓝色的表明每一个进程的LocalRouter,深蓝色的表明每一个LocalRouter的守护Service。WideRouter经过AIDL与每一个进程LocalRouter的守护Service绑定到一块儿,每一个LocalRouter也是经过AIDL与WideRouter的守护Service绑定到一块儿,这样,就达到了全部路由都是双向互连的目的。

事件分发

以前单一路由的事件分发是经过两层HashMap查找Provider和Action,进行事件下发。那么如今在外面加了一层WideRouter,那么咱们再加一层Domain,Domain对应的是Android应用内,各个进程的进程名。一般状况下,若是事件是在同一进程下,那么就相似于局域网内部事件传递,不须要经过WideRouter,直接内部按照以前的路由逻辑进行转发,若是不在相同进程内,就由WideRouter进行进程间通讯,达到跨进程调用的效果。

事件请求RouterRequest能够写成两种,一种是URL,一种JSON。(内部处理的时候统一使用JSON),同时也提供了对URL和JSON的解析方法,方便使用。

URL:xxxDomain/xxxProvider/xxxAction?data1=xxx&data2=xxx
这就和Http请求很像了。这样作的好处就是对后续WebView上能够很是便利得直接调用本地Action。

JSON:

{
domain: xxx,
provider: xxx,
action: xxx,
data{
data1: xxx,
data2: xxx
}
}
JSON方式简单明了,可做为接口返回值由服务器下发给客户端。

下面仔细讲一下一次跨进程请求,事件是如何传递的:

这里写图片描述

事件传递图

从图中能够清晰地看出,咱们主要是分两大部分去完成事件分发传递的。

第一部分,跨进程判断目标Action是不是异步程序。
第二部分,跨进程执行目标Action调用。
首先咱们先经过Domain、Provider、Action去跨进程查找是不是异步程序。若是是异步程序,那么咱们直接生成RouterResponse(Step13),而且,将Step14-Step24统一封装成Future,放在RouterResponse中,直接返回。若是是同步程序,那么就在当前方法内执行Step14-Step24,将返回结果放入RouterResponse内(Step25),直接返回。这么作的目的是,咱们的路由调用方法route(RouterRequest)默认是同步方法,不耗时的,能够直接在主线程里调用而不形成阻塞,不形成ANR。若是调用的目标Action是异步的,那么能够利用Java的FutureTask原理,调用RouterResponse的get()方法,获取结果。这个get()方法有多是耗时的,是否耗时,取决于RouterResponse.isAsync的值是不是true。

至于本地事件分发,仍是与以前的Router模式,从Step17到Step21,都是咱们上文中,单进程同步Router分发机制,没有做任何改变。

多进程Application逻辑分发

在多进程中,每启动一个新的进程,都会从新建立一次Application,因此,咱们须要把各个进程的Application逻辑剥离出来,而后根据不一样的Process Name,选择不一样的Application逻辑进行处理。

实际的Application启动流程以下:

这里写图片描述

多进程Application启动流程

首先,咱们先把全部ApplicationLogic注册到Application中,而后,Application会根据注册时的进程名信息进行筛选,选择相同进程名的ApplicationLogic,保存到本进程中,而后,对这些本进程的ApplicationLogic进行实例化,最后,调用ApplicationLogic的onCreate方法,实现ApplicationLogic与Application生命周期同步,同时还有onTerminate、onLowMemory、onTrimMemory、onConfigurationChanged等方法,与onCreate一致。

结束进程,释放内存

在咱们不使用某些进程的时候,好比听音乐的时候,能够把主界面关掉等等。咱们能够调用对应进程的LocalRouter的stopSelf()方法,该方法可使本进程与WideRouter进行解绑,而后咱们在手动关掉进程内的其余组件,最后调用System.exit(),达到释放内存的目的。合理的释放内存,能有效的改善用户体验。

小结

这篇文章大概讲了一下做者这几年对Android架构的理解。其实本文中没有什么很深的技术点,大可能是一些设计模式,架构思想。这套框比起大公司的一些优秀的动态更新、编译分包、apk插件化加载,仍是简单不少的,更适合中小型应用。

这套框架目前还有比较多能够改进的地方,目前正在整理的:

增长对Action的动态关闭功能。
经过Instant Run原理,实现Action的热更新。
增长Message Pool,实现Request、Response的循环利用,减小GC触发。
已解决《高并发对象池思考》
优化Message在传递过程当中的打包,拆包的速度,提高总体性能。

原文地址: Android架构思考(模块化、多进程)

相关文章
相关标签/搜索