前情提要:很多Android开发者工作一段时间后或多或少都会遇到Module之间循环依赖的情况,这种时候就要把架构师拉出来鞭尸了,因为这肯定是前期代码拆分时没有规划好Module职责,我个人一般会拆分成这种依赖关系:通用基础库(哪个项目都能用)→通用业务库→复杂业务库,右边的依赖左边的。不过这里我们不多说,本文主要是提供一个解决已经形成了循环依赖后,如何去解决的方案。
一、原理:
二、操作步骤
1. 创建空module api_base
之后的api化模块都会基于这个模块
2. 在项目settings.gradle文件顶部添加如下几个函数
def includeWithApi(String moduleName) {
// 先正常加载源模块
include(moduleName)
// 原模块的名字
String originName = project(moduleName).name
// 源模块路径
String originModuleDir = project(moduleName).projectDir
// 生成的api模块 的名字
def apiModuleName = "${originName}_api"
// 生成的api模块 的路径
String apiModuleDir = "${originModuleDir}_api"
// 生成的api模块 gradle文件后缀
String apiGradleSuffix = "build.gradle"
// 生成的api模块 要依赖的基础模块
String baseCommonProjectDir = project(":api_base").projectDir
// 每次编译时,删除上一次编译生成的api模块,重新生成新的
deleteDir(apiModuleDir)
// 复制 *.api、*.kapi 文件到生成的api模块
copy() {
from originModuleDir
into apiModuleDir
exclude '**/build/'
exclude '**/res/'
include '**/*.api'
include '**/*.kapi'
}
//直接复制公共模块的AndroidManifest文件到新的路径,作为该模块的文件
copy() {
from "${baseCommonProjectDir}/src/main/AndroidManifest.xml"
into "${apiModuleDir}/src/main/"
}
//直接复制公共模块的.gitignore文件到新的路径,作为该模块的文件
copy() {
//注意:在windows下可以创建文件名为.gitignore.,保存之后系统会自动重命名为 .gitignore
from "${baseCommonProjectDir}/.gitignore."
into "${apiModuleDir}/"
}
//复制 gradle文件到新的路径,作为该模块的gradle
println "${baseCommonProjectDir}/${apiGradleSuffix}hhjjkk"
println "${apiModuleDir}/hhjjkk"
copy() {
from "${baseCommonProjectDir}/${apiGradleSuffix}"
into "${apiModuleDir}/"
}
//删除空文件夹
deleteEmptyDir(new File(apiModuleDir))
//为AndroidManifest package="x.x.x"新建包路径,作为里面的包名
String packagePath = "${apiModuleDir}/src/main/java/x/x/x/${originName}/api"
new File(packagePath).mkdirs()
//修改AndroidManifest文件包路径
fileReader("${apiModuleDir}/src/main/AndroidManifest.xml", "x.x.x.${originName}.api")
// 重命名一下gradle
def build = new File(apiModuleDir + "/${apiGradleSuffix}")
println "${build}hhjjkk"
if (build.exists()) {
build.renameTo(new File(apiModuleDir + "/build.gradle"))
}
// 重命名文件 *.api → *.java
renameApiFiles(apiModuleDir, '.api', '.java')
// 重命名文件 *.kapi → *.kt
renameApiFiles(apiModuleDir, '.kapi', '.kt')
// 正常加载新的模块
include ":$apiModuleName"
}
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()
}
}
/**
* rename api files(java, kotlin...)
*/
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, packageName) {
// 用于找到包名所在行的正则表达式
def packageLineReg = "package=".*""
def readerString = ""
def hasReplace = false
file(path).withReader('UTF-8') { reader ->
reader.eachLine {
if (it.find(packageLineReg)) {
// 引号中间的内容
def innerOfQuote = it.substring(it.indexOf('"') + 1, it.lastIndexOf('"'))
it = it.replace(innerOfQuote, packageName)
hasReplace = true
}
readerString <<= it
readerString << '\n'
}
if (hasReplace) {
file(path).withWriter('UTF-8') {
within ->
within.append(readerString)
}
}
return readerString
}
}
3. 处理需要被api化的模块
如果想将Module1中被依赖的部分API化,则需要将其中的代码改造一下。将.java后缀改为.api后缀,将.kt后缀改为.kapi。注意只改需要被api化到独立模块的代码,别把所有文件都改了,跑不起来别怪我
4. 修改需要api化的模块配置
include ':module1' → includeWithApi ':module1'
5. 重新sync gradle文件
会新生成一个module1_api模块,在module1、module2中都依赖上module1_api模块
Done