在目前移动互联网时代每个 APP 就是流量入口与过去 PC Web 浏览器时代不同的是APP 的体验与迭代速度影响着用户的粘性这同时也对从事移动开发人员提出更高要求进而移动端框架也层出不穷。上图显示的是传统的服务端架构和客户端 App 架构对比。传统的服务端架构中最底下是一个 OS一般是 Linux最上面服务端的业务而中间有非常多的层次可以在架构上按照我们的意愿搭建中间的各个层次的衔接环节使得架构具有足够的灵活性和扩展性。但是到了 App 就会面对一个完全不同的现状App 的OSAndroid或iOS本质上并不是一个很瘦的像 Linux 这样的 OS而是在 OS 上有一个很重的 App Framework开发一个普通的客户端应用所要用到的绝大多数接口都在 Framework 里而上面的业务也是一个非常复杂多样化的业务最后会发现“架构”是在中间的一个非常尴尬的夹心层因为会遇到很多在服务端架构中不需要面临的挑战。比如以下两点体积的制约。体积对用户来说是一个非常敏感的概念如果我们要在架构上做很多事情的话通常意味着架构占据的代码量会比较大。在服务端架构中我们可以容忍我们在架构层面去做几十兆的代码。但是对于客户端架构即使你的架构只有一两兆对于一个客户端可能都占据了 10%20%的容量。性能的挑战。从性能上来看对于服务端架构我们通常关注的是吞吐率我们不会去关注启动速度。一个服务端的启动哪怕是花了一两分钟只要它运作起来吞吐率足够高支持的并发能力足够好响应速度足够快我们就认为这是一个良好的架构。但客户端不同客户端的进程对用户而言往往是一个栈态的手机里面使用完一个应用退出之后可能过不了多久就会被回收掉当用户下次再打开的时候它会再次启动进程需要重新完成一次初始化的流程。如果在这个上面做了很多事情的话会导致程序启动的速度会很慢在很多用户看来这就是一种不可接受的用户体验。客户端 APP 与服务端在架构上是有着一定的区别在选择对客户端架构需要谨慎对待需要有许多权衡的条件在此前提上是否有一种归一的方式呢可以分而治之并行开发把业务分隔成一个个单独的组件整个架构围绕组件开发构建也是组件一切皆组件。答案是有的那就是打造客户端组件框架。起源为何组件化客户端 APP 自身在飞速发展APP 版本不断迭代新功能不断增加业务模块数量不断增加业务上的处理逻辑越变越复杂同时每个模块代码也变得越来越多这就引发一个问题所维护的代码成本越来越高稍微一改动可能就牵一发而动全身改个小的功能点就需要回归整个 APP 测试这就对开发和维护带来很大的挑战。同时原来APP 架构方式是单一工程模式业务规模扩大随之带来的是团队规模扩大那就涉及到多人协作问题每个移动端软件开发人员势必要熟悉如此之多代码如果不按照一定的模块组件机制去划分将很难进行多人协作开发随着单一项目变大而且 Andorid 项目在编译代码方面就会变得非常卡顿在单一工程代码耦合严重每修改一处代码后都需要重新编译打包测试导致非常耗时最重要的是这样的代码想要做单元测试根本无从下手所以必须要有一个更灵活的架构去代替过去单一工程模式。同样这样的问题在我们工作具体项目中处处碰到就拿我们组内负责的某个移动端 APP 来说就碰到如下几个问题代码量膨胀不利于维护不利于新功能的开发。项目工程构建速度慢在一些电脑上写两句代码重新编译测试的话编译速度起码 10-20 分钟有的甚至更长。不同模块之间代码耦合严重比如消息模块严重耦合视频模块如果修改视频模块相应的消息模块也需要修改不然会产生一连串问题。每个模块之间都有引用第三方库但有些第三方库版本不一致导致打包 APP 时候代码冗余容易引起版本冲突。有些定制项目如果只需要消息模块其他模块不需要的话做不到按需加载打包因为模块之间有互联依赖。现有项目基于以前其他人项目基础上开发经手的人次过多存在着不同的代码风格项目中代码规范乱类似的功能写法却不一样导致不统一。项目工程架构模式改变是大势所趋那又该如何做呢那就是打造组件化开发框架用以解决目前所面临问题在讲解如何打造之前需要谈谈组件化概念组件化框架是什么。概念组件化是什么问什么是组件什么是组件化答在软件开发领域组件Component是对数据和方法的简单封装功能单一高内聚并且是业务能划分的最小粒度。举个我们生活中常见的例子就是电脑主板上每个元件电容器件每个元件负责的功能单一、容易组装、即插即拔但作用有限需要一定的依赖条件才可使用。如下图那么同样组件化就是基于组件可重用的目的上将一个大的软件系统按照分离关注点的形式拆分成多个独立的组件使得整个软件系统也做到电路板一样是单个或多个组件元件组装起来哪个组件坏了整个系统可继续运行而不出现崩溃或不正常现象做到更少的耦合和更高的内聚。问组件化、模块化容易混淆两者区别又是什么答模块化就是将一个程序按照其功能做拆分分成相互独立的模块以便于每个模块只包含与其功能相关的内容模块我们相对熟悉,比如登录功能可以是一个模块,搜索功能可以是一个模块等等。而组件化就是更关注可复用性更注重关注点分离如果从集合角度来看的话可以说往往一个模块包含了一个或多个组件或者说模块是一个容器由组件组装而成。简单来说组件化相比模块化粒度更小两者的本质思想都是一致的都是把大往小的方向拆分都是为了复用和解耦只不过模块化更加侧重于业务功能的划分偏向于复用组件化更加侧重于单一功能的内聚偏向于解耦。问组件化能带来什么好处答简单来说就是提高工作效率解放生产力好处如下代码简洁冗余量少维护方便易扩展新功能。提高编译速度从而提高并行开发效率。避免模块之间的交叉依赖做到低耦合、高内聚。引用的第三方库代码统一管理避免版本统一减少引入冗余库。定制项目可按需加载组件之间可以灵活组建快速生成不同类型的定制产品。制定相应的组件开发规范可促成代码风格规范写法统一。系统级的控制力度细化到组件级的控制力度复杂系统构建变成组件构建。每个组件有自己独立的版本可以独立编译、测试、打包和部署。设计构建组件化框架回到刚开始讲的 APP 单一工程模式看张常见 APP 单一工程模式架构图上图是目前比较普遍使用的 Android APP 技术架构往往是在一个界面中存在大量的业务逻辑而业务逻辑中充斥着各种网络请求、数据操作等行为整个项目中也没有模块的概念只有简单的以业务逻辑划分的文件夹并且业务之间也是直接相互调用、高度耦合在一起的。单一工程模型下的业务关系总的来说就是你中有我我中有你相互依赖无法分离。如下图组件化的指导思想是分而治之并行开发一切皆组件。要实现组件化无论采用什么样的技术方式需要考虑以下七个方面问题代码解耦。如何将一个庞大的工程分成有机的整体组件单独运行。因为每个组件都是高度内聚的是一个完整的整体如何让其单独运行和调试组件间通信。由于每个组件具体实现细节都互相不了解但每个组件都需要给其他调用方提供服务那么主项目与组件、组件与组件之间如何通信就变成关键UI 跳转。UI 跳转指的是特殊的数据传递跟组件间通信区别有什么不同组件生命周期。这里的生命周期指的是组件在应用中存在的时间组件是否可以做到按需、动态使用、因此就会涉及到组件加载、卸载等管理问题。集成调试。在开发阶段如何做到按需编译组件一次调试中可能有一两个组件参与集成这样编译时间就会大大降低提高开发效率。代码隔离。组件之间的交互如果还是直接引用的话那么组件之间根本没有做到解耦如何从根本上避免组件之间的直接引用也就是如何从根本上杜绝耦合的产生组件化架构目标告别结构臃肿让各个业务变得相对独立业务组件在组件模式下可以独立开发而在集成模式下又可以变为 AAR 包集成到“ APP 壳工程”中组成一个完整功能的 APP。先给出框架设计图然后再对这七个问题进行一一解答。从图中可以看到业务组件之间是独立的互相没有关联这些业务组件在集成模式下是一个个 Library被 APP 壳工程所依赖组成一个具有完整业务功能的 APP 应用但是在组件开发模式下业务组件又变成了一个个 Application它们可以独立开发和调试由于在组件开发模式下业务组件们的代码量相比于完整的项目差了很远因此在运行时可以显著减少编译时间。各个业务组件通信是通过路由转发如图这是组件化工程模型下的业务关系业务之间将不再直接引用和依赖而是通过“路由”这样一个中转站间接产生联系。那么针对以上提出的七个问题具体解决如下1代码解耦问题对已存在的项目进行模块拆分模块分为两种类型一种是功能组件模块封装一些公共的方法服务等作为依赖库对外提供一种是业务组件模块专门处理业务逻辑等功能这些业务组件模块最终负责组装APP。2组件单独运行问题通过Gradle脚本配置方式进行不同环境切换。比如只需要把 Apply plugin: com.android.library 切换成Apply plugin: com.android.application 就可以同时还需要在 AndroidManifest 清单文件上进行设置因为一个单独调试需要有一个入口的 Activity。比如设置一个变量 isModule标记当前是否需要单独调试根据isModule 的取值使用不同的 gradle 插件和 AndroidManifest 清单文件甚至可以添加 Application 等 Java 文件以便可以做一下初始化的操作。3组件间通信问题通过接口实现的结构进行组件间的通信。每个组件声明自己提供的服务 Service API这些 Service 都是一些接口组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去如果要使用某个组件的功能只需要向Router 请求这个 Service 的实现具体的实现细节我们全然不关心只要能返回我们需要的结果就可以了。在组件化架构设计图中 Common 组件就包含了路由服务组件里面包括了每个组件的路由入口和跳转。4UI 跳转问题可以说 UI 跳转也是组件间通信的一种但是属于比较特殊的数据传递。不过一般 UI 跳转基本都会单独处理一般通过短链的方式来跳转到具体的 Activity。每个组件可以注册自己所能处理的短链的 Scheme 和 Host并定义传输数据的格式然后注册到统一的 UIRouter 中UIRouter 通过 Scheme 和 Host 的匹配关系负责分发路由。但目前比较主流的做法是通过在每个 Activity 上添加注解然后通过 APT 形成具体的逻辑代码。目前方式是引用阿里的 ARouter 框架通过注解方式进行页面跳转。5组件生命周期问题在架构图中的核心管理组件会定义一个组件生命周期接口通过在每个组件设置一个配置文件,这个配置文件是通过使用注解方式在编译时自动生成配置文件中指明具体实现组件生命周期接口的实现类来完成组件一些需要初始化操作并且做到自动注册暂时没有提供手动注册的方式。6集成调试问题每个组件单独调试通过并不意味着集成在一起没有问题因此在开发后期我们需要把几个组件机集成到一个 APP 里面去验证。由于经过前面几个步骤保证了组件之间的隔离所以可以任意选择几个组件参与集成这种按需索取的加载机制可以保证在集成调试中有很大的灵活性并且可以加大的加快编译速度。需要注意的一点是每个组件开发完成之后需要把 isModule 设置为 true并同步这样主项目就可以通过参数配置统一进行编译。7代码隔离问题如果还是 compile project(xxx:xxx.aar) 来引入组件我们就完全可以直接使用到其中的实现类那么主项目和组件之间的耦合就没有消除那之前针对接口编程就变得毫无意义。我们希望只在 assembleDebug 或者 assembleRelease 的时候把 AAR 引入进来而在开发阶段所有组件都是看不到的这样就从根本上杜绝了引用实现类的问题。目前做法是主项目只依赖 Common 的依赖库业务组件通过路由服务依赖库按需进行查找用反射方式进行组件加载然后在主工程中调用组件服务组件与组件之间调用则是通过接口实现进行通信后续规划通过自定义Gradle 插件通过字节码自动插入组件的依赖进行编译打包实现自动筛选 assembleDebug 或 assembleRelease 这两个编译命任务只有属于包含这两个任务的命令才引入具体实现类其他的则不引入。代码具体项目实践一创建工程1APP空壳工程通过AndroidStudio创建一个APP空壳工程如图然后在 APP 工程添加依赖具体业务组件 Module。比如2具体业务组件Module需要遵循一定组件命名规范为何需要规范呢因为需要通过组件命名规范来约束和保证组件的统一性和一致性避免出现冲突。比如登陆组件那么名称b(类型)-ga(部门缩写)-login(组件名称)这就是我们基于共同的约定进行命名的为后期维护和扩展都带来辨识度。二业务组件配置文件1build.gradle配置文修改。如下if (isModule.toBoolean()) { apply plugin: com.android.application } else { apply plugin: com.android.library } android { compileSdkVersion rootProject.ext.compileSdkVersion buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion if (isModule.toBoolean()) { applicationId com.hik.ga.business.login versionCode 1 versionName 1.0 } else { //ARouter javaCompileOptions { annotationProcessorOptions { arguments [ moduleName : project.getName() ] } } } } sourceSets { main { if (isModule.toBoolean()) { manifest.srcFile src/main/module/AndroidManifest.xml } else { manifest.srcFile src/main/AndroidManifest.xml //集成开发模式下排除debug文件夹中的所有Java文件 java { exclude debug/** } } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.pro } } } dependencies { implementation project(:b-ga-common-function-comlib) if (!isModule.toBoolean()) { annotationProcessor com.alibaba:arouter-compiler:${rootProject.annotationProcessor} } }