1. 自定义字段
在开发的时候需要控制debug、release不同情况下某些开关的控制,如日志打印,debug需要打印,release不需要打印。以前做法是在assets中创建一个config.properties文件,通过读取参数去控制,但是每次release都得去修改,不方便且难免有遗漏。在AS中我们可以在build.gradle配置文件中配置,设置好了之后就直接debug、release即可,无需来回切换修改。
1.1 默认字段
项目中的build.gradle编译之后会生成一个BuildConfig的java文件,其中有部分参数是默认每个APP都会有的,与gradle文件中参数的对应关系如下:
build.gradle文件路径:../app/buildgradle
BuildConfig文件路径:../app/build/generated/source/buildConfig/flavourName(未配置productFlavors,则没有该目录)/debug/包名/BuildConfig
- DEBUG↔debuggable:
buildTypes { debug { ... debuggable true //是否为debug状态,也可以不配置,默认为true } }
这个参数默认是不用配置的,debug中默认为true,release中默认为false。
但是有些特殊情况下为release模式又需要打印log就可以显示的设置这个值,比如preview预览版本的时候。
- BUILD_TYPE↔buildTypes中的每种类型的名字,其中debug、release就是BUILD_TYPE的值
buildTypes { debug { … } release { … } }
- FLAVOR↔productFlavors中的每种flavor的名字,其中xiaomi、tencent就是FLAVOR的值
productFlavors{ xiaomi{ … } tencent{ … } }
- APPLICATION_ID↔applicationId
- VERSION_CODE↔versionCode
- VERSION_NAME↔versionName
1.2 自定义字段
除了这些系统预设的字段外,我们自己还可以根据需要设置自己的字段,比如在测试环境和生产环境中baseUrl是不相同的,我们就可以在debug、release中分别设置对应的字段值,这样就不必每次切换了。其中在buildTypes不同模式中公共字段信息我们可以设置在defaultConfig里面,不必再每个buildTypes中都设置。
android {
defaultConfig { //默认项目配置信息,buildTypes中公共字段可以配置在这里
…
//公共字段
buildConfigField('String', 'logTag', '"TAG"') //打印日志输出的TAG标记名
buildConfigField('boolean', 'crashInfoSaveAsJson', 'false') //崩溃日志保存为JSON,否则直接保存为String
}
//自定义字段语法 buildConfigField('boolean','API_ENV','true')
buildTypes { //构建的类型方式
debug {
…
buildConfigField('boolean', 'delAttachment', 'false') //邮件发送成功后,是否删除附件
buildConfigField('String', ‘baseUr’l,'"http://www.android.com/test"') //测试网络
}
release {
…
buildConfigField('boolean', 'delAttachment', 'true') //邮件发送成功后,是否删除附件
buildConfigField('String', 'baseUrl','" http://www.android.com/product "') //生产网络
}
}
}
这里需要注意的值如果是String类型的自定义字段,value需要放在双引号里面,例如:’”TAG”’。
编译之后就可以直接使用BuildConfig.baseUrl就可以了,以后debug、release切换的时候都不用去切换值了。
2. 签名文件配置
在多渠道分发的时候,我们可以在不同的渠道使用不同的签名文件,但是debug的时候只能使用一个。签名文件根据keystore的信息存放位置不同,可以分为三种。
1) 直接将签名的所有信息配置在gradle中
signingConfigs {
release {
storeFile file('../keystore.jks')
storePassword '123456'
keyAlias 'HomeKey'
keyPassword '123456'
}
}
2) 将gradle信息存放在local.properties或新建一个properties文件,相对来讲,这种方式取值的时候比较麻烦
signingConfigs {
release {
storeFilefile(properties.getProperty("keystroe_storeFile"))
storePasswordproperties.getProperty("keystroe_storePassword")
keyAliasproperties.getProperty("keystroe_keyAlias")
keyPasswordproperties.getProperty("keystroe_keyPassword")
}
}
local.properties中的配置,路径:../modul/local.properties,在与app同级的目录中:
keystroe_storeFile=../keystore.jks #keystory所在路径
keystroe_keyAlias=HomeKey #别名不能为中文
keystroe_storePassword=123456
keystroe_keyPassword=123456
3) 第三种方法综合了前两种方法的优点,既保护了签名信息,使用起来又方便。将签名信息保存到gradle.properties文件中,默认是没有的,需要自己建。
signingConfigs {
release {
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
}
}
gradle.properties中的配置,路径:../project/gradle.properties,在与app同级的目录中
RELEASE_KEY_ALIAS= HomeKey #别名不能为中文
RELEASE_KEY_PASSWORD=123456
RELEASE_STORE_PASSWORD=123456
RELEASE_STORE_FILE=../../keystore.jks
在keystore配置相对路径的时候,要以实际使用的build.gradle文件所在目录为起点去找keystore文件所在目录。
如果按照第二、第三种方式去配置签名文件,则在整个project中都可以使用,配置成了一个通用的工具。
3. 多渠道打包
多渠道打包可以实现两个作用:
1) 可以实现渠道标识,根据不同的渠道加载不同的logo、资源等。
2) 可以实现在同一台手机上安装多个APK。
3.1 加入渠道变量
在AndroidManifest.xml 里添加渠道变量:
<meta-data android:name="CHANNEL"android:value="${CHANNEL_VALUE}" />
3.2 配置渠道信息
在使用多渠道的时候,我们可以为不同的渠道设置不同的applicationId、logo、加载的资源等。实现不同的渠道的个性化需求。
配置:
productFlavors { //多渠道分发 gradlewassembleFlavorRelease
xiaomi { //小米
applicationIdSuffix ‘.debug’ //在原有applicationId的基础上加上此后缀
}
tencent { //腾讯
applicationId"com.xx.xx.tencent" //在此渠道使用新的包名,将会替换manifeset中package的值,其他不变
}
qihu360 { //360
}
productFlavors.all {
//CHANNEL_VALUE同manifest中meta-data标签(CHANNEL)中的value值保存一致
flavor ->flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}
}
不同的渠道、编译类型如果想改变包名有两种方式:
1) applicationIdSuffix ‘.debug’,在applicationId的末尾追加后缀,形成一个新的包名
2) 重写applicationId指定一个完全不一样的包名
3.3 Release打包配置
在release里面配置混淆、批量更改APK名字。
release {
buildConfigField('boolean','delAttachment', 'true') //邮件发送成功后,是否删除附件
zipAlignEnabled true // 开启ZipAlign优化
shrinkResources true //移除无用的资源文件
minifyEnabled true //编译时是否混淆
proguardFilesgetDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release //签名信息
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null &&outputFile.name.endsWith('.apk') &&'release'.equals(variant.buildType.name)) {
def fileName =outputFile.name.replace("${variant.flavorName}", "V${defaultConfig.versionName}-${variant.flavorName}")
fileName =fileName.replace(".apk", "-${buildTime()}.apk")
output.outputFile = newFile(outputFile.parent, fileName)
}
}
}
}
获取时间方法:
def buildTime() { //生成时间
return new Date().format('yyyyMMddHHmmss')
}
在批量打包生成APK名字的时候,有个地方需要注意,因为我打包的时候APK名字加上了'yyyyMMddHHmmss'这个时间限制,所有在debug的时候会报错,报错信息为找不到安装包,因为Debug命名的apk同pull到手机上的apk名字对不上,生成的时间会相差几秒。
解决办法:
1) 时间不精确到秒,直接yyyyMMdd就可以了,但是这样的话还是存在一个问题,在output文件夹中每天都会产生一个新的debug的apk,需要勤clean项目,不然会越来越大。
2) 加上一个限制条件'release'.equals(variant.buildType.name),判断当前build的类型,只有release的时候我们才执行改名操作,这样debug的时候生成的apk也永远只有一个。
执行命令gradlew assembleRelease就可以批量生成每一个渠道的签名包了,在../app/build/outputs/apk/...文件中可以找到。至此,基本的多渠道打包就可以啦。
4. BuildTypes切换
其中buildTypes+productFlavors会生成Build Variants中的选项,选择不同的build variant的,则你在点击run、debug的时候就是执行的相应的variant。之前每次安装带签名的APK的时候都是通过打包好APK,然后传到手机上安装的。在这里我们在release中配置好打包签名信息之后(完整配置附在文章结尾),选择xiaomiRelease直接run就相当于你安装了一个带签名的APK,就是这么叼。
5. 渠道个性化
在不同的渠道显示不同的splash页面、logo等是最常见的需求,我们也可以根据渠道的不同在java文件中做不同的逻辑操作,这些在gradle的配置中都可以轻松实现。
5.1 运行多个APK
在程序Debug的时候,我们就不能在这台手机上执行Release,无法同时跑两个(或更多)应用。而我想要实现Debug的时候APP的名字和logo区分开来。有两种方法可以实现:
5.1.1 第一种方法
这种方法只适合做一些简单的个性化,比如修改APP名称、启动图标等。
首先修改manefest中的值:
<application ...
android:icon="${app_icon}"
android:label="${app_name}">
然后在buildTypes中配置对应的信息:
debug { ...
applicationIdSuffix ".debug" //这里是在applicationId中添加了一个后缀
manifestPlaceholders = [app_icon: '@mipmap/ic_launcher_1' ,app_name:'@string/app_name_1']
}
release {
...
manifestPlaceholders = [app_icon: '@mipmap/ic_launcher' ,app_name:'@string/app_name']
}
运行项目便可以达到切换logo图标、app名称的目的。同理可以推广到其他的资源文件的个性化需求,但是这种方法就无法对java源码做个性化操作,在维护起来也比较困难。
5.1.2 第二种方法
首先在buildTypes中给debug配置applicationIdSuffix,形成一个新的applicationId,此时debug的APK就可以同release的APK同时安转在手机上了。
debug { …
debuggable true //是否为debug状态,也可以不配置,默认为true
applicationIdSuffix ".debug" //这里是在applicationId中添加了一个后缀
buildConfigField('boolean','delAttachment', 'false') //邮件发送成功后,是否删除附件
}
然后在../app/src目录下新建一个debug名字的文件夹(名字必须与buildTypeName、flavorName保存一致),将需要替换的resource、java文件等资源创建,其中目录结构必须与mian中的保存一致。
此时运行APP可以看到debug的apk与release的apk可以共存且名字与logo都不一样。这种方法同样适用于flavor的个性化操作
5.2 多渠道独立签名
首先配置多个签名信息
signingConfigs { release_xiaomi {
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
}
release_tencent {
keyAlias RELEASE_KEY_ALIAS_1
keyPassword RELEASE_KEY_PASSWORD_1
storeFile file(RELEASE_STORE_FILE)_2
storePassword RELEASE_STORE_PASSWORD__1
}
}
渠道信息:
productFlavors { //多渠道分发 gradlewassembleFlavorRelease xiaomi { //小米
}
tencent { //腾讯
}
productFlavors.all {
//CHANNEL_VALUE同manifest中meta-data标签(CHANNEL)中的value值保存一致
flavor ->flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}
}
打包配置:
release { ...
//多个 flavor ,指定 flavor 使用指定 签名
productFlavors.xiaomi.signingConfig signingConfigs.release_xiaomi
productFlavors.tencent.signingConfig signingConfigs.release_tencent
}
// debug 并不能设置多个签名,如果debug包需要测试诸如微信、地图等第三方 sdk ,则可以指定 debug 包使用 release 包的签名
debug {
...
signingConfig signingConfigs.release
}
这里再做几点补充:
1) 给某个 flavor指定签名的方法对 debug无效,简单来说,debug签名只能指定一个或者使用默认的debug签名。
2) 多渠道使用独立签名,打包时千万不要使用Android Studio 中 Build 菜单下的 GenerateSigned APK,因为当你使用这个打包的时候, Android Studio 会让你指定使用的签名文件。解决方法就是使用 gradle tasks。传送门:Android GradleBuild Tasks
3) 鉴于第一点中的传送门需要FQ,所以在这里简单介绍一下 Android Gradle Build Tasks 的使用。
6. 打包命令
Terminal中输入命令就可以随心所欲的打出你想要的apk。
基本语法:gradlew assemble[flavor][buildType]
如:gradlew assembleFlavor1Release;gradlew assembleFlavor2Debug
gradle本身支持命令缩写, 如:gradleW assFlavor1R
拓展:
执行 gradlew build 打全渠道即所有flavor;且含所有buildTypes
执行 gradlew assembleRelease ,将会打出所有渠道的release包;
执行 gradlew assembleXiaomi,将会打出小米渠道的release和debug版的包;
执行 gradlew assembleXiaomiRelease将生成小米的release包。
7. Gradle完整配置
initWith 可以方便我们继承其他的配置,只需要添加需要的部分
//debug的一个扩展preview{
initWith debug //继承debug的配置
applicationIdSuffix ".preview"
}
完整的bulid.gradle配置:
apply plugin: 'com.android.application'
android { //此语法只有当你的apply plugin:'com.android.xxx'时才可以用
compileSdkVersion 23 //开发时采用的sdk版本
buildToolsVersion '25.0.0' //编译时采用的编译工具版本
defaultConfig { //默认项目配置信息
applicationId "com.hyj.xxx"
minSdkVersion 18
targetSdkVersion 23
versionCode 14
versionName "3.4.4"
buildConfigField('String', 'logTag', '"TAG"') //打印日志输出的TAG标记名
buildConfigField('boolean', 'crashInfoSaveAsJson', 'false') //崩溃日志保存为JSON,否则直接保存为String
manifestPlaceholders= [CHANNEL_VALUE: "official"] //默认渠道为官网
}
signingConfigs { //签名信息配置
release { //正式版本
keyAlias RELEASE_KEY_ALIAS
keyPassword RELEASE_KEY_PASSWORD
storeFile file(RELEASE_STORE_FILE)
storePassword RELEASE_STORE_PASSWORD
}
}
//自定义字段语法buildConfigField('boolean','API_ENV','true')
buildTypes { //构建的类型方式
debug {
debuggable true //是否为debug状态,也可以不配置,默认为true
applicationIdSuffix ".debug" //这里是在applicationId中添加了一个后缀
buildConfigField('boolean', 'delAttachment', 'false') //邮件发送成功后,是否删除附件
}
preview{
initWith release //继承debug的配置
applicationIdSuffix ".preview"
debuggable true
}
release {
buildConfigField('boolean', 'delAttachment', 'true') //邮件发送成功后,是否删除附件
zipAlignEnabled true // 开启ZipAlign优化
shrinkResources true //移除无用的资源文件
minifyEnabled true //编译时是否混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
signingConfig signingConfigs.release //签名信息
applicationVariants.all { variant -> //批量修改Apk名字
variant.outputs.each { output ->
def outputFile =output.outputFile
if (outputFile != null&& outputFile.name.endsWith('.apk') &&'release'.equals(variant.buildType.name)) {
def fileName =outputFile.name.replace("${variant.flavorName}","V${defaultConfig.versionName}-${variant.flavorName}")
fileName =fileName.replace(".apk", "-${buildTime()}.apk")
output.outputFile = newFile(outputFile.parent, fileName)
}
}
}
}
}
productFlavors { //多渠道分发 gradlewassembleFlavorRelease
xiaomi { //小米
}
tencent { //腾讯
}
qihu360 { //360
}
productFlavors.all {
//CHANNEL_VALUE同manifest中meta-data标签(CHANNEL)中的value值保存一致
flavor -> flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile'junit:junit:4.12'
compile 'com.android.support:design:22.2.1'
compile 'com.android.support:appcompat-v7:23.4.0'
compile project(':Library')
compile files('libs/xstream-1.4.7.jar')
compile files('libs/xpp3_min-1.1.4c.jar')
compile files('libs/activation.jar')
compile files('libs/additionnal.jar')
compile files('libs/commons-email-1.4.jar')
compile files('libs/mail.jar')
}
def buildTime() { //生成时间
return new Date().format('yyyyMMdd')
}
8. 参考资料
Gradle官方说明:
http://tools.android.com/tech-docs/new-build-system
http://tools.android.com/tech-docs/new-build-system/user-guide
参数说明:
多渠道签名成功校验方法:
Gradle基本知识:
lib项目中无法使用主项目中BuildConfig.DEBUG值问题:http://www.jianshu.com/p/1907bffef0a3
美团Android自动化之旅—生成渠道包:http://tech.meituan.com/mt-apk-packaging.html
美团Android自动化之旅—适配渠道包:http://tech.meituan.com/mt-apk-adaptation.html
Groovy脚本语言:
Gradle中的应用解析:http://www.jianshu.com/p/a3805905a5c7#
语法规范:http://ifeve.com/groovy-syntax/