前言
架构设计是一个不断演变的过程,当项目较小,或者项目刚刚起步的阶段,我们往往不需要关注架构设计,只有当软件膨胀到一定程度,我们才会针对当前业务,设计出适合当前阶段的架构。所以有的项目就会出现不断膨胀,不断重构的情况。那为什么不一开始就设计一个大而全的架构?我认为有以下几点:
1、架构是对当前业务进行的一层抽象,当业务不稳定,或者在高速迭代的过程中,我们没办法定义出稳定的抽象层。导致哪怕架构设计好,也会在业务迭代的过程中被破坏。
2、架构的过程事实上也是一个解耦的过程,而解耦越充分,相应的也会导致代码量膨胀、代码开发难度变大,所以在项目不是太复杂的阶段,适当的耦合可以提高开发效率。
适用项目
那什么样的情况适用架构设计呢?我认为以下几种情况可以考虑使用模块化重构项目:
1、多人协作开发的情况
2、项目足够复杂,且当前架构下很难进行维护
3、有插件化或者按需加载需求的项目
目标
架构目标
1、安全性和可靠性
软件运行不引起系统事故的能力,在模块化中当某一个模块出现异常,框架可以保证在不使用该模块能力的情况下正常运行。
2、可伸缩性和可扩展性
对于新增模块,或者移除模块项目的改动较少,我们可以通过较少的改动线性的增加需要,大多数情况下只需要简单的配置。
3、可定制性
可临时对架构能力进行扩展,或者能力的重新实现,来达到不同环境下使用的需要。这样的架构设计可以适用于更多的项目中使用。
4、可维护性
保证模块内高内聚,模块间低耦。
模块目标
1、可独立运行
模块可以作为一个独立的APP,运行使用。
2、可插拔
模块的插拔对项目无影响,只是能力的可用和无响应的区别。
3、可移植
模块可以通过简单的适配移植到其他项目中使用,与运行环境完全隔离。
架构设计
架构图
架构图解读
和大多数模块化不同,架构设计中我采用了两层抽象,一方面将项目APP与具体业务解耦,另外一方面将单个模块与框架能力解耦。APP层不再关心业务实现,它只负责业务模块的初始化、生命周期的调用、及适配业务模块所依赖的接口能力。
项目在编译期不再依赖具体的业务模块,而只在打包时候才会将业务模块打包进APK,所以在开发过程中我们无法感知到其他业务模块的存在。
在开发具体的业务模块时,我们无法感知到当前模块在哪个项目中,我们只能依赖框架能力的抽象接口来实现编程,另外我们也无法知道我们的行为要和其他哪一个模块进行交互,我们只能定义一组接口来描述我们的行为。当模块在具体的环境中运行时,由环境来适配模块所依赖的服务,就像使用第三方库的过程中需要进行初始化一样。
项目结构
项目结构解读
我将项目模块分为,业务模块,核心模块,业务组件模块来进行管理,关于他们的定义如下:
1、业务模块,是项目组成的基本单元,可以理解为一个项目由N个业务模块组合起来。另外业务模块也是完全独立的,可以看成一个小型APP,代码是高内聚的。
2、核心模块,是项目中可沉淀,可输出部分。它不依赖于具体的业务,可以在任何android项目中使用,比如我们常常使用的第三方库。
3、业务组件模块,和核心模块不同的是,它不能对外输出,是都当前项目能力的封装,依赖于当前项目的环境,它与核心模块一起属于框架能力层的具体实现。
Gradle构建项目管理
概述
对比上面的架构图和项目结构图,我们可以发现它们并不能完全对应得上。具体的业务模块缺少抽象层,APP仍然依赖具体的业务模块。我们需要针对每一个业务模块,实现一个抽象模块,然后APP依赖抽象模块,抽象模块打包时才依赖具体模块。但是这样做会让事情变得很麻烦,我们需要自动生成API模块,另外我们也需要针对每一个业务模块在编译时生成单独可运行的APK模块。具体的代码如下。
代码实现
/**
* 依赖业务模块时,生成对业务模块的抽象模块,并依赖
* @param moduleName
* @return
*/
def includeWithApi(String moduleName) {
//先正常加载这个模块
include(moduleName)
//生成测试APP
includeWithDebugApk(moduleName)
//找到这个模块的路径
String originDir = project(moduleName).projectDir
String parentDir = project(moduleName).getParent().projectDir
//原模块的名字
String originName=project(moduleName).name
//这个是新的路径
String targetDir = "${parentDir}/build/${originName}-api"
//todo 替换成自己的公共模块,或者预先放api.gradle的模块
//这个是公共模块的位置,我预先放了一个 新建的api.gradle 文件进去
String apiGradle = rootProject.projectDir
// 每次编译删除之前的文件
deleteDir(targetDir)
//复制.api文件到新的路径
copy() {
from originDir
into targetDir
exclude '**/build/'
exclude '**/res/'
exclude '**/main/'
exclude '**/test/'
exclude '**/androidTest/'
include '**/*.java'
}
//直接复制公共模块的AndroidManifest文件到新的路径,作为该模块的文件
copy() {
from "${apiGradle}/ApiAndroidManifest.xml"
into "${targetDir}/src/api/"
rename("ApiAndroidManifest.xml", "AndroidManifest.xml")
}
//复制 gradle文件到新的路径,作为该模块的gradle
copy() {
from "${apiGradle}/api.gradle"
into "${targetDir}/"
}
//删除空文件夹
deleteEmptyDir(new File(targetDir))
//修改包名
fileReader("${targetDir}/src/api/AndroidManifest.xml", "%REPLACE","com.vivo.${originName}.api");
//重命名一下gradle
def build = new File(targetDir + "/api.gradle")
def renameBuild = new File(targetDir + "/build.gradle")
if (build.exists()) {
build.renameTo(renameBuild)
}
fileReader("${renameBuild.getAbsolutePath()}","%REPLACE", "${project(moduleName).getParent()}:$originName")
// 重命名.api文件,生成正常的.java文件
renameApiFiles(targetDir, '.api', '.java')
//正常加载新的模块
include "${project(moduleName).getParent()}"+":build:"+"$originName"+"-api"
}
private void deleteEmptyDir(File dir) {
if (dir.isDirectory()) {
File[] fs = dir.listFiles();
if (fs != null && fs.length > 0) {
for (int i = 0; i < fs.length; i++) {
File tmpFile = fs[i];
if (tmpFile.isDirectory()) {
deleteEmptyDir(tmpFile);
}
if (tmpFile.isDirectory() && tmpFile.listFiles().length <= 0) {
tmpFile.delete();
}
}
}
if (dir.isDirectory() && dir.listFiles().length == 0) {
dir.delete();
}
}
}
private void deleteDir(String targetDir) {
FileTree targetFiles = fileTree(targetDir)
targetFiles.exclude "*.iml"
targetFiles.each { File file ->
file.delete()
}
}
private def renameApiFiles(root_dir, String suffix, String replace) {
FileTree files = fileTree(root_dir).include("**/*$suffix")
files.each {
File file ->
file.renameTo(new File(file.absolutePath.replace(suffix, replace)))
}
}
//替换AndroidManifest里面的字段
def fileReader(path, name,sdkName) {
def readerString = "";
def hasReplace = false
file(path).withReader('UTF-8') { reader ->
reader.eachLine {
if (it.find(name)) {
it = it.replace(name, sdkName)
hasReplace = true
}
readerString <<= it
readerString << '\n'
}
if (hasReplace) {
file(path).withWriter('UTF-8') {
within ->
within.append(readerString)
}
}
return readerString
}
}
/**
* 依赖业务模块时,针对每个业务模块生成一个Debug模块,该模块可以独立运行,是一个完整的APK
* @param moduleName
*/
def includeWithDebugApk(String moduleName){
//找到这个模块的路径
String originDir = project(moduleName).projectDir
String parentDir = project(moduleName).getParent().projectDir
//原模块的名字
String originName=project(moduleName).name
//这个是新的路径
String targetDir = "${parentDir}/App/${originName}-app"
// 每次编译删除之前的文件
deleteDir(targetDir)
if(!new File("${originDir}/src/apk/build.gradle").exists()){
new File(targetDir).deleteDir()
return null
}
copy() {
from originDir
into targetDir
exclude '**/build/'
exclude '**/main/'
exclude '**/api/'
exclude '**/test/'
exclude '**/androidTest/'
include '**/*.java'
include '**/*.xml'
}
copy() {
from "${originDir}/src/apk/AndroidManifest.xml"
into "${targetDir}/src/apk/"
}
//复制 gradle文件到新的路径,作为该模块的gradle
copy() {
from "${originDir}/src/apk/build.gradle"
into "${targetDir}/"
}
//删除空文件夹
deleteEmptyDir(new File(targetDir))
//重命名一下APk TO Main
def apkFile = new File(targetDir + "/src/apk")
def renameApkFile = new File(targetDir + "/src/main")
if (apkFile.exists()) {
apkFile.renameTo(renameApkFile)
}
//正常加载新的模块
include "${project(moduleName).getParent()}"+":App:"+"$originName"+"-app"
}
include ':app', ':base', ':component:business:account', ':component:business:security',
':component:ui:card', ':component:business:basics',
':component:ui:map', ':component:ui:animation',
':core:other', ':component:business:buryingpoint',
':core:utils', ':core:ui', ':core:thread',
':core:net', ':core:db'
includeWithApi ":business:carmode"
includeWithApi ":business:hotel"
includeWithApi ":business:transit"
includeWithApi ":business:trip"
includeWithApi ":business:wea