Android组件化:build.gradle配置

欢迎转载,转载时请注明出处和作者
作者:kerwin
原文地址:
http://www.jianshu.com/p/9620a40c203f

之前已经整体的对组件化框架进行了概述:Android组件化开发框架,这篇文章只针对组件化的编译脚本的配置进行详述。

我们先回顾一下组件化的目标,可复用、热插拔、灵活发布。那么要达到这些个目标,显然不只是代码层就能完成的工作。其中灵活发布就是我们这一篇需要重点研究的内容。

如何才能算是可灵活发布呢?

  1. 组件可以独立运行
  2. 组件可以独立发布
  3. 组件有独立的版本

android 的gradle配置中有2种Module,library、application。其中library是发布为aar的子Module,application是可以运行打包为apk的主Module,如果我们希望组件能独立运行,则组件必须是application,但是application发布后是apk文件,apk是不能被其它Module所引用的。

为了解决上述问题,我们需要Module同时具有application以及library的特性。所以,最理想的方式是直接这样添加:


同时设置application和library

可惜这样是不行的,编译时会出现如下错误:


同时设置时的编译错误

也就是说application与library都有android这个结构,是冲突的,不能同时存在,于是我们立马想到改成这样:


使用if语句进行区分

很棒~!因为编译通过了,而且只需要修改isApp的值就能动态修改Module的类型。但是如果每个Module都是这样简单的设置就可以的话,那也就不会有这篇文章了。

想一想,还有什么没考虑到的呢?

  1. ApplicationId在Library内无法识别
  2. Library与Application的AndroidMenifest文件是不一样的
  3. 组件在单独运行时的测试代码以及资源等应该与发布代码进行隔离
  4. 说好的版本管理呢?

通过上面的尝试,我们确定了build在编译时是可以采用动态参数进行动态调整的。下面我们继续完善之前未完成的工作。

1、ApplicationId在Library内无法识别
解决办法:修改module的build.gradle内容

    boolean isApp = false
    if (isApp) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }

    android {
        ...
        defaultConfig {
            if (isApp) {
                multiDexEnabled true
                applicationId "com.bamboo.component"
            }
            ...
        }
       ...
    }

2、Library与Application的AndroidMenifest文件是不一样的
3、组件在单独运行时的测试代码以及资源等应该与发布代码进行隔离
解决办法:添加Module在单独运行时的测试代码以及测试资源存放文件夹。

    boolean isApp = true
    if (isApp) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }

    android {
        ...
        defaultConfig {
            if (isApp) {
                multiDexEnabled true
                applicationId "com.bamboo.component"
            }
            ...
        }

        

        if (isMyApp) {
            flavorDimensions "run"
            productFlavors {
                run {
                    dimension "run"
                    minSdkVersion 21
                }
            }
        }
       ...
    }

配置好上面的代码后,还需要修改Module的目录结构,修改后如下:


image.png

这里是利用了gradle的sourceSets方案,每一个flavor都会匹配一个对应的sourceSet,每个sourceSet都有自己的源码文件夹。文件夹说明:
/src/main : 核心源码默认文件夹
/src/run : 在切换module为可运行模式时的扩展源码文件夹
/src/androidTestRun、/src/testRun :扩展的测试代码文件夹

需要特别注意的地方

  1. main文件夹中的class不能引用run中的class,因为run文件夹中的class在作为library发布的时候是非源码文件夹,不参与编译,如果引用了里面的代码编译时会报错。而run文件夹的类可以随意引用java中的类。main文件夹与run文件夹不能出现重复的类。

  2. main文件夹中不能引用run文件夹中的资源,原因同上,并且不能出现重复的资源。

  3. main/AndroidManifest.xml和run/AndroidManifest.xml中的配置信息在可执行模式时编译器会合并两个文件。所以run/AndroidManifest.xml中配置可运行时需要的额外内容即可。

最后我们在run/java中添加我们的测试入口类TestActivity.java,在run/res中添加测试layout文件activity_test.xml,在run/AndroidManifest.xml中设置TestActivity为Main入口。
将isApp设置为true,我们就能直接运行我们的Module。
将isApp设置为false,就能将Module发布成aar。

进阶优化

到这里其实已经能完成我们的前3个目的,只需要修改一个配置,就能随意更改Module的类型,但是如果Module有很多个的时候,想同时修改几个,就得修改好几个build.gradle文件,而且,每改一次build.gradle文件就需要全部重新build一次,这样有点浪费时间。

那我们能不能修改配置之后不需要重新build就能修改Module的类型呢?

答案是可以。

第一步:在工程的根目录(与setting.gradle文件同级)新建include.properties文件,然后添加如下内容:

applications=module

第二步:在Module的build.gradle文件中添加如下代码:

static findProperty(String propertiesFile, String propertyName) {
    if (propertiesFile != null) {
        java.util.Properties properties = new java.util.Properties()
        InputStream inputStream = new File(propertiesFile).newDataInputStream()
        properties.load(inputStream)
        def propertyValue = properties.getProperty(propertyName)
        return propertyValue == null ? "" : propertyValue
    }
    return "";
}

