前言
相信各位小伙伴们对组件化开发都不陌生了,本文只对我所理解和使用的组件化开发方案做一个总结,有不正确或者需要改进和优化的地方,望大家及时指出
如何将一个项目组件化
下面以我的ModuleDemo举例说明
架构图
大致分为4部分:
1. App壳
启动页,组件初始化,项目基本框架
2. 业务组件
具体的组件,例如上图home,moduleA组件
3. 组件基础库
例如上图business
定义了各个组件对外提供的服务,组件间的共享资源
4. 通用库,路由库
全局通用的资源(例如Theme和color等),第三方库
路由库主要用户业务组件间交互
调试和发布
为了避免每个组件都进行重复的一些配置,我这里对gradle做了些封装,下面会给完整的配置,这里我们先来看看大致步骤
工程 gradle配置
apply from: "${rootProject.rootDir}/version.gradle"
统一依赖,组件模式开关
App gradle配置
dependencies {
implementation rootProject.ext.dependencies["appcompat-v7"]
if (rootProject.ext.isModuleADebug){
implementation project(':modulea')
}
if (rootProject.ext.isHomeDebug){
implementation project(':home')
}
}
组件单独运行时,取消app的依赖
组件 gradle配置
模式切换
apply from: "${rootProject.rootDir}/config.gradle"
def isHomeDebug = rootProject.ext.isHomeDebug
if (rootProject.ext.isHomeDebug) {
project.ext.setLibDefaultConfig project
} else {
project.ext.setAppDefaultConfig project
}
通过控制isHomeDebug的值,来导入不同的配置,调试模式引入Application插件,和设置Application相关的配置,组件模式引入library插件,设置library相关配置
指定资源目录
sourceSets {
main {
if (!isModuleADebug) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//集成开发模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
}
}
}
如图
当组件作为单独的project运行时,我们需要提供调试相关一些资源,所以我创建了两个文件夹,module文件夹里面提供AndroidManifest为了调试模式时的启动页配置,debug文件夹可以放置调试时需要使用的资源,例如Activity等
UI跳转
组件内部,采用原生的跳转方式,不同组件间采用Arouter中的Provider来提供组件的相关跳转,我觉得这样可以很清楚的知道每个模块对外提供的功能,比起在模块上的Activity上加注解跳转方式好一些
在business组件基础库中,定义home组件的接口
/**
* home模块对外暴露功能
*/
public interface IHomeProvider extends IProvider {
void goHome(Context context);
}
在home组件中实现该接口
@Route(path = RouterHelper.ROUTER_HOME_PROVIDER)
public class HomeProviderImpl implements IHomeProvider {
@Override
public void init(Context context) {
Log.d("HomeProviderImpl", "home初始化");
}
@Override
public void goHome(Context context) {
Intent intent = new Intent(context, HomeActivity.class);
context.startActivity(intent);
}
}
跳转
((IHomeProvider) RouterHelper.getModule(RouterHelper.ROUTER_HOME_PROVIDER)).goHome(MainActivity.this);
组件通信
组件间都是相互独立的,他们之间不存在任何依赖.没有依赖,就无法产生关系,没有关系就无法传递任何的信息.这个时候我们需要依赖第三方协助我们,也就是业务基础库,所有的业务组件都依赖该库,通过它来进行通信.
我们可以采用事件总线的方式,例如EventBus,将在业务基础库中定义需要传递的消息对象,在业务组件中订阅该消息,通过这种方式我们就可以实现不同组件中的消息通信,当然我们同样也可以在上面提到的Provider方式,不过我觉得这两种方式没啥区别
注意点
组件化过程中还是有许多需要注意的地方,这里主要说三个地方
1. 依赖冲突
每个组件都可能引入自己的库,这样合并的时候就会产生冲突,解决这个冲突的方式就是统一依赖管理,后面会给出详细配置
2. 不要使用butterknife
相信很多人都喜欢使用这个库,但是组件化中最好还是不要使用了,虽然它提供了R2这种方式解决了lib中的id问题,但是每次单独运行的时候得手动改成R,个人觉得有点麻烦,再说findviewbyid也花不了多少时间,一个插件的就搞定的事,也不容易出问题
3. 资源冲突
如果A和B组件中都有同一个名字的资源文件,在集成模式的时候打包就会报错,可以使用在gradle中配置resourcePrefix 来解决
//设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
//但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
resourcePrefix "模块名"
当然也可以团队约定好代码规范,那样更好
详细配置
version.gradle
ext {
isHomeDebug = true
isModuleADebug = true
android = [
applicationId : "com.hc.moduledemo",
compileSdkVersion: 27,
minSdkVersion : 19,
targetSdkVersion : 22,
versionCode : 1,
versionName : "1.0",
supportVersion : "27.1.1"
]
dependencies = [
"arouter" : ["arouter-api" : "com.alibaba:arouter-api:1.4.1",
"arouter-compiler": "com.alibaba:arouter-compiler:1.2.2"],
"appcompat-v7": "com.android.support:appcompat-v7:${android.supportVersion}"
]
}
config.gradle
//所有业务模块通用的一些配置
ext {
//设置App配置
setAppDefaultConfig = {
extension ->
extension.apply plugin: 'com.android.application'
extension.description "app"
setAndroidConfig extension.android
setDependencies extension.dependencies
extension.android.defaultConfig {//设置applicationId
applicationId rootProject.ext.android.applicationId + "." + extension.getName()
}
}
//设置Lib配置
setLibDefaultConfig = {
extension ->
extension.apply plugin: 'com.android.library'
extension.description "lib"
setAndroidConfig extension.android
setDependencies extension.dependencies
}
//设置Android配置
setAndroidConfig = {
extension ->
extension.compileSdkVersion rootProject.ext.android.compileSdkVersion
extension.defaultConfig {
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: extension.project.getName()]
}
}
}
}
//设置依赖
setDependencies = {
extension ->
extension.implementation fileTree(dir: 'libs', include: ['*.jar'])
extension.testImplementation 'junit:junit:4.12'
extension.androidTestImplementation 'com.android.support.test:runner:1.0.2'
extension.androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
extension.annotationProcessor rootProject.ext.dependencies["arouter"]["arouter-compiler"]
}
}