Android 自定义构建类型 BuildType
简单介绍了 BuildType 定义在 app 项目与 module 项目的区别,以及在 Gradle 2.x 与 3.x 版本的区别。
最近接触到自定义构建类型 BuildType,发现这一块有些地方稍不注意的话会被绕进去浪费点时间,既然我这边已经花费时间了,如果正好你也需要接触到 BuildType,也许接下来分享的 tips 可能会帮你节省些时间。
缘起
BuildType 相信许多开发者都不陌生,很常见的一种使用场景是线上、线下的后台接口 BaseUrl 不同,许多人会选择在 build.gradle 文件的 buildTypes 中定义全局变量来实现线上线下环境的定义(Gradle 2.x 版本),例如:
buildTypes {
debug {
buildConfigField "String", "BASE_URL", "\"http://debug.api/\""
}
release {
buildConfigField "String", "BASE_URL", "\"https://release.api/\""
}
}
在开发过程中,除了默认的 Debug 和 Release 版本,我们可能还需要为程序自定义一些东西。比如在上线 release 版本前,还需要一个预发布版本,该版本除了后台接口的 BaseUrl 与线上版本不同外,其他资源(包括数据库环境)都与线上相同,该版本用来做发布前的最后测试,最大程度避免线上环境出问题。如果每次打预发版本都去直接修改代码中的 BaseUrl 很明显不是最优解。有一种解决方案是自定义 BuildType,在 app 模块下的 build.gradle 的 buildTypes 中自定义新的构建版本:
buildTypes {
debug {
buildConfigField "String", "BASE_URL", "\"http://debug.api/\""
}
release {
buildConfigField "String", "BASE_URL", "\"https://release.api/\""
}
pre.initWith(release)
pre {
buildConfigField "String", "BASE_URL", "\"https://pre.api/\""
}
}
//java 类中调用 BuildConfig.BASE_URL 获取定义的变量
initWith() 是 BuildType 的一项配置项,我们还可以看到上文中提到的buildConfigField其实也是一项配置项。该配置可以理解成initWith(release) 可以理解成拷贝了 release 这一构建类型的所有变量,因为我们知道,每一个构建类型都有一些默认的变量,例如debuggable、zipAlignEnabled等,使用该配置就免去为新增的构建类型定义所有的变量。
定义了新的构建类型后,gradle会自动生成新的task,使用gradle assemblePre即可打包新定义的预发包。这里需要稍微注意的地方就是,必须在 app 模块下的 build.gradle 中定义新的构建类型,gradle 才会生成新的task。
Moduel 中自定义 BuildType 的问题
随着现在模块化开发越来越流行,许多项目都会将一些业务无关的模块独立出去,作为 Moduel 在项目中依赖使用,以此达到复用的效果。很常见的例如网络库可能就会被独立出来,那么上文中的关于就会被定义在子 Module 中。这里不注意的话就会浪费一些时间。假设你在 app 模块与子模块的 build.gradle 的 buildTypes 中都如上文定义了三种类型 debug
、release、pre 版本的BASE_URL,请注意:
如果你执行了gradle assemblePre,没错是构建了 pre 版本,但是打印出日志你会发现:
app.BuildConfig.BASE_URL = "https://pre.api/"
module.BuildConfig.BASE_URL = "https://release.api/"
子模块中如果没有特别指定构建版本,无论你执行的是gradle assemblePre还是gradle assembleDebug,构建的都是 release 版本。可以使用defaultPublishConfig配置指定需要构建的版本,例如在子模块的 build.gradle 中指定:
android {
...
//指定构建版本
defaultPublishConfig "pre"
...
}
当然最好设置个变量,否则如果有许多子模块,不可能修改构建版本时一个一个改过去,常用的是在项目最外层的 build.gradle 中设置变量供子模块调用:
buildscript {
...
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
...
}
ext {
projectBuildType = "debug"
}
//子模块中引用变量
defaultPublishConfig rootProject.ext.projectBuildType
这里需要注意,一旦子模块中指定了构建类型,例如 pre 版本,则该模块的 buildTypes 中必须也要有对应的构建类型 pre,否则编译不通过。并且,一旦指定了构建类型,则该模块的构建类型就只会是指定的类型。举个栗子:子模块中指定了defaultPublishConfig "pre",执行gradle assembleRelease,打印日志:
app.BuildConfig.BASE_URL = "https://release.api/"
module.BuildConfig.BASE_URL = "https://pre.api/"
所以这里就会比较烦人,每次打不同的包都需要去修改projectBuildType的值,还是需要手动修改。
分支名决定构建的版本类型
想要隔离手动修改,网上看到过一种解决方案:
子模块的 build.gradle 中设置:
android {
publishNonDefault true
}
然后在模块的依赖处,例如 app 模块的 build.gradle 中改进依赖的写法:
releaseCompile project(path: ':module', configuration: 'release')
debugCompile project(path: ':module', configuration: 'debug')
这样就可以实现构建时子模块与 app 模块的类型一致。但这种写法太烦了,如果你有十来个依赖,还得一个一个写过去,又如果你还自定义了不止一种构建类型,且没新增一个新的 buildType 都得修改所有的依赖,我认为也不是最优解。
这里提供一个方案仅供参考。先介绍一下我们的开发流程,例如新开发1.0版本。首先从 master 切出一条 devel1.0 分支用于前期开发阶段,开发完毕达到上线标准后,切出一条 pre1.0 预发分支,打预发包做最后测试并做最后的 bug 修复,最后测试通过,合并代码到 master 分支,打线上包发布至应用商店。不同阶段对应不同的分支,所以不同的构建版本可以通过分支名来决定,git 肯定可以获取当前分支名,而 grale 中则可以执行 cmd 命令,二者结合即可达到想要的效果。有这个思路后实现起来就很简单:
ext {
projectBuildType = "debug"
def gitBranchName = "git rev-parse --abbrev-ref HEAD".execute().text.trim()
if(gitBranchName.contains("master")) {
projectBuildType = "release"
} else if(gitBranchName.contains("pre")) {
projectBuildType = "pre"
}
}
Gradle 3.0.0 带来的问题
Android Studio 3.0 + Gradle 3.0 相信许多人都跃跃欲试。升级到 Gradle 3.0 可能需要做一些改动,详情可见Migrate to Android Plugin for Gradle 3.0.0。
Gradle3.0 中自定义 BuildType 有需要注意的地方
Cause of build error
Your app includes a build type that a library dependency does not.
在 Gradle 2.x 时代,如果 app 中定义了 pre 类型,而子模块中没有定义,是不会报错的。但在 Gradle 3.0 下,如果你的 app 包含了新的自定义的 buildType,而依赖库中却没有相应的自定义 buildType,则编译阶段就会报错。
一种解决方案是在子模块里也定义 app 中的所有 buildType,当然,项目里依赖多的同学肯定要吐槽了:我懒!不想修改辣么多东西!
这里 Gradle 也提供了比 2.x 时代更智能的兼容方案:matchingFallbacks
在 buildTypes 中定义 matchingFallbacks,可以指定在子模块中没找到对应的构建类型时要加载哪个类型
//app 模块下的 build.gradle 中
buildTypes {
debug {
buildConfigField "String", "BASE_URL", "\"http://debug.api/\""
}
release {
buildConfigField "String", "BASE_URL", "\"https://release.api/\""
}
pre.initWith(release)
pre {
buildConfigField "String", "BASE_URL", "\"https://pre.api/\""
matchingFallbacks = ['pre', 'debug', 'release']
}
}
matchingFallbacks 可以定义多个构建类型,当执行gradle assemblePre 构建 Pre 版本时,而恰巧某个子模块又没有定义 pre 版本,则会一一按照你指定的 matchingFallbacks 从前往后依次寻找,直到类型匹配。这种匹配方案比 Gradle 2.x 时代默认为你构建 release 版本要智能的多,至少我们还可以根据变量来在构建不同类型时定义不同的匹配顺序。并且我们也可以抛弃掉前文提到的defaultPublishConfig配置了,因为 Gradle 3.x 中,只要 gradle 构建时,子模块的构建类型变成了与 app 构建类型一致了(前提是子模块中也定义了该类型),变得更加灵活。
技术终归是在向前发展的。关于自定义 BuildType 的一些使用小贴士就是这些了,至于更多的关于构建类型相关的知识,例如 buildTypes 结合 productFlavors,或是定义 sourceSets 属性指定不同的代码目录、资源文件目录等知识以后有机会再开一篇聊吧(又挖坑2333)。
啰嗦了一堆,权且算是抛砖引玉。吼啦,下篇博客见~
Android 自定义构建类型 BuildType的更多相关文章
- Android开发之深入理解Android Studio构建文件build.gradle配置
摘要: 每周一次,深入学习Android教程,TeachCourse今天带来的一篇关于Android Studio构建文件build.gradle的相关配置,重点学习几个方面的内容:1.applica ...
- android 自定义view 前的基础知识
本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了 ...
- Android 自定义 view(四)—— onMeasure 方法理解
前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...
- [原] Android 自定义View步骤
例子如下:Android 自定义View 密码框 例子 1 良好的自定义View 易用,标准,开放. 一个设计良好的自定义view和其他设计良好的类很像.封装了某个具有易用性接口的功能组合,这些功能能 ...
- Android Studio构建系统基础
基础知识 项目创建成功后会自动下载Gradle,这个过程特别慢,建议FQ.下载的Gradle在Windows平台会默认在 C:\Documents and Settings\<用户名>.g ...
- Android 自定义title 之Action Bar
Android 自定义title 之Action Bar 2014-06-29 飞鹰飞龙... 摘自 博客园 阅 10519 转 25 转藏到我的图书馆 微信分享: Action Ba ...
- Android自定义View
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...
- Gradle学习系列之九——自定义Task类型
在本系列的上篇文章中,我们学习了多Project构建,在本篇文章中,我们将学到如何自定义Task类型. 请通过以下方式下载本系列文章的Github示例代码: git clone https://git ...
- Android自定义View之ProgressBar出场记
关于自定义View,我们前面已经有三篇文章在介绍了,如果筒子们还没阅读,建议先看一下,分别是android自定义View之钟表诞生记.android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检 ...
随机推荐
- HBase中Memstore存在的意义以及多列族引起的问题和设计
Memstore存在的意义 HBase在WAL机制开启的情况下,不考虑块缓存,数据日志会先写入HLog,然后进入Memstore,最后持久化到HFile中.HFile是存储在hdfs上的,WAL预写日 ...
- 帆软用工具测试超链接打开弹窗(iframe嵌套),解决js传参带中文传递有乱码问题
1.新建超链接 随意点击一个单元格右击,选择 超级链接 2.在弹出的窗口中选择JavaScript脚本 如图: 其中红框框出的是几个要点 ,左边的就不讲了,右上角的参数cc是设置了公式remote ...
- 思维导图学《JVM 虚拟机规范》
目录 工具 虚拟机实现 class 文件结构 字节码指令 其他 虚拟机结构 公众号 coding 笔记.点滴记录,以后的文章也会同步到公众号(Coding Insight)中,希望大家关注_ 公众号 ...
- 冲刺随笔——Day_One
这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 团队进行Alpha冲刺 作业正文 正文 其他参考文献 无 ...
- Python中排序函数sorted和排序方法sort的异同点对比分析
Python中对序列进行排序有两种方法,一种是使用python内置的全局sorted函数,另一种是使用序列内置的sort方法. 一. 两者相同点 在支持sort方法的序列中都可以对序列进行排序: 二者 ...
- RedHat操作指令第2篇
六.RPM包管理命令 主要功能 查询RPM软件.包文件的相关信息 安装.升级.卸载RPM软件包 维护RPM数据库信息 查询RPM软件信息 查询已安装的RPM软件信息 格式:rpm -q[子选项] [软 ...
- 阿里云服务器搭建Docker版AWVS
本文严重参考该文章:https://www.sqlsec.com/2020/04/awvs.html 阿里云服务器搭建Docker版AWVS,因为之前有使用Docker的经验,所以本文只是简述一下安装 ...
- 函数与函数式编程(生成器 && 列表解析 && map函数 && filter函数)-(四)
在学习python的过程中,无意中看到了函数式编程.在了解的过程中,明白了函数与函数式的区别,函数式编程的几种方式. 函数定义:函数是逻辑结构化和过程化的一种编程方法. 过程定义:过程就是简单特殊没有 ...
- CTFHub Web题学习笔记(SQL注入题解writeup)
Web题下的SQL注入 1,整数型注入 使用burpsuite,?id=1%20and%201=1 id=1的数据依旧出现,证明存在整数型注入 常规做法,查看字段数,回显位置 ?id=1%20orde ...
- 云服务器 ECS Linux 安装 VNC Server 实现图形化访问配置说明
阿里云官方公共 Linux 系统镜像,基于性能及通用性等因素考虑,默认没有安装 VNC 服务组件.本文对常见操作系统下的 VNC Server 安装配置进行简要说明. 本文中仅讨论VNC的安装,关于图 ...