static verifyProperties(Project project, String propertyName, String property) {
    return findProperty(project.getProjectDir().absolutePath + '/include.properties', propertyName).split(",").contains(property)
}

第三步:修改isApp的取值:

boolean isApp = verifyProperties(rootProject, 'applications', name)

原理:

第一步解析:include.properties用来配置哪些Module作为Application来使用,其中后面的module是Module的名称,如果想配置多个Module为Application类型,用','隔开。

第二步解析:gradle的脚本语言是Groovy,Groovy语言是java代码的扩展语言,所以我们可以在build.gradle中使用几乎所有的java语法来灵活设置我们的脚本。这部分代码主要是读取include.properties文件中的属性值。

第三步解析:读取include.properties 中的applications属性值,并验证当前Module是否已配置在其中,如果在就设置isApp为true,否则为false。

这样修改以后,只需要修改include.properties文件中的内容,然后点击下图中的刷新按钮就能切换Module的类型。


刷新配置信息

4、说好的版本管理呢?
解决这个问题之前,先思考一下为什么需要版本管理?

为了存档,为了热修复的时候可以不用全部重新发布Module

我们使用常规的compile project('...')的方式引入Module时是无法进行版本管理的。版本只对已发布到maven仓库的library才有效。

那难道我们要把组件都发布成maven吗?是的。
那难道我们要把组件都发布到远处仓库吗?不是的。

我们可以把组件发布到本地仓库,甚至于Project文件夹目录的本地仓库。下面是发布到Project文件夹的方案解析。

首先:我们在工程根目录的build.gradle中添加如下代码:

buildscript {
    ...
}


allprojects {
    apply plugin: 'maven'

    repositories {
        jcenter()
        maven {
           //本地maven仓库,路径是./projectName/.repo-tmp
            url 'file://' + project.rootProject.rootDir + File.separator + '.repo-tmp'
        }
    }

    configurations.all {
        // check for updates every build
        resolutionStrategy.cacheChangingModulesFor 1, 'seconds'
    }
}


subprojects { subp ->
    //发布后的group名称
    project.group = 'com.bamboo.component'
    subp.afterEvaluate {
        //只能发布设置了版本号的Module
        if (!(subp.version + '').equals('unspecified')) {

            if (subp.extensions.findByName('android') != null
                    && !getPlugins().hasPlugin("com.android.application")) {
                android.libraryVariants.all { variant ->
                    if (variant.name.equals('release')) {
                        def generateandroidSourcesJar = task("generate${variant.name.capitalize()}SourcesJar", type: Jar) {
                            classifier = 'sources'
                            from android.sourceSets.main.java.sourceFiles
                        }
                        artifacts {
                            archives generateandroidSourcesJar
                        }
                    }
                }
            } else if (subp.extensions.findByName('android') == null) {
                //java工程
                //生成source 文件
                task('sourcesJar', type: Jar) {
                    from sourceSets.main.java.srcDirs
                    classifier = 'sources'
                }
                artifacts {
                    archives sourcesJar
                }
            }
            uploadArchives {
                repositories.mavenDeployer {
                    repository(url: 'file://' + project.rootProject.rootDir + File.separator + '.repo-tmp')
                    pom.groupId = project.group
                    pom.artifactId = project.name
                    pom.version = project.version
                }
            }
        }
    }
}

然后在需要发布的Module的build.gradle中添加版本号:

version = '1.0'
android{
    ...
}

刷新工程,会发现右侧的task list多了一组task,如下图upload task group。


添加maven发布功能后的task list

点击upload下面的uploadArchives,即可将你的Module发布到Project下的 .repo-tmp 文件夹中,如下图:

发布到仓库成功

然后修改我们的引用Module的方式:
原引用方式:

compile project(':module')

本地maven引用方式:

compile 'com.bamboo.component:module:1.0'

本地maven的方案到这里就讲完了。
发布到本地仓库的优缺点:
优点:

  1. 可以进行组件的版本管理,
  2. 节约整个工程运行时的build时间。
  3. 方便重用。
    缺点是每次Module修改的时候不能实时生效需要重新发布到仓库刷新后才能生效。

写在最后
组件化的配置以及在实践中需要注意的地方不是一篇文章就能说完的,所以如果大家在使用上述方案时遇到了什么麻烦,可留言,我们可以一起研究学习。

本文示例源码,源码把读取include配置的代码放到了buildSrc中,实际效果相同。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容

  • 不怕跌倒,所以飞翔 组件化开发 参考资源 Android组件化方案 为什么要组件化开发 解决问题 实际业务变化非常...
    笔墨Android阅读 2,900评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,561评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,097评论 18 139
  • 2017年8月19日王世明结婚至今。始终感觉很累。而上海皮展也是累得筋疲力尽。 好几个月没有学习了。感觉自己的心态...
    春暖花开_048d阅读 174评论 0 0
  • 洗衣的故事 文/池家明 上世纪五、六十年代的石桥铺醪糟铺街上没有自来水,生活饮用水都是从水井里取。水井的水是从地下...
    石桥广角阅读 943评论 5 3