从零开始Android组件化改造(二) - Gradle与Manifest管理

我的Github:https://github.com/BzCoder
本文基于MVPArms进行分析:https://github.com/JessYanCoding/MVPArms
欢迎各位留言讨论

Gradle的管理在组件化改造中是一个非常有学问的环节。在我看来Gradle在其中的主要几个职责:

  • 引入包的版本管理
  • 组件化编译与总体编译的切换
  • 各模块间的层级关系维护
  • gradle.properties 配置中转站

接下来我们就一点一点的讲。

1.引入包的版本管理

这其实不是组件化开发的专利。正如其他的项目一样,统一的版本号我们都管理在Config.gradle中。类似下面的文件。在模块中引入模块时,统一通过类似api rootProject.ext.dependencies["mmkv"]的引入方式来保证版本的统一。这个很好理解,因为都是常规操作。

ext {

    android = [
            applicationId    : "${PACKAGE_NAME}",
            compileSdkVersion: 28,
            buildToolsVersion: "28.0.3",
            minSdkVersion    : 18,
            targetSdkVersion : 28,
            versionCode      : VERSION_CODE as int,
            versionName      : "${VERSION_NAME}"
    ]

    version = [
            androidSupportSdkVersion: "28.0.0",
            retrofitSdkVersion      : "2.4.0",
            dagger2SdkVersion       : "2.19",
    ]

    dependencies = [
            //support
            "appcompat-v7"             : "com.android.support:appcompat-v7:${version["androidSupportSdkVersion"]}",
            "design"                   : "com.android.support:design:${version["androidSupportSdkVersion"]}",
            "support-v4"               : "com.android.support:support-v4:${version["androidSupportSdkVersion"]}",
            "cardview-v7"              : "com.android.support:cardview-v7:${version["androidSupportSdkVersion"]}",
            "annotations"              : "com.android.support:support-annotations:${version["androidSupportSdkVersion"]}",
            "recyclerview-v7"          : "com.android.support:recyclerview-v7:${version["androidSupportSdkVersion"]}",
            "constraint-layout"        : 'com.android.support.constraint:constraint-layout:1.1.3',
]
  defaultConfig {
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]
        testInstrumentationRunner rootProject.ext.dependencies["androidJUnitRunner"]
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
                includeCompileClasspath true
            }
        }

        multiDexEnabled true
    }

2.组件化编译与总体编译的切换

2.1 App与Lib

我们知道,lib模块需要引入apply plugin: 'com.android.library',应用模块引入apply plugin: 'com.android.application',而在组件化开发中,这两种状态是要不断地切换的,所以我们可以在gradle.properties设定参数isBuildModule来控制。于是gradle的头部就变成了以下

if (isBuildModule.toBoolean()) {
    apply plugin: 'com.android.application'

} else {
    apply plugin: 'com.android.library'
}

当然在实际开发中我们设计了多层,相应的我们也给每层建立了基础gradle,参数也随之变成了isBuildModuleOne,isBuildModuleTwo,isBuildModuleThree...每一层的业务只要依赖该层基础gradle即可。

2.2 两套Manifest

组件化编译和整体编译,他们的清单文件也是不同的。我们通过在Gradle中android节点中加入以下代码来控制工程使用两套Manifest,但是这种方案美中不足的是,你的每个Activity的注册都必须要写两次,否则切换编译模式后,会出现错误,有待改进。

 sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
            if (isBuildModule.toBoolean()) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            }
        }
    }

2.3 不同编译方式时导入不同的包

说到包的导入,我们先必须明确两个关键字的概念。

  • runtimeOnly:只有在运行时才编译。
  • compileOnly:只有编译的时候引入,实际打包时并不会加入到包当中。

根据以上原则
在模块互相导入时,我们使用如下格式:

  if (isBuildModule.toBoolean()) {
 if (moduleXX.toBoolean()) {
     runtimeOnly project(":moduleXX")
}
}

这样写,首先(if语句)可以通过配置文件来确认某个模块是否加入,使用runtimeOnly ,可以在某一个Module组件化编译时,不会影响其他引用它的模块。还有一点,我们在引用的外层包裹了一层是否为组件化编译的状态判断(isBuildModule),这是为了防止模块的重复引入。因为在组件化开发时,模块之间是不需要互相显式引用的,我们最终的模块引用是通过APP引用的。

在通用模块导入时,我们使用如下格式:
butterknife,dagger2等包引入。

   if (isBuildModule.toBoolean()) {
        //view
        annotationProcessor(rootProject.ext.dependencies["butterknife-compiler"]) {
            exclude module: 'support-annotations'
        }
        //tools
        annotationProcessor rootProject.ext.dependencies["dagger2-compiler"]
        annotationProcessor rootProject.ext.dependencies["arouter-compiler"]
        //test
        debugImplementation rootProject.ext.dependencies["canary-debug"]
        releaseImplementation rootProject.ext.dependencies["canary-release"]
        testImplementation rootProject.ext.dependencies["canary-release"]
    } else {
        compileOnly rootProject.ext.dependencies["butterknife-compiler"]
        compileOnly rootProject.ext.dependencies["dagger2-compiler"]
        compileOnly rootProject.ext.dependencies["arouter-compiler"]
        compileOnly rootProject.ext.dependencies["canary-debug"]
        compileOnly rootProject.ext.dependencies["canary-release"]
    }

这样写只有在组件化编译的时候,模块才会真正把基础包引入,整体打包时,只有一份。

3.Manifest与gradle.properties的数据桥梁

因为我们目标想把所有的配置文件都整合到gradle.properties中,但是manifest又不能直接调用gradle.properties中的参数,我们就必须借助gradle作为“中间人”。我们在gradle的defaultconfig节点中加入(以下为推送的写法)

 manifestPlaceholders = [
                GETUI_APP_ID    : "${PUSH_APPID}",
                GETUI_APP_KEY   : "${PUSH_APPKEY}",
                GETUI_APP_SECRET: "${PUSH_APPSECRET}"
]

这样一来,在Manifest中就可以取到gradle.properties 的参数了。

总结

Gradle配置总的来说不是太难,以上方案也存在可以优化的地方,就是Manifest,携程曾经有一篇文件是利用manifest的tools:node ="remove"来进行manifest的合并,但是很可惜,在我自己实践的过程中,遇到了不少困难,最终没有采用携程的方案。好的,那么今天的文章就先暂时写到这里。

推荐阅读更多精彩内容