工程链接
1.背景介绍
工程配置 android gradle3.0.1,Gradle4.4。因为Android Studio的版本不同,可能upgrade gradle plugin的时候,会触发起码module的build.gradle的运行,而此时module的build.gradle又要使用自己写的这个plugin的东西去解析自己的声明,所有这就是一个鸡和蛋的问题。所以建议将module_build单独拉project,编辑上传本地maven库,然后实例工程使用上传的gradle插件记好了;
最新的修改:所有的module都变为地位平等,app的module也需要配置是否可以作为其他module的依赖module。gradle编译的时候,一般命令为:gradle build(表示运行以根目录下config.gradle中配置的main_module为主module的编译工作),单独编译指定module,可以使用命令:gradle :module_name:task_name。
项目功能已趋近稳定,具体每个功能都已经有完备的流程;
就目前我看到的产品业务发展规划,是在不断完善项目,修复bug的前提下,主要工作就是对接第三方的设备。
而在这个过程中,比较突出问题:针对越来越多不同设备,不同需求的接入,修剪适配项目代码,但是前期代码糙快猛,模块功能之间耦合比较严重,对项目代码的修剪工作造成了不小的麻烦,多半情况下的妥协处理就是把不需要的功能入口屏蔽掉,但是相关代码还存在。所以即使某个第三方设备仅仅需要一小部分的功能,其安装包也是和全功能的版本大小一样的,这种在我们推送有流量限制的前提下,是很不节俭的。
另一个也是代码耦合造成的问题:因为所有功能都耦合在一个module中,在一个版本的迭代中,就有可能因为相互依赖的问题,本该并行开发的工作,变成了线性,严重影响效率。对于测试人员,也要因为任何的改动,做全功能的回归测试。
基于以上,项目的组件化重构就提上了日程。
2.组件化原理
- 组件化的定义:
Component-based software engineering (CBSE), also known as component-based development (CBD), is a branch of software engineering that emphasizes the separation of concerns in respect of the wide-ranging functionality available throughout a given software system. It is a reuse-based approach to defining, implementing and composing loosely coupled independent components into systems. This practice aims to bring about an equally wide-ranging degree of benefits in both the short-term and the long-term for the software itself and for organizations that sponsor such software.
概括一下,就是基于可重用的目的,将一个大型的软件系统按照分离关注点的形式,拆分成多个独立的组件,以此减少耦合。针对我们的项目,大体的体现如下:(未体现base-lib等底层公共module)
组件化思想应用到我们的项目上,就是将收银,Erp,团购验券,预授权等业务模块进行解耦,独立成单独的组件,降低业务复杂度,每个组件都可以有自己独立的版本。根据不同的设备和商务需求,动态的去组合不同组件。这样做的好处,
代码耦合降低,一个模块的改动不涉及或者很少涉及其他模块,提升代码质量,减少出bug的概率;
代码修改范围变小,测试的工作量也相应的减少,将具体的业务分拆组件化之后,可以将没有变动的模块剔除回归测试的checklist,减少测试工作量;
每一个独立的组件,都可以独立运行,方便开展独立测试;
根据不同设备的需求,定制app,不需要的组件不会打包到app中,减少apk的大小;
3.方案选择
明确了目标,那么就要提出对应的实现方案,网络上有一热心网友对比了几个比价成熟的组件化方案:组件化方案对比
根据热度和传播程度,重点关注了一下得到 DDComponentForAndroid 和 阿里Arouter
DDComponentForAndroid 在路由上面的设计和其他几家通过注解,动态代码生成的实现方式都是大同小异的,但是他在使用Gradle Plugin实现Module之间的完全隔离上,想法比较新颖。只需要声明使用一个plugin,完全不用再多处手动配置手动配置的实例;
ARouter 严格意义上说,不能算是一个组件化框架,是一个用来解耦的路由框架,通过路由完成功能模块间之间的跳转,杜绝了模块间之间的直接依赖,也是组件化重要的思想和工具。
经过方案的对比,所以最终决定我们项目中的组件化使用:
ARouter (路由功能) + Gradle Plugin(Module隔离,Module初始化代码动态注入)
4.方案实施
1, ARouter的使用
由于ARouter推出时间比较长,功能多样,性能稳定,关于如何使用,网络上比比皆是,没必要过多介绍了,这里重点关注一下其优缺点和典型的应用场景:
功能介绍
支持直接解析URL进行跳转、参数按类型解析到Bundle,支持Java基本类型(*)
支持应用内的标准页面跳转,API接近Android原生接口
支持多模块工程中使用,允许分别打包,包结构符合Android包规范即可(*)
支持跳转过程中插入自定义拦截逻辑,自定义拦截顺序(*)
支持服务托管,通过ByName,ByType两种方式获取服务实例,方便面向接口开发与跨模块调用解耦(*)
映射关系按组分类、多级管理,按需初始化,减少内存占用提高查询效率(*)
支持用户指定全局降级策略
支持获取单次跳转结果
丰富的API和可定制性
被ARouter管理的页面、拦截器、服务均无需主动注册到ARouter,被动发现
支持Android N推出的Jack编译链
2 不支持的功能
自定义URL解析规则(考虑支持)
不能动态加载代码模块和添加路由规则(考虑支持)
多路径支持(不想支持,貌似是导致各种混乱的起因)
生成映射关系文档(考虑支持)
3 典型应用场景
从外部URL映射到内部页面,以及参数传递与解析
跨模块页面跳转,模块间解耦
拦截跳转过程,处理登陆、埋点等逻辑
跨模块API调用,模块间解耦(注册ARouter服务的形式,通过接口互相调用)
2,关键点:组件化Gradle插件
上文提到一种手动配置组件合并打包和组件独立打包的开发模式,其配置需要过多人为的参与。在配置完后,当前的配置只能符合一种组件组合方式的运行,如图所示:
当前可以运行app这个module,可能app module依赖于base,payplatform这两个module;
但是如果我现在想运行payplatform这个module,那么就需要手动的去改每一个module的gradle配置,然后clean project。操作完全部修改步骤之后,才能run payplatform,着实繁琐。
那么我们的目标是如下图这样的:
每个module都是不需要手动去修改配置的,选中任意一个module,根据预先配置好的module依赖关系,会自动将配置的组件打包进apk,然后运行在机器上。
每个module都可以直接run的秘密:
每一个module都会有一个build.gradle文件,其管理当前module的配置,在每一个build.gradle的开头,都有一句声明:
只要是使用application plugin的module,AS就认为这是一个可运行的module,声明为library plugin的module,AS认为它是一个库,不具备单独运行的能力,因此也就会在Run Configurations中对应的module上打上一个叉号❌。
那么我们是不是可以直接将所有module都声明使用application,就可以达到所有module全部可运行的目的呢?图样图森破。
不能这么操作的原因有二:
当一个module作为application开始build时,他要求他所依赖的其他所有module,都必须使用library plugin。不然build是无法完成的。
在build过程中,其中有一步就是merge AndroidManifest.xml,其会将所有module的manifest做一个合并,其他module作为app运行,一定会声明作为LAUNCHER的Activity,也多半会声明自定义的Application,那么在合并时就会报错,build system不知道要保留哪一个module的Application。即使build完成之后的安装,也会因为manifest中存在声明的每个module的LAUNCHER,而会在桌面上出现多个启动图标。
那么我们自定义的gradle plugin,就需要在点击run时,根据所点击的module,动态的取修改所有的module的build.gradle;大体流程如下:
步骤解析
当run某一个具体module的时候:
step1:
在每个module下,都有一个gradle.properties的配置文件其中声明了当前module作为独立app运行时,需要依赖的module信息:
当在命令行下执行:
表明当前是将app 这个module作为主module,那么其就会读取app module目录下的gradle.properties,获得app 所依赖的module信息。
step2-1,step2-2:
对应的,需要在module中建立相应的目录:
step3:
一般我们会将一些全局的初始化动作,变量在自定义的Application中进行声明。在Module作为独立App 进行build的时候,这样做是没有问题的,但是当Module作为组件被包含在主Module的代码中时,如上面提到的,为了避免merge Manifest失败,就不能再在Module自身的Manifest中声明自定义的Application了,且即使Module继续使用自定义Application,那么此时Module的Application也是一个普通的类,没有生命周期,不会被系统调用。那么Module又必须要进行一些初始化,怎么做呢?此时就要使用到android gradle1.5之后提供的Transform接口了:transform-api简介,配合transform-api就需要javaassist基本操作demo,一个能够将动态生成的代码插入到class文件的开源库,最终编译成dex,打包到apk中。
首先在每个module的build.gradle中声明,module作为app,作为组件时初始化使用的类名:
然后插入到transforms过程中的自定义Transform做如下操作:
|
|
编译完成之后,通过反编译看一下是否奏效了:
|
|
bingo,此时,在启动apk时,也会自动的启动Module的初始化工作。
5.总结
在每一个Module(不管是主工程,还是其他功能Module),都声明使用该gradle插件,然后在对应Module的build.gradle中,声明该模块单独运行和作为module运行时,初始化需要调用的类(都是继承自AppSimilar接口)。
在对应Module的proper.properties文件中声明,当前这个模块如果单独运行所需要依赖的其他module信息。
像base_lib这种,不是独立功能模块,直接使用android library插件,不能声明使用组件化的gradle插件。
然后选择module,直接run就可以了(如果碰到编译错误,什么R8之类的,clean一下再run)。
附1:关于Android Gradle dependencies中集中依赖方式的区别:
可以看到,在android Gradle 3.0以后推出的runtimeOnly是最接近于我们对于组件化的需求的。但是其还是有些许不足,runtimeOnly虽然能隔离module间的java依赖,但是没有隔离资源,也就是moduleA可以直接调用到moduleB的layout,string,drawable等,无法实现我们期望的module间完全隔离。
附2:Android App Bundle 官方的动态插件方案
The new app publishing format, the Android App Bundle, is an improved way to package your app. The Android App Bundle lets you more easily deliver a great experience in a smaller app size, allowing for the huge variety of Android devices available today. You don’t need to refactor your code to start benefiting from a smaller app.
感觉国内的一杆插件化,要毙掉了 :P