• Android 组件化 组件上下依赖关系实现


    首先需要编写gradle插件

    1、创建plugin工程
    2、自定义plugin实现Plugin接口
    3、在apply(Project)主入口方法内,完成插件的加载

    组件化插件实现及加载流程

    1、定义层级关系,从上往下依次为app/component/library/base,
    2、定位单个组件的类型定义等级,通过等级来控制是否可用做依赖
    例:将工程区分为api和impl模块,组件之间通信,只能依赖api模块
    3、自定义插件,如上
    4、主工程壳里面,实现组件的加载,

    具体实现代码分为:

    非插件部分

    1、统一配置config.gradle

    ext {
        /**
         * 普通变量,统一的路径表
         *
         * libPath:jar包和aar包放置的目录,方便填写引用依赖库的文件
         */
        paths = [
                libPath: "${rootProject.projectDir}/base/common/libs"
        ]
        /**
         * 插件变量,允许被依赖的模块目录,若需消除此约束,则删掉变量。
         *
         * 1、表示m_orderDirectory中目录内的模块,可以被其它模块所依赖,违反此约束将抛出异常;
         *
         * 2、目录的下标越大,表示层级越低,层级高的模块可依赖层级低的,不可反向依赖,否则会抛出异常。
         *
         * 例如:
         * component目录中带_aip后缀的模块可依赖library目录中带_aip后缀以及base的,但反过来不行
         */
        m_top_category = ['app', 'component', 'library', 'base']
    
        /**
         * 公共库列表,例如:base
         */
        m_common_directory = ['base']
    
        /**
         * 组件依赖有效后缀,例如:"_api"
         */
        m_api_suffix = ['_api']
    
        /**
         * 插件调试开关
         */
        m_debug_mode = false
    
        /**
         * 插件变量,依赖库信息。
         */
        m_dependency = [
               
        ]
    
        m_dependency_ex = [
              
        ]
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    插件部分:

    1、自定义plugin

    import org.apache.commons.io.FileUtils
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    class ModularizationPlugin implements Plugin<Project> {
        public static final String PLUGIN_NAME = 'modularization'
    
        @Override
        void apply(Project project) {
            println "project(${project.name}) apply ${PLUGIN_NAME} plugin"
            applyPluginConfiguration(project)
            def isApp = ProjectModuleManager.manageModule(project)
            performBuildTypeCache(project, isApp)
        }
    
        private static void applyPluginConfiguration(Project project) {
            String configPath = "${project.rootProject.projectDir}/config.gradle"
            if (project.file(configPath).exists()) {
                project.apply from: configPath
            }
        }
    
        private static void performBuildTypeCache(Project project, boolean isApp) {
            if (!RegisterCache.isSameAsLastBuildType(project, isApp)) {
                RegisterCache.cacheBuildType(project, isApp)
                //切换app/lib编译时,将transform目录清除
                def cachedJniFile = project.file("build/intermediates/transforms/")
                if (cachedJniFile && cachedJniFile.exists() && cachedJniFile.isDirectory()) {
                    FileUtils.deleteDirectory(cachedJniFile)
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2、逻辑实现部分

    import org.apache.http.util.TextUtils
    import org.gradle.api.Project
    
    import java.util.regex.Pattern
    
    class ProjectModuleManager {
        //插件名称
        static final String PLUGIN_NAME = ModularizationPlugin.PLUGIN_NAME
        //组件单独以app方式运行时使用的测试代码所在目录(manifest/java/assets/res等),这个目录下的文件不会打包进主app
        static final String DEBUG_DIR = "src/main/debug/"
        //依赖库
        static final String CONFIG_DEPENDENCY = "m_dependency"
        //顶级目录
        static final String TOP_CATEGORY = "m_top_category"
        //公共module的根目录
        static final String BASE_DIRECTORY = "m_common_directory"
        //支持的后缀
        static final String API_SUFFIX = "m_api_suffix"
        //是否为插件debug模式
        static final String DEBUG_MODE = "m_debug_mode"
        //主app,一直以application方式编译
        static final String MAIN_APP = "m_main_app"
        //以application方式编译的模块
        static final String MODULE_APP = "m_module.app"
        // 是否集成测试
        static final String INTEGRATION_TEST = "m_integration.test"
        //需要集成打包相关的task
        static final String TASK_TYPES = ".*((((ASSEMBLE)|(BUILD)|(INSTALL)|((BUILD)?TINKER)|(RESGUARD)).*)|(ASR)|(ASD))"
        static final MODULE_TYPE_API = 1
        static final MODULE_TYPE_BASE = 2
    
        static String sAssembleModule
        static boolean sAssembleTask
        static boolean sMainApp
        static boolean sModuleApp
        static Properties sLocalProperties
        static Map<String, MyProjectInfo> sProjectCategoryMap
        static boolean sDebug
        static boolean sIntegrationTest
    
        static boolean manageModule(Project project) {
            sAssembleTask = false
            sAssembleModule = null
            sMainApp = false
            sModuleApp = false
            sDebug = project.ext.has(DEBUG_MODE) && project.ext.m_debug_mode
            checkDependencies(project)
            checkTaskAssemble(project)
            initProjectCategoryMap(project)
            loadLocalProperties(project)
            sMainApp = isMainApp(project)
            sIntegrationTest = isIntegrationTest()
            sModuleApp = isModuleApp(project)
            boolean runAsApp = false
            if (sMainApp) {
                runAsApp = true
            } else if (sModuleApp && (!sAssembleTask || project.name == sAssembleModule)) {
                runAsApp = true
            }
            project.ext.runAsApp = runAsApp
    
            println "${PLUGIN_NAME}: project=${project.name}, integrationTest=${sIntegrationTest}, runAsApp=${runAsApp}, sAssembleTask=${sAssembleTask}, mainApp=${sMainApp}, moduleApp=${sModuleApp}, moduleAppName=${sAssembleModule}"
            if (sIntegrationTest) {
                if (sMainApp) {
                    project.apply plugin: 'com.android.application'
                } else {
                    project.apply plugin: 'com.android.library'
                }
                addProjectSourceSet(project)
                addResourcePrefix(project)
            } else if (runAsApp) {
                project.apply plugin: 'com.android.application'
                if (!sMainApp) {
                    addProjectSourceSet(project)
                    addResourcePrefix(project)
                }
            } else {
                project.apply plugin: 'com.android.library'
            }
            addComponentDependencyMethod(project)
            return runAsApp
        }
    
        private static void checkDependencies(Project project) {
            if (project.ext.has(CONFIG_DEPENDENCY)) {
                def depList = []
                project.ext.m_dependency.each { entry ->
                    if (TextUtils.isBlank(entry.key) || TextUtils.isBlank(entry.value)) {
                        throw new IllegalArgumentException("Illegal dependency format : ${entry}")
                    }
                    String key = entry.value
                    if (!entry.value.contains("/") && !entry.value.contains("\\")) {
                        //如果是远程依赖,获取groupId和artifactId
                        int index = entry.value.lastIndexOf(":")
                        if (index < 0) {
                            throw new IllegalArgumentException("Illegal dependency value : ${entry.value}")
                        }
                        key = entry.value.substring(0, index)
                    }
                    if (TextUtils.isBlank(key)) {
                        throw new IllegalArgumentException("Illegal dependency value : ${entry.value}")
                    }
                    //判断是否有重复引用依赖库的情况
                    if (depList.contains(key)) {
                        throw new IllegalArgumentException("Duplicated dependency value ${key} for ${entry.value}")
                    }
                    depList.add(key)
                }
            }
        }
    
        static void initProjectCategoryMap(Project project) {
            if (project.ext.has(TOP_CATEGORY)) {
                Set<Project> allProjects = project.rootProject.getAllprojects()
                sProjectCategoryMap = new LinkedHashMap<>()
                for (Project p : allProjects) {
                    if (TextUtils.isEmpty(p.path)) {
                        continue
                    }
                    String[] splitPaths = p.path.split(":")
                    if (splitPaths == null || splitPaths.length <= 1) {
                        continue
                    }
                    String categoryName = splitPaths[1]
                    if (sDebug) {
                        println("project : ${p.path}, categoryName : ${categoryName}")
                    }
                    if (project.ext.m_top_category.indexOf(categoryName) >= 0) {
                        MyProjectInfo projectInfo = new MyProjectInfo()
                        projectInfo.categoryName = categoryName
                        projectInfo.leafModule = p.subprojects == null || p.subprojects.isEmpty()
                        if (isApiModule(project, p.name)) {
                            projectInfo.moduleType = MODULE_TYPE_API
                        } else if (isBaseModule(project, categoryName)) {
                            projectInfo.moduleType = MODULE_TYPE_BASE
                        }
                        sProjectCategoryMap.put(p.path, projectInfo)
                    }
                }
            }
        }
        /**
         * 加载本地local.properties的键值对
         */
        static void loadLocalProperties(Project project) {
            sLocalProperties = new Properties()
            try {
                def localFile = project.rootProject.file('local.properties')
                if (localFile != null && localFile.exists()) {
                    sLocalProperties.load(localFile.newDataInputStream())
                }
            } catch (Exception ignored) {
                println("${PLUGIN_NAME}: local.properties not found")
            }
        }
    
        /**
         * 判断当前的task是否为集成打包相关的task
         */
        static void checkTaskAssemble(Project project) {
            def taskNames = project.gradle.startParameter.taskNames
            def allModuleBuildApkPattern = Pattern.compile(TASK_TYPES)
            for (String task : taskNames) {
                if (allModuleBuildApkPattern.matcher(task.toUpperCase()).matches()) {
                    sAssembleTask = true
                    if (task.contains(":")) {
                        def arr = task.split(":")
                        sAssembleModule = arr[arr.length - 2].trim()
                    }
                    return
                }
            }
        }
    
        /**
         * 判断当前Project是否为主app,即一直以application方式编译
         */
        static boolean isMainApp(Project project) {
            return project.ext.has(MAIN_APP)
        }
    
        /**
         * 判断当前Project是否以application方式编译的模块
         */
        static boolean isModuleApp(Project project) {
            return sLocalProperties != null && project.name == sLocalProperties.getProperty(MODULE_APP)
        }
    
        static boolean isIntegrationTest() {
            return sLocalProperties != null && sLocalProperties.getProperty(INTEGRATION_TEST) == "true"
        }
    
        /**
         * 以application方式编译的模块,代码以及资源需添加debug目录
         */
        static void addProjectSourceSet(Project project) {
            project.android.sourceSets.main {
                //application模式下,如果存在src/main/debug/AndroidManifest.xml,则自动使用其作为manifest文件
                def debugManifest = "${DEBUG_DIR}AndroidManifest.xml"
                if (project.file(debugManifest).exists()) {
                    manifest.srcFile debugManifest
                }
                //application模式下,如果存在src/main/debug/assets,则自动将其添加到assets源码目录
                if (project.file("${DEBUG_DIR}assets").exists()) {
                    assets.srcDirs += "${DEBUG_DIR}assets"
                }
                //application模式下,如果存在src/main/debug/java,则自动将其添加到java源码目录
                if (project.file("${DEBUG_DIR}java").exists()) {
                    java.srcDirs += "${DEBUG_DIR}java"
                }
                //application模式下,如果存在src/main/debug/res,则自动将其添加到资源目录
                if (project.file("${DEBUG_DIR}res").exists()) {
                    res.srcDirs += "${DEBUG_DIR}res"
                }
                //application模式下,如果存在src/main/debug/aidl,则自动将其添加到aidl目
                if (project.file("${DEBUG_DIR}aidl").exists()) {
                    aidl.srcDirs += "${DEBUG_DIR}aidl"
                }
                if (project.file("${DEBUG_DIR}jni").exists()) {
                    jni.srcDirs += "${DEBUG_DIR}jni"
                }
                if (project.file("${DEBUG_DIR}jniLibs").exists()) {
                    jniLibs.srcDirs += "${DEBUG_DIR}jniLibs"
                }
                if (project.file("${DEBUG_DIR}renderscript").exists()) {
                    renderscript.srcDirs += "${DEBUG_DIR}renderscript"
                }
                if (project.file("${DEBUG_DIR}resources").exists()) {
                    resources.srcDirs += "${DEBUG_DIR}resources"
                }
            }
        }
    
        /**
         * 防止模块间的资源同名,导致资源冲突,所以强制资源命名时需以模块名作为资源前缀,从而保证资源命名的唯一性
         */
        static void addResourcePrefix(Project project) {
            if (!sMainApp) {
                project.android {
                    resourcePrefix "${project.name}_"
                }
            }
        }
    
        static boolean isApiModule(Project project, String moduleName) {
            if (project.ext.has(API_SUFFIX)) {
                for (String supportedSuffix : project.ext.m_api_suffix) {
                    if (moduleName.endsWith(supportedSuffix)) {
                        return true
                    }
                }
            }
            return false
        }
    
        static boolean isBaseModule(Project project, String categoryName) {
            if (project.ext.has(BASE_DIRECTORY)) {
                for (String supportedRootName : project.ext.m_common_directory) {
                    if (supportedRootName == categoryName) {
                        return true
                    }
                }
            }
            return false
        }
    
        static void addComponentDependencyMethod(Project project) {
            /**
             * implementModule 模块直接依赖,只能依赖于api模块
             * implementLib、compileOnlyLib、annotationProcessorLib 库依赖
             *
             * 上述依赖均包含以下两种情形:
             * 1、testIncluded为true,说明该lib是在带测试界面时才依赖,即sIntegrationTest为true的时候才需要依赖
             * 2、testIncluded为false,正常依赖
             */
            def setCommonDependency = { dependencyPattern, moduleName, testIncluded = false ->
                if (sIntegrationTest && testIncluded || !testIncluded) {
                    if (sDebug) {
                        println("addComponentDependencyMethod: dependencyPattern = " + dependencyPattern + ", moduleName = " + moduleName + ", sIntegrationTest = " + sIntegrationTest + ", testIncluded = " + testIncluded)
                    }
                    for (Project p : project.getRootProject().getSubprojects()) {
                        if (moduleName == p.getName()) {
                            MyProjectInfo projectInfo = sProjectCategoryMap.get(p.getPath())
                            if (projectInfo == null) {
                                throw new IllegalArgumentException(p.getName() + " not included, check it.")
                            }
                            String rootName = projectInfo.categoryName
                            if (sDebug) {
                                println("addComponentDependencyMethod: moduleName = " + moduleName + ", rootName = " + rootName + ", project.name = " + project.name)
                            }
                            if (!TextUtils.isEmpty(rootName)) {
                                // step1.依赖的module有效性校验;被依赖的module不包含已支持的后缀,如api;且不属于已支持的公共库如base,则抛出异常,使得编译不通过
                                boolean isSupportedSuffix = isApiModule(project, moduleName)
                                boolean isSupportedCommonDirectory = isBaseModule(project, rootName)
                                if (!isSupportedSuffix && !isSupportedCommonDirectory) {
                                    throw new IllegalArgumentException(moduleName + " must in directory of base or is api module.")
                                }
                                MyProjectInfo currentProjectInfo = sProjectCategoryMap.get(project.getPath())
                                if (currentProjectInfo == null) {
                                    throw new IllegalArgumentException(moduleName + " not included, check it.")
                                }
                                String currentRootName = currentProjectInfo.categoryName
                                int moduleIndex = project.ext.m_top_category.indexOf(rootName)
                                int currentIndex = project.ext.m_top_category.indexOf(currentRootName)
                                if (sDebug) {
                                    println("addComponentDependencyMethod: moduleIndex = " + moduleIndex + ", currentIndex = " + currentIndex + ", currentRootName = " + currentRootName + ", project.getPath() = " + project.getPath())
                                }
                                // step2.范围校验,module不在指定范围,抛出异常,使得编译不通过
                                if (moduleIndex == -1 || currentIndex == -1) {
                                    throw new IllegalArgumentException("implementModule of \'" + moduleName + "\' should be the child directory of m_top_category.")
                                }
                                // step3.层级依赖关系校验,m_top_category中低下标可依赖高下标,反之编译不通过
                                if (moduleIndex < currentIndex) {
                                    throw new IllegalArgumentException("Can't implement upper-level api module : \"${moduleName}\" in \"${project.getName()}\"")
                                }
                            }
                            project.dependencies.add(dependencyPattern, p)
                            return
                        }
                    }
                }
            }
    
            project.ext.implementModule = { moduleName, testIncluded = false ->
                setCommonDependency("implementation", moduleName, testIncluded)
            }
    
            project.ext.compileOnlyModule = { moduleName, testIncluded = false ->
                setCommonDependency("compileOnly", moduleName, testIncluded)
            }
    
            project.ext.implementLib = { libName, testIncluded = false, excludeParams = null ->
                if (sIntegrationTest && testIncluded || !testIncluded) {
                    if (sDebug) {
                        println("implementLib: libName = " + libName + ", sIntegrationTest = " + sIntegrationTest + ", testIncluded " + testIncluded)
                    }
                    if (!project.ext.has(CONFIG_DEPENDENCY) || !project.ext.m_dependency.containsKey(libName)) {
                        throw new IllegalArgumentException("Cant find implementation libPath for \"${libName}\"")
                    }
                    String libPath = project.ext.m_dependency[libName]
                    if (libPath.contains("/") || libPath.contains("\\")) {
                        project.dependencies.add("implementation", project.files(libPath))
                    } else {
                        if (excludeParams == null) {
                            project.dependencies.add("implementation", libPath)
                        } else {
                            project.dependencies.add("implementation", libPath, {
                                exclude excludeParams
                            })
                        }
                    }
                }
            }
    
            project.ext.compileOnlyLib = { libName, testIncluded = false ->
                if (sIntegrationTest && testIncluded || !testIncluded) {
                    if (!project.ext.has(CONFIG_DEPENDENCY) || !project.ext.m_dependency.containsKey(libName)) {
                        throw new IllegalArgumentException("Cant find compileOnly libPath for \"${libName}\"")
                    }
                    String libPath = project.ext.m_dependency[libName]
                    if (libPath.contains("/") || libPath.contains("\\")) {
                        project.dependencies.add("compileOnly", project.files(libPath))
                    } else {
                        project.dependencies.add("compileOnly", libPath)
                    }
                }
            }
    
            project.ext.annotationProcessorLib = { libName, testIncluded = false ->
                if (sIntegrationTest && testIncluded || !testIncluded) {
                    if (!project.ext.has(CONFIG_DEPENDENCY) || !project.ext.m_dependency.containsKey(libName)) {
                        throw new IllegalArgumentException("Cant find annotationProcessor libPath for \"${libName}\"")
                    }
                    String libPath = project.ext.m_dependency[libName]
                    if (libPath.contains("/") || libPath.contains("\\")) {
                        project.dependencies.add("annotationProcessor", project.files(libPath))
                    } else {
                        project.dependencies.add("annotationProcessor", libPath)
                    }
                }
            }
    
            //判断task是否为给本module打apk包
            def curModuleIsBuildingApk = sAssembleTask && (sMainApp || sAssembleModule == project.name)
            /**
             * runtimeModule 模块间接依赖,没有直接的代码引用,打包时编到apk中
             *
             * 该依赖包含以下三种情形:
             * 1、说明不是在处于打包apk的task,这种情况不引用直接返回,从而达到代码隔离的效果
             * 2、testIncluded为true,说明该lib是在带测试界面时才依赖,即sIntegrationTest为true的时候才需要依赖
             * 3、testIncluded为false,正常依赖
             */
            project.ext.runtimeModule = { moduleName, testIncluded = false ->
                if (!curModuleIsBuildingApk) {
                    return
                }
                if (sIntegrationTest && testIncluded || !testIncluded) {
                    for (Project p : project.getRootProject().getSubprojects()) {
                        if (moduleName == p.getName()) {
                            project.dependencies.implementation p
                            return
                        }
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408

    自定义工程实体类

    class MyProjectInfo {
        private String categoryName
        private boolean leafModule
        private int moduleType
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    软件项目管理【UML-组件图】
    浅析电力监控在新型数据中心的设计和应用-Susie 周
    Docker 安装RabbitMq
    游戏复用列表实现思路
    leetcode-每日一题-1710-卡车上的最大单元数(简单,哈希,暴力)
    关于地址存放的例题
    CSS相关
    stm32、IO口、中断、串口、定时器讲解
    【debug】安装diffusion的bug解决合集
    Executor接口实现线程池管理
  • 原文地址:https://blog.csdn.net/aaa1050070637/article/details/127822544