组件化架构
组件化和插件化的目的都是为了解决项目越来越复杂,耦合性高,牵一发而动全身,一个小的改动也要编译十几分钟等问题,两者的区别简单来说组件化是在编译期分模块,插件化是在运行期。一般插件化用于动态修复bug或者动态更新模块,相对来说黑科技更多一些,而相对于插件化,组件化的架构更容易操作,效率高,基于安卓自有特性和gradle的功能,没有插件化那么多的坑,对于大多数应用而言,其优势还是相当明显的。
先看下我们的架构图:
App控制中心主要负责组件的添加和删除,路由规则的定义。
module是我们单独开发的模块,如个人中心,详情页,专题页等等
common主要功能就是集合第三方的library,统一引入到项目中
原理与思路:
为每一个module设置library和application两种属性,通过配置文件控制,当集合一起编译时作为library依赖到app控制中心,当独立开发时切换到application属性变成一个普通的module,common作为一个公共的三方库集合,页面跳转基于ARouter路由框架,消息传递用可以用EventBus,当然也可以用Arouter内部的API。
框架概览:
其中main、modulefirst和modulesecond是三个独立的模块,应用的首页在main里,当然也可以指定到其他组件,只需要配一个注解就可以,修改起来非常方便。
当为集合编译时项目是这样的:
当为独立开发时项目是这样的:
开始搭建
1.创建完工程后,配置可切换library和application属性的文件。
在project根节点下在gradle.properties文件添加以下代码:
# 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮
isModule=false
然后在每个组件的build.gradle根节点里添加代码:
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
2.清单文件的处理
由于每一个组件都是一个独立的APP,都有自己的清单文件,都有自己的Application属性和MainActivity,当合并一起编译时势必会和其他组件的清单文件产生冲突,而我们自己的模块又需要自己开发时用到清单文件,为此,我们的解决方法是:分别建立一个debug文件夹,一个release文件夹,分别放置一个清单文件,不同的工程属性时启用对应的清单文件即可解决,两个清单文件的区别如图所示:
然后再build.gradle里添加如下代码:
android {
/……/
//这里是我们添加的代码
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
//release模式下排除debug文件夹中的所有Java文件
java {
exclude 'debug/**'
}
}
}
}
}
3.解决全局Application和组件自己的Application的冲突问题
应用启动时,系统会为每一个应用创建唯一一个Application对象,其生命周期最长一直到应用结束,我们开发时往往会自己定义一个Application,做一些初始化的操作,这时就要去告诉系统要实例化自己定义的,也就是我们在清单文件的Application节点配置的name属性,那么我们的组件本就是独立的APP,肯定会有自己的Application,如何合并编译,肯定又有问题了,对此,我们的的解决方法是,先创建一个Common库,这个库包含了各种公共的类,如BaseActivity,BaseApplication,Utils等等,每一个组件的Application都继承自Common的BaseApplication,这样组件就可以用自己的Application了,记得在自己的清单文件中做相应修改。为了保证合并编译时只有一个BaseApplication,我们可以这样修改
然后在build.gradle里的这一行代码就起作用了:
4.对编译版本统一配置,依赖库也统一配置
这样做的目的是为了统一组件的工作环境,避免API版本参差不齐带来的问题,同时也符合规范化管理的要求,而且便于维护,默认情况下,如果是 aar 依赖,gradle 会自动帮我们找出新版本的库而抛弃旧版本的重复依赖。但是如果你使用的是 project 依赖,gradle 并不会去去重,最后打包就会出现代码中有重复的类了。为了避免同一个库依赖两次,我们把所有第三方依赖放到Common库中管理,组件的build.gradle里只需要这样写:
5.对App控制中心模块的处理
App模块在项目里负责路由规则定义和组件注册,没有界面,虽然看起来和普通module一样,但是千万不要搞混了,它除了管理功能,还有就是作为整个项目合并编译的端口,通常我们debug编译都是习惯这样
这里app就是这样一个作用,双击即可合并所有组件一起编译,在此雄伟的壮举成功之前,我们一定要注意先修改App模块的build.gradle文件如下:
代码贴出来
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
if (!isModule.toBoolean()) {
compile project(':main')
compile project(':modelfirst')
compile project(':modelsecond')
} else {
compile project(':common')
}
}
顺便也把全局编译版本统一配置:
在project的build.gradle里这样写
ext{
// Sdk and tools
buildToolsVersion = localBuildToolsVersion
compileSdkVersion = 25
minSdkVersion = 16
targetSdkVersion = 25
versionCode = 1
versionName = "1.0"
javaVersion = JavaVersion.VERSION_1_8
aptCompilerVersion = "1.1.7"
routerVersion = "1.2.2"
loggerVersion = "1.15"
}
组件的build.gradle里这样写
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
}
/……/
}
6.最后就是各个组件之间的跳转问题也是最关心的问题。
这一部分,对于不熟悉Router的我真是踩了不少坑,其实总结起来却是非常简单,只是一些小的细节没有注意,你的APP就总是无法跳转成功。先说项目如何配置:
本文章用的是github一个开源路由框架:ActivityRouter里面有详细的说明,另外阿里也开源了一个路由框架:ARouter,有时间的化可以拿过来研究一下,使用起来都比较容易上手的,如果你想自己实现一个路由,可以参考这篇文章:Android路由实现
在project的build.gradle添加代码:
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
//这是我们要添加的
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
然后在每一个组件的build.gradle里添加:
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
apt 'com.github.mzule.activityrouter:compiler:1.1.7'
compile project(':common')
}
如图所示:
sync工程一下后,开始处理路由配置
首先是App模块,这个是编译入口,需要管理所有组件,
新建一个类文件
注意图中的注解,是@Module,不是@Modules,
然后给MainApplication添加注解,这里注意是@Modules了,如图:
接下来处理应用入口的组件,注意APP只是编译控制组件,应用的主页是由另一个组件控制的,这里我定义为main组件,这个地方需要注意,和其他模块略有不同,如图:
注意看,这个Activity是没有注解的,同时,其清单文件要添加Launcher属性。
对应的定义一个Main类,如图
和入口组件不同的是,其他组件的Activity上要加注解:
这里注意,注解里的字段一定是和组件module的名称是一致的!
接下来就可以开始跳转了,随意定义几个按钮,然后添加监听里的方法
当然路由协议有多种跳转规则,后期我会将其他示例逐步加到框架里。
关于组件间的通信
组件内跳转建议还是采用startActivity,组建间跳转,可以用ActivityRouter的路由协议:
例如:
组件2定义了两个Activity:
@Router("modelsecond")
public class ModelSecondActivity extends BaseActivity{……}
……
……
@Router("modelsecond/:demo")//注意这里“/:demo”即作为此Activity的定位符,路由可以据此找到对应Activity
public class ActivityDemo extends BaseActivity{……}
组件1中有一个Activity:
@Router("modelfirst")
public class ModelFirstActivity extends BaseActivity{}
现在如果想从组件1的ModelFirstActivity 跳转到组件2的ActivityDemo ,那么点击事件里只需要这样写:
Routers.open(ModelFirstActivity.this, "module://modelsecond/:demo");
如果需要传递intent参数,那么只需要这么写:
Routers.open(ModelFirstActivity.this, "module://modelsecond/:demo?sign=abcdef&name=jack");
在需要获取参数的地方这么写:
getIntent().getStringExtra("sign");
getIntent().getStringExtra("name");
更多细节可以参考这里:【ActivityRouter】
如果单纯的传递消息可以用EventBus
注意事项
- 每一个组件,要给自己的所有资源文件制定命名规则,避免和其他组件资源重名,切记
- 注意入口组件和其他组件的区别是入口Activity是没有注解的
- App控制中心组件也就是编译组件里,@Module和@Modules切记不要记混了
- 总之就是细心,再细心,一定要自己动手搞一遍
最后贴出demo地址:【传送门】
参考文章: