Android组件化探索与实践
在Android开发中,随着业务复杂性不断的增长,项目的代码量会不断的增大,这对于项目的维护提出了更大的挑战。Android的组件化开发就是旨在解决大型项目的可维护性、可扩展性的难题。
1、组件化简介
组件化是大型Android项目的一个好的解决方案。经过对项目工程进行组件化,利于代码的维护与扩展,特别是在多团队协做开发的模式中,尤为有效。
java
对于Android组件化的讨论其实已经持续很长的一段时间了,也产生了不少种的组件化方案,不一样的组件化方案有不一样的利弊,可是其组件化目标基本一致,只是实现思路可能不一样。本篇文章就是讲解咱们实现的一种组件化方案。app
首先须要了解什么是组件化?简单来讲,组件化就是对项目依据业务功能拆分红不一样的模块,这些业务功能模块彼此独立,不相互依赖,既能够独立编译运行,也可一块儿打包成一个app。经过下图来对比一下组件化的特色。框架
图1.1传统项目组织方式
maven
图1.2 Android组件化方式ide
经过上图能够明显的看出,传统的项目组织形式,不一样模块之间会相互引用,形成耦合。而在组件化工程中,不一样业务模块之间是没有相互引用关系的,彼此项目独立。组件化
2、组件化目标
在了解了组件化的定义以后,须要明确组件化的目标主要是什么。组件化的目标主要是解决以下几方面的问题。
gradle
解耦合
随着项目不断的迭代,代码愈来愈庞大、臃肿,这时代码的维护就会显得极其繁重。若是可以根据业务来对项目进行业务模块划分,不一样开发人员来维护不一样业务模块,同时业务模块之间彼此无关联,这样就方便了项目的维护。
ui
代码复用
组件化后,能够将一些功能组件、业务组件作成aar,上传到maven私服,这样便于其余团队经过maven来引用,快速的引入相关的功能模块。
this
加快编译速度、偏于开发调试
当项目很大的时候,编译一次是很是耗时的。若是项目采用组件化,那么业务模块能够做为主module运行,这样在开发阶段,能够只加载开发人员关心的模块,而无需加载全部模块,这样会大大的提供编译速度,同时也能够明确问题边界,利于bug的分析、处理。
设计
便于多团队协同开发、维护
当不一样团队维护一个app时,是极容易形成代码冲突的。组件化便可完美解决这个问题,不一样的业务团队维护不一样的业务组件,业务组件之间彼此隔离,不会相互干扰。
3、组件化设计
在上一节中明确组件化须要解决的问题,本小结就来讨论下如何经过组件化的设计来逐条解决上述问题。
3.1 解耦合
在传统方式中,不一样的module之间想要相互调用,须要经过在gradle文件中相互引用,这样才能够获取到module中的类。而在组件化中,须要在不一样模块彼此隔离的状况下相互调用,这样才能达到解耦合的目的。
能够经过路由框架来实现不一样业务模块之间的隔离。咱们的组件化实践采用了阿里的ARouter。经过ARouter能够实现Fragment的实例获取、Activity之间的跳转、交互等。举例以下:
业务组件B想要跳转到业务组件A中的Tab页面,能够经过路由框架跳转:
业务组件A中TabActivity定义以下:
@Route(path = "/app/tab")
public class TabActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
业务组件B中利用路由框架来跳转:
ARouter.getInstance().build("/app/tab").navigation()
能够看到,经过路由框架,在业务组件B中,无需引入业务组件A中的TabActivity类便可实现页面的跳转。这样就达到了解耦合。
组件之间页面的跳转能够经过如上的方式来解决,还有一种情景,经过如上的路由方式是解决不了的,即接口的调用。例如:业务组件B想要调用登陆组件中获取登陆信息的方法。想要实现这样的功能,比较通用的实现方式是接口下沉。
图3.1接口下沉方式
这种方式的缺点就是:每一个业务组件都须要把对外暴露的方法下沉到底部的组件中,这样其实并无很好的实现解耦合。
而咱们的组件化方案则采用了一种不一样的方式,这也是本文所论述的组件化实践中的一个核心内容。咱们采起的方案是将业务组件拆分红两个模块:对外暴露的接口模块、业务功能模块。以下图所示:
图3.2 模块拆分
以上图为例,将本来的业务模块Module A,拆分红接口模块Module A interface、业务模块Module A。接口模块的做用是对外暴露接口,而业务模块是接口真正的实现类,其会实现Module A interface中的接口。若其余模块想要调用此模块的相关功能,则只需引用其接口模块,而无需引用其业务模块,这样就实现了业务模块之间的解耦合。
剩下要解决的问题就是如何在只引用接口的状况下,就能调用接口实现类的相关方法。咱们采用的方式是编译期注解处理、反射等技术。详细论述以下。
首先,须要定义两个注解,BxBundleService、BxService。BxService的做用是标记对外暴露接口的实现类,BxBundleService的做用是标记须要注入的接口实例。以登陆模块为例示例以下:
登陆接口模块对外提供的接口:
登陆业务模块中会实现上述的接口,经过BxService注解来标记其为实现类:
其余业务模块能够在须要的时候调用登陆模块中的方法:
其次,有了注解的标记,接下来就是注解的处理,咱们为了提升程序的运行效率,没有采用运行时处理注解,而是采用了编译期间处理注解,具体以下:
1.引入编译期注解处理类库,在gradle文件中引入:
2. 定义Processsor,在编译期对注解进行处理
注解处理,这点也是本组件化实践过程当中的核心要点
3.1 遍历BxService注解标记的类,对其生成一个辅助源文件,以登陆模块为例,生成以下的辅助文件
3.2 遍历含有BxBundleService注解的类,完成BxBundleService标记的属性对象的注入,其也是经过生成辅助源文件的方式来完成。生成的文件以下
辅助源文件的生成可使用javapoet库来实现。这里再也不论述。
4.流程串联,在使用登陆模块时,须要经过BxbankModuleBus.inject(this)方法先完成服务的注入,其实现方式以下
经过代码能够看出,其经过反射获取到BxbankModuleBusInterface实例,并调用期bxbankModuleBusInject方法,此时再看上面3.2中的BxbankModuleBusInterface的实现类能够看到,bxbankModuleBusInject方法的主要做用就是找到LoginService的全路径名,并经过反射生成其实例,并最终将其实例注入到引用对象中。至此完成了loginService的实例注入,以后就能够经过loginService实例来进行登陆模块的相关调用。
经过前面的论述能够看到模块间只需引入模块的接口模块,就可完成模块间的相互调用,达到了解耦合的目的。
3.2 代码复用
对代码进行模块拆分后能够看到,代码已经完成了高内聚、低耦合,一些基础的功能模块、业务模块能够提供给其余项目组来复用。
3.3 加快编译速度、偏于开发调试
有时全量编译整个工程是很耗时的,也是没必要要的。例如某个开发者只负责登陆模块,那么其能够排除其余的业务模块,而只编译、运行登陆模块来进行开发和维护,这样能够大大的提供编译效率,提高开发体验。能够采用以下的技术方案来达到这个目的:
首先:在工程的build.gradle文件中定义个变量来控制组件是否能够独立编译、运行
其次:在模块组件的build.gradle文件中经过上面定义的变量来决定其是否做为主module运行
最后:须要对源文件进行处理,由于主module的manifest文件中,应包含launcher页面的入口,以下:
从配置中能够看出,根据isModule变量的不一样,源码则引入不一样的manifest文件,这样就实现了业务组件的独立编译运行。
3.4便于多团队协同开发、维护
经过以前的论述能够看出,项目进行组件化改造以后,各个业务组件之间相互独立,无直接的引用,这样不一样业务组件之间能够进行独立的黑盒开发、面向接口开发,而彼此不会相互依赖,极大的方便了多团队的协同开发,减小冲突的可能行。
4、总结前面论述了组件化的定义、目标、实现思路等内容。经过以前的论述能够看到,咱们在进行组件化探索与实践中,主要用到的技术包括路由、编译期注解处理、反射、gradle配置等技术、技巧,经过这些技术方案的实施,造成了咱们本身的组件化方案框架,达到了组件化的目的。