Android组件化探索与实践
在Android开发中,随着业务复杂性不断的增加,项目的代码量会不断的增大,这对于项目的维护提出了更大的挑战。Android的组件化开发就是旨在解决大型项目的可维护性、可扩展性的难题。
一、组件化简介
组件化是大型Android项目的一个好的解决方案。通过对项目工程进行组件化,利于代码的维护与扩展,特别是在多团队协作开发的模式中,尤其有效。
对于Android组件化的讨论其实已经持续很长的一段时间了,也产生了很多种的组件化方案,不同的组件化方案有不同的利弊,但是其组件化目标基本一致,只是实现思路可能不同。本篇文章就是讲解我们实现的一种组件化方案。
首先需要了解什么是组件化?简单来说,组件化就是对项目依据业务功能拆分成不同的模块,这些业务功能模块彼此独立,不相互依赖,既可以独立编译运行,也可一起打包成一个app。通过下图来对比一下组件化的特点。
图1.1传统项目组织方式
图1.2 Android组件化方式
通过上图可以明显的看出,传统的项目组织形式,不同模块之间会相互引用,造成耦合。而在组件化工程中,不同业务模块之间是没有相互引用关系的,彼此项目独立。
二、组件化目标
在了解了组件化的定义之后,需要明确组件化的目标主要是什么。组件化的目标主要是解决如下几方面的问题。
解耦合
随着项目不断的迭代,代码越来越庞大、臃肿,这时代码的维护就会显得极其繁重。如果能够根据业务来对项目进行业务模块划分,不同开发人员来维护不同业务模块,同时业务模块之间彼此无关联,这样就方便了项目的维护。
代码复用
组件化后,可以将一些功能组件、业务组件做成aar,上传到maven私服,这样便于其他团队通过maven来引用,快速的引入相关的功能模块。
加快编译速度、偏于开发调试
当项目很大的时候,编译一次是非常耗时的。如果项目采用组件化,那么业务模块可以作为主module运行,这样在开发阶段,可以只加载开发人员关心的模块,而无需加载所有模块,这样会大大的提供编译速度,同时也可以明确问题边界,利于bug的分析、处理。
便于多团队协同开发、维护
当不同团队维护一个app时,是极容易造成代码冲突的。组件化即可完美解决这个问题,不同的业务团队维护不同的业务组件,业务组件之间彼此隔离,不会相互干扰。
三、组件化设计
在上一节中明确组件化需要解决的问题,本小结就来讨论下如何通过组件化的设计来逐条解决上述问题。
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便于多团队协同开发、维护
通过之前的论述可以看出,项目进行组件化改造之后,各个业务组件之间相互独立,无直接的引用,这样不同业务组件之间可以进行独立的黑盒开发、面向接口开发,而彼此不会相互依赖,极大的方便了多团队的协同开发,减少冲突的可能行。
四、总结
前面论述了组件化的定义、目标、实现思路等内容。通过之前的论述可以看到,我们在进行组件化探索与实践中,主要用到的技术包括路由、编译期注解处理、反射、gradle配置等技术、技巧,通过这些技术方案的实施,形成了我们自己的组件化方案框架,达到了组件化的目的。
来源:oschina
链接:https://my.oschina.net/u/4342169/blog/4264833