随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。
为什么采用组件化
我们从两个角度来分析这个问题。
单一工程模式开发的痛点
对工程的任意修改调试都要编译整个工程,效率十分低下。
做APP开发时,我们需要经常在手机或模拟器上进行调试,而每次调试都需要对整个工程进行编译,然后安装在手机上运行。即便你只是改了一句代码,或是UI调整了一个像素点,同样需要完整的编译工程。当工程代码越来越多时,编译也会越来越慢,你可以想象一下我修改了某句代码,编译一下需要等待4、5分钟才能成功运行的场景么,那简直让人崩溃,严重影响了开发效率(想起曾经用eclipse开发Android时,各种转菊花,卡顿得让人想死的心都有)。
不利于多人团队协同开发。
早期一个APP可能就1、2个人来开发,但是随着业务的扩张,我们可能会发展到一个团队来开发一个APP,少则4、5个人,多则10几个人甚至更多。像手机淘宝、微信、支付宝这些巨无霸APP,他们的APP开发人员估计起码有数百个。
以10人团队为例,如果10个人都是基于同一个工程的代码拉分支进行开发,每人的开发任务虽然不同,但是都能修改整个工程的任意地方。为了适应自己的需求,团队内某人改了某句代码,但是这个改动又有可能影响别人的开发,这样开发人员之间势必要花更多的时间去沟通和协调,没法专注自己的功能点。最后进行10个人的代码合并时,有过这方面经历的人,就知道这是一件多么痛苦的事情,解决冲突解决得你要怀疑人生。
无法做到功能复用。
我曾经做过一个项目,每个开通这个业务的城市,都要做一个单独的APP,初期我们只开通了3、4个城市,需要同时发布3、4个APP,这些APP大概6、70%功能是相同的,但是都需要加入地方定制功能。如果你每个APP都采用单一工程模式开发,刚开始你可能每个工程都有同样的代码存在,只需要复制拷贝一下就行,但是如果有需求要对这些进行修改时,你必须得每个工程都逐一修改一遍,然后每个APP都测试一遍,工作量直接翻了数倍。
业务模块间耦合严重。
采用单一工程模式开发项目,到最后势必会造成业务模块高度耦合,可以说是你中有我、我中有你,修改任何业务都有可能导致牵一发而动全身,这显然是不利于后期项目功能维护以及迭代开发的。
组件化开发的优点
- 极大提高工程编译速度。进行组件化拆分后,每个业务或者功能都是一个单独的工程,这个单独的工程可以独立编译运行,拆分后的工程通常都比较小,代码量也比较少,我再也不用像以前编译一下得等待好几分钟了。
- 业务模块解耦,有利于多人团队协作开发。业务组件之间不能相互引用,每个组件都把对应的业务功能收敛在一个工程里,彼此互不打扰。 在多人团队里,每个人只负责自己的业务模块,他对业务功能的增删改查,都只限定在自己的这个业务模块里,不会影响其他人的业务,他代码质量的好坏也只会影响到自己的业务模块;对测试来说,也十分方便,大部分情况下,我们只需要着重测试修改过的业务组件即可,而不用老是进行全部回归测试。
- 组件化是功能重用的基石。业务组件类似一个个积木一样,我们可以用积木搭建出不同的房子,同理我们也可以创建多个不同的APP。我们只需要维护好每个组件,需要用到该组件的功能时,一建引用集成就可以了。
当然,这并不是说组件化开发只有优点没有缺点,例如:
- 组件化开发前期可能要花费更多的时间来进行模块拆分;
- 一个人的小项目完全没必要组件化开发,那样只会给自己带来更多的工作量;
- 组件化可能会带来更多重复的代码;
- 组件化需要良好的架构设计,包括怎么拆分业务,组件之间怎么通信等等,需要有个高水平的架构师统筹全局,经验不足的同学盲目进行组件化反而会适得其反,带来更多的麻烦。
组件化架构设计
在进行组件化开发之前,一定要设计好构架方案,一般来说,会分为至少4层:APP壳工程、常规业务组件层、基础/公共业务组件层和基础功能组件层。
下面一个图示意了一个工程的典型分层:
我们来从下往上分析一下这样分层的思路。
基础功能组件层
这一层的功能都是最基础的功能,通常不包含任何业务逻辑,也可以说这些组件是一些通用的工具类。例如日志记录组件,它只是提供了日志记录的能力,你要记录什么样的日志,它并不关心;例如基础UI组件,它是一个全局通用的 UI 资源库;例如网络服务组件,它封装了网络的请求能力,等等。
基础业务组件层
这一层层组件是对一些系统通用的业务能力进行封装的组件。例如公共业务组件里,可以封装 BaseActivity、BaseFragment 等;例如分享能力组件,我封装了微信、QQ、微博等的分享能力,其他业务只要集成该组件就能进行相关分享;例如共享公共数据组件,可以封装应用能够全局访问的数据,如用户登录信息等。
常规业务组件
该层的组件就是我们真正的业务组件了。我们通常按照功能模块来划分业务组件,例如注册登录、用户个人中心、APP的首页模块等。这里的每个业务组件都是一个小的APP,它必须可以单独编译,单独打包成APK在手机上运行。
核心管理组件
主要包括路由服务组件、组件生命周期管理组件。路由主要是为了解决组件间通信问题,而组件生命周期管理主要是为了解决组件的初始化等问题。
APP壳工程
壳工程依赖了需要集成的业务组件,它可能只有一些配置文件,没有任何代码逻辑。根据你的需要选择集成你的业务组件,不同的业务组件就组成了不同的 APP。
在组件之间必须遵循以下规则:
- 只能上层组件引用下层组件,不能反向依赖,否则可能会出现循环依赖的情况
- 同一层组件之前不能相互依赖,这是为了组件间彻底解耦合
组件间的通信问题
在组件化实施的过程中,必然会遇到几个问题,比如:
怎么从这个组件跳转到另一个组件的页面
组件之间怎么传递数据
怎么获取其他组件的数据或服务
组件怎么通知其他组件响应某个事件
这些问题可以统统定义为:组件之间的通信问题。
我们来按照使用场景来分析一下。
页面跳转
在单一工程中,页面跳转可以直接用startActivity()
方法跳转页面。但是组件化之后,同一组件层的组件是不能相互依赖的,也就是说无法再通过startActivity()
来进行跳转了。这就必须要引用一个新的思路:页面路由。
路由框架现在成熟的有很多了,比如美团的WMRouter和阿里的ARouter。
路由框架的核心原理都是一样的,我们来简单分析一下。
实现思路是专门抽取一个 Module 作为路由服务,每个组件声明自己提供的服务 Service API,这些 Service 都是一些接口,组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去,如果要使用某个组件的功能,只需要向 Router 请求这个 Service 的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。看起来是不是有点像 Flutter 的路由?
数据传递
有了路由系统,数据传递自然也可以交给路由系统。举个例子:
我有一个路由,URI为:
myscheme://custompath/pageA?param1=*¶m2=*
param1 和 param2 自然就可以做为参数传入目标组件。
数据共享、事件响应
数据共享和事件响应可以采用 EventBus 来实现,但是 EventBus 在组件化开发时,一定要注意事件名的命名,组件事件名最好有规范的定义方式,避免不同人员开发时出现冲突,日后不好维护。
当然如美团的WMRouter也有比较成熟的 ServiceLoader 的技术方案,提供服务共享、数据共享。
组件的生命周期管理
每个Android应用启动时,都会先创建一个 Application。通常在 Application 里我们会做一些应用初始化的操作,常见的有第三方 SDK 初始化。在应用组件化之后,组件与壳工程是隔离开来的,但是组件有时候也需要获取应用的 Application,也需要在应用启动时进行初始化。这就涉及到组件的生命周期管理问题。
- 抽象出一个类似Application的类,它模拟了Application的几个主要方法:
public abstract class BaseAppLike {
public static final int MAX_PRIORITY = 10;
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
/**
* 返回组件的优先级,优先级范围为[1-10],10为最高,1为最低,默认优先级是5
*
* @return
*/
public int getPriority() {
return NORM_PRIORITY;
}
/**
* 应用初始化
*
* @param context
*/
public abstract void onCreate(Context context);
public abstract void onTerminate();
}
而组件内部都要实现一个继承 BaseAppLike 的类,并将其当做 Application 的容器。组件可以在这里获取 Application 实例、执行启动时的初始化等等。
这时我们假设有三个组件 ModuleA、ModuleB、ModuleC,这三个组件内部分别有 ModuleAAppLike、ModuleBAppLike、ModuleCAppLike,那么在壳工程中,初始化时可以这样做:
class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ModuleAAppLike moduleA = new ModuleAAppLike();
ModuleBAppLike moduleB = new ModuleBAppLike();
ModuleCAppLike moduleC = new ModuleCAppLike();
moduleA.onCreate(this);
moduleB.onCreate(this);
moduleC.onCreate(this);
}
}
这样会不会有问题?有。
组件初始化的顺序
前面介绍过,上层业务组件是依赖下层业务组件的,如果下层组件在应用启动时也需要初始化,那么我们在加载组件时,必然要先加载下层组件,否则加载上层组件时可能会出现问题。但是组件这么多,我们怎么确定要先加载谁后加载谁呢,当然你可以手动维护,代码里写死,但是当业务越来越多、时间越来越久,肯定不灵活,你新加一个业务组件进来,你都需要确定组件初始化先后顺序。所以,我们必须有个机制来确定组件初始化先后顺序。
类似线程优先级一样, 为每个组件定义了一个优先级,通过重写getPriority()
方法可以设置组件的优先级。优先级范围从[1-10],默认优先级都为5,下层组件或需要先初始化的组件,优先级设置高一点。这样我们在加载组件的时候,先对所有组件的优先级进行排序,优先级高的排前面,然后再按顺序进行加载组件,就可解决这个问题了。