Android 循环依赖解决方案 - Module API化

1,270 阅读2分钟

前情提要:很多Android开发者工作一段时间后或多或少都会遇到Module之间循环依赖的情况,这种时候就要把架构师拉出来鞭尸了,因为这肯定是前期代码拆分时没有规划好Module职责,我个人一般会拆分成这种依赖关系:通用基础库(哪个项目都能用)通用业务库复杂业务库,右边的依赖左边的。不过这里我们不多说,本文主要是提供一个解决已经形成了循环依赖后,如何去解决的方案。


一、原理:

Untitled.png

二、操作步骤

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