gradle plugin实现android组件化

525 阅读1分钟

首先声明一点标题所说的组件化, 并非类似Small的插件化. 如果没有搞错的话, 可以继续往下看.
android中实现组件化的方案一般是在gradle.properties里设置一个变量, 区分合并运行还是拆分运行.

//'mine'模块中的build.gradle
if (IS_DEV_MODE.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

上面是最主要的设置, 细分下来gradle中需要修改的地方不少, 例如applicationId, AndroidManifest的区分, 主app的依赖问题等等. 但是在修改gradle.properties属性之后需要一次同步. 一次同步的耗时对于一些低端机子会比较耗时, 没错就是我之前用的陈年老笔记本.
所以, 我写了个简单的plugin, 避免每次跑app和跑module时设置属性在同步的问题.

这里并不介绍怎么编写自定义插件, 只是提出这么个东西希望跟大家一起讨论目录.
目录很简单, 并且每个文件都是几十行, 很简单的plugin.
首先说明一下每个project分三种类型, 公共组建, 模块. 公共组建为library, module和app为application, 依赖模块时将动态改为library.

public class RootPlugin implements Plugin<Project>{

    private Project rootProject //记录跟目录Project, 用于获取扩展

    @Override
    void apply(Project rootProject) {
        //创建一个task, 打包时用
        rootProject.task('packing', group: 'pack', dependsOn: ':app:assembleRelease')
        this.rootProject = rootProject 
        //创建扩展属性
        //该扩展总共定义了三个变量, 
        // pack 打正式包
        // debugApp debug模式下跑app
        // moduleProjectList 各个模块的Project集合
        rootProject.extensions.create('root', RootExtension.class)

        //获取第一个命令参数
        def param = rootProject.gradle.startParameter.taskNames[0];
        println("param -> $param")
        //根据参数判断是整合还是, 分开, 这里只是用简单判断了一下
        if (param != null){
            if (param == 'rootExt'){
                //如果是 'packing', 则为 -> 正式包
                rootExt.pack = true
            }else {
                //AS中 跑app那么他的参数是 :app:assembleDebug
                if (param.startsWith(':app:assemble')){
                    if (param.contains('Debug')){
                        rootExt.deBugApp = true
                    }else if (param.contains('Release')){
                        rootExt.pack = true
                    }
                }
            }
        }

        println("start changeDynamic -------------------> ")

        def moduleList = rootExt.moduleProjectList

        rootProject.subprojects { subProject ->

            def subProjectName = subProject.name
            //组件化一般分为, app, 模块, 以及公共组件
            //这里简单的用各个模块的名称做了区分
            switch (getType(subProjectName)) {

                case Type.APP: //app
                    subProject.apply plugin: AppPlugin
                    break
                case Type.LIB: //公共组件
                    subProject.apply plugin: LibPlugin
                    break
                case Type.MODULE: //
                    // 模块的project需要加入到 扩展属性中
                    moduleList.add(subProject)
                    subProject.apply plugin: ModulePlugin
                    break
            }
        }
    }
    public RootExtension getRootExt(){
        return rootProject.rootExt
    }

    private static Type getType(String projectName){
        if ('app' == projectName){ //'app'
            return Type.APP
        }else if (projectName != null && projectName.startsWith('base')){ //公共组件一般以 'base' 开头
            return Type.LIB
        }else {
            return Type.MODULE
        }
    }

    static enum Type{
        APP,LIB,MODULE
    }
}

先看下简单的LibPlugin

class LibPlugin implements Plugin<Project> {

    @Override
    void apply(Project subProject) {
        //这里只考虑只有一个公共组建
        //获取root的拓展属性
        def rootExt = subProject.rootProject.rootExt
        subProject.afterEvaluate {
            //添加 IS_DEV BuildConfig
            //根据是否是正式包做区分
            it.android.defaultConfig.buildConfigField('boolean', 'IS_DEV', rootExt.pack ? 'false' : 'true')
            //区分sourceSets
            if (rootExt.pack || rootExt.deBugApp) {
                //sourceSets
                subProject.android.sourceSets.main.manifest.srcFile 'src/release/AndroidManifest.xml'
            }else{
                //sourceSets
                subProject.android.sourceSets.main.manifest.srcFile 'src/debug/AndroidManifest.xml'
                subProject.android.sourceSets.main.java{
                    exclude 'src/debug/**'
                }
            }
        }
    }
}

接着看一下AppPlugin

class AppPlugin implements Plugin<Project> {

    @Override
    void apply(Project subProject) {
        def rootExt = subProject.rootProject.rootExt
        subProject.afterEvaluate {
            //动态设置 versionCode
            subProject.android.defaultConfig.versionCode = Integer.parseInt(new SimpleDateFormat("yyMMdd", Locale.CHINA).format(new Date()))

            def dependencies = subProject.configurations.compile.dependencies
            //添加其他模块依赖
            if (rootExt.pack || rootExt.deBugApp) {
                // 移除 baseModule依赖
                // app和模块的build.gradle中默认都依赖公共组件, 所以当整合的时候
                // 移除掉公共组建, 貌似不移除也无伤大雅
                dependencies.remove(dependencies.find{
                    it.name == 'base_module' //这里简单的用名称移除
                })

                // 这里就是将扩展属性中模块project的集合, 依次添加到'app'的依赖
                // 我们默认所有模块都是apply plugin: 'com.android.application'
                // 如果不改为library那么依赖的时候就会报错
                // 如何动态改为library马上介绍
                it.rootProject.rootExt.moduleProjectList.each {
                    subProject.dependencies.add('compile', it)
                }
            }
        }
    }
}
class ModulePlugin implements Plugin<Project> {

    private File mTempBuildFile

    @Override
    void apply(Project subProject) {

        def rootExt = subProject.rootProject.rootExt

        //...省略 设置sourceSets 和LibPlugin一样

        // 修改build.gradle文件
        // 这里就是最关键的代码, 动态的修改模块是application还是library
        // 这部分的代码是 Small插件化的gradle代码照搬过来的,
        // 有兴趣的可以去和研究一下 Smallgradle插件的源码
        if (rootExt.pack || rootExt.deBugApp) {
            mTempBuildFile = new File(subProject.buildFile.parentFile, "${subProject.name}~")

            subProject.beforeEvaluate {
                def text = it.buildFile.text.replaceAll("""com.android.application""", """com.android.library""")
                it.buildFile.renameTo(mTempBuildFile)
                it.buildFile.write(text)
            }

            subProject.gradle.buildFinished {
                if (mTempBuildFile != null && mTempBuildFile.exists()) {
                    subProject.buildFile.delete()
                    mTempBuildFile.renameTo(subProject.buildFile)
                }
            }
        }
    }
}

最后, 只要在根目录的build.gradle中使用自定义的插件就可以了.