模块化架构设计

 

前言

架构设计是一个不断演变的过程,当项目较小,或者项目刚刚起步的阶段,我们往往不需要关注架构设计,只有当软件膨胀到一定程度,我们才会针对当前业务,设计出适合当前阶段的架构。所以有的项目就会出现不断膨胀,不断重构的情况。那为什么不一开始就设计一个大而全的架构?我认为有以下几点:

1、架构是对当前业务进行的一层抽象,当业务不稳定,或者在高速迭代的过程中,我们没办法定义出稳定的抽象层。导致哪怕架构设计好,也会在业务迭代的过程中被破坏。

2、架构的过程事实上也是一个解耦的过程,而解耦越充分,相应的也会导致代码量膨胀、代码开发难度变大,所以在项目不是太复杂的阶段,适当的耦合可以提高开发效率。

 

适用项目

那什么样的情况适用架构设计呢?我认为以下几种情况可以考虑使用模块化重构项目:

1、多人协作开发的情况

2、项目足够复杂,且当前架构下很难进行维护

3、有插件化或者按需加载需求的项目

 

目标

 

架构目标

 

1、安全性和可靠性

软件运行不引起系统事故的能力,在模块化中当某一个模块出现异常,框架可以保证在不使用该模块能力的情况下正常运行。

 

2、可伸缩性和可扩展性

对于新增模块,或者移除模块项目的改动较少,我们可以通过较少的改动线性的增加需要,大多数情况下只需要简单的配置。

扫描二维码关注公众号,回复: 11631954 查看本文章

 

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

Modularization.rar

猜你喜欢

转载自blog.csdn.net/long8313002/article/details/108367446