文章目录
什么是组件化
模块化工程
在引入组件化之前,我们开发的APP工程架构模型基本上是这样的:
各个业务关联同一个/多个依赖库(模块),模块中封装常用的业务共用流程、网络请求、数据操作等,业务逻辑通过文件夹划分,且业务之间高度耦合,我中有你,你中有我。
这种单一的工程模块在业务功能简单且变动不大的情况下,能快速进行迭代开发,满足产品需求。但当应用业务越来越复杂,产品需求频繁变化的情况下,就会暴露出一些问题:
- 修改某个业务,因耦合度高,导致其他业务受影响
- 对工程所做的任何修改都要编译整个工程
- 每次修改,都需要覆盖所有业务测试
- 当多人进行协同开发时,业务的修改需同步到所有人
- 工程会越来越臃肿,后续维护成本高
组件化工程
当应用越来越复杂,为了解决模块化工程存在的种种问题,越来越多地应用开始引入组件化工程的架构,我们来一起看下它是如何解决这些问题的。
- 工程下的各个业务组件是独立的,没有关联,这意味着对其中一个业务组件进行修改,不影响其他业务。
- 各个业务组件,既可以单独编译运行,也可以集成到app工程中运行。当对单个业务进行修改时,只需要单独编译运行,从而提高编译效率。
- 若业务间通信接口未更改,则只需要对修改的业务组件单独进行测试即可。
- 多人协同开发,只需先定好组件通信的接口,剩余的开发和修改不会相互影响,提高开发效率。
- app工程可根据实际情况,自由加载需要的业务组件,降低后期维护成本。
除了能解决以上这些问题外,组件化还能够带来其他一些好处:
- 代码权限控制可精细到组件,更好地做代码管控
- 不需要熟悉所有业务,使新员工能更快上手
组件化实施
总体流程
- 根据APP开发的功能,对业务进行划分,得到多个业务组件,然后再对各个业务组件进行分析,抽离出公共的功能点,划分出多个功能模块。
业务组件和功能模块的不同点在于前者可单独编译运行,后者不行
- 定义好业务通信的接口,选用三方或者定制组件间的通信方案。
- 各个组件功能开发联调及测试。
- 将各个组件集成到同一个app,联调测试。
组件模式和集成模式
- 当划分好业务组件,需要对组件功能进行单独调试时,需使用组件模式
- 而组件功能已基本开发联调完成,需要集成到APP中测试时,需使用集成模式
组件模式和集成模式可在module中的build.gradle中进行设置:
//组件模式,设置为application属性,可单独运行
apply plugin: ‘com.android.application’
//集成模式,设置为libaray属性,不可单独运行
apply plugin: ‘com.android.library’
因组件开发经常需要在两种模式之间进行切换,如果每次切换都去修改每个module下的属性,就很会影响开发效率,需要提供一个自动切换的方案。
好在app下的gradle.properties可协助实现此功能,在该文件下配置的常量属性,可以在工程中的任意build.grade文件中读取。
首先我们在gradle.properties文件中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否)
# 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮
isModule=false
然后在业务组件中读取该值,并设置为对应的属性,从而实现自动切换
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
AndroidManifest.xml合并冲突
每一个业务组件都会关联一个AndroidManifest.xml,每个AndroidManifest.xml在组件开发模式下,都会包含一个app和launch activity。
组件单独运行时正常,但在集成模式下合并时,因存在多个app会导致冲突。
这里的解决思路类似于上个段落,不同模式加载不同的xml文件,组件模式下加载带app的xml文件,集成模式下加载不带app的文件。
同样地,在业务组件中指定对应AndroidManifest.xml文件的位置,解决合并冲突问题
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
组件数据初始化
当使用组件模式进行开发,需要获取应用上下文context及初始化数据,但由于应用上下文context是全局的,只能有一个,当多个组件进行合并的,如何共用一个context就是个问题。
这里我们可以建立一个common模块,用于封装项目常用的基础功能,每个业务组件都依赖它,并在其中创建一个BaseApplication类,用于应用初始化时,通过继承此类来获取context以及做组件的初始化工作。
通用NewApplication继承BaseApplication类,并在其中做组件的初始化工作,同时也可以在文件夹中增加组件测试用Activity,传递测试参数,如下所示:
public class LauncherActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
request();
Intent intent = new Intent(this, TargetActivity.class);
intent.putExtra("name", "avcd");
intent.putExtra("syscode", "023e2e12ed");
startActivity(intent);
finish();
}
//申请读写权限
private void request() {
AndPermission.with(this)
.requestCode(110)
.permission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE)
.callback(this)
.start();
}
}
接下来在业务组件的 build.gradle 中,根据 isModule 是否是集成模式将 debug 这个 Java代码文件夹排除:
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
}
}
}
组件化通信
前面我们解决了单独开发组件存在的模块切换以及文件冲突、数据初始化等问题,组件化还少不了需要互相通信,那如何在不相互依赖的情况,进行通信呢?
这里可以借鉴初始化数据的思想,将组件化通信统一通过三方进行处理,如下图所示:
各个组件通过统一的路由进行转发通信,实现组件解耦的目的。
组件间常见的通信场景包括:
- Activity的跳转
- Fragment的交互
- 组件服务接口的调用
- 支持跳转携带参数,支持startActivityForResult
- 跳转失败降级
网上有很多开源的路由通信引擎,可以满足组件化通信,常见的包括ARouter、CC、得到DDComponentForAndroid等
各开源引擎对比:https://github.com/luckybilly/AndroidComponentizeLibs
可根据APP开发实际情况选择合适的路由通信引擎,此处选取ARouter作为组件化通信的例子来说明
ARouter是一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。
具体使用说明可参见ARouter github地址
组件化最佳实践
类型划分
可将组件划分为不同类型,每个类型承担不同的职责,使得组件化工程标准化。
- common模块
各个组件共用的模块,用于封装一些基础功能、公用功能,可将组件共用的依赖库声明、工程权限声明放置在该模块中,方便组件使用
- 业务组件
承担具体的业务功能,业务组件的划分标准是功能独立可复用,声明isModule变量,用于集成模式和开发模式切换
- main组件
业务入口,一般启动页、欢迎页会放置在此组件中,通过此组件可跳转到业务组件
- app组件
app壳工程,没有具体的业务,一般把工程包名、版本号、应用图标及应用打包相关参数放置在此
统一配置
不同开发人员使用的电脑配置都是不一样的,这就导致了工程导入时经常因为版本的问题需要重新调试编译,导致浪费了很多时间。
我们可以约定在工程的根目录的build.gradle进行统一配置,当需要移植时,只需更改该文件即可,如下所示:
// Define versions in a single place
//时间:2017.11.28;每次修改版本号都要添加修改时间
ext {
// Sdk and tools
//localBuildToolsVersion是gradle.properties中的数据
buildToolsVersion = localBuildToolsVersion
compileSdkVersion = 26
minSdkVersion = 17
targetSdkVersion = 26
versionCode = 1
versionName = "1.0"
javaVersion = JavaVersion.VERSION_1_8
// App dependencies version
supportLibraryVersion = "26.+"
retrofitVersion = "2.1.0"
glideVersion = "3.7.0"
loggerVersion = "1.15"
eventbusVersion = "3.0.0"
gsonVersion = "2.8.0"
photoViewVersion = "2.0.0"
//检查时间:2017.6.6
annotationProcessor = "1.0.3"
routerVersion = "1.2.1.1"
easyRecyclerVersion = "4.4.0"
cookieVersion = "v1.0.1"
toastyVersion = "1.1.3"
gradle的buildToolVersion和gradlePluginVersion可配置在gradle.properties中
# 为自动化出包配置(因为每个开发的电脑坏境不一致)
localBuildToolsVersion=26.0.2
# 这个值一般跟你的AndroidStudio版本号一致
localGradlePluginVersion=2.3.3
组件化混淆
一般情况下可直接在app组件中配置混淆,应用打包发布也都是在这个组件中,这么有几个好处:
- 在app组件中混淆,其余组件不混淆,方便根据日志定位问题原因
- 只生成一份混淆配置文件,便于修改和管理
组件化Demo
根据本篇组件化思想,开发了一个入门的组件化实例,供各位参考。
下载地址:组件化入门实例