在开发的时候需要控制debug、release不同情况下某些开关的控制,如日志打印,debug需要打印,release不需要打印。以前做法是在assets中创建一个config.properties文件,通过读取参数去控制,但是每次release都得去修改,不方便且难免有遗漏。在AS中我们可以在build.gradle配置文件中配置,设置好了之后就直接debug、release即可,无需来回切换修改。
项目中的build.gradle编译之后会生成一个BuildConfig的java文件,其中有部分参数是默认每个APP都会有的,与gradle文件中参数的对应关系如下:
build.gradle文件路径:../app/buildgradle
BuildConfig文件路径:../app/build/generated/source/buildConfig/flavourName(未配置productFlavors,则没有该目录)/debug/包名/BuildConfig
buildTypes { debug { ... debuggable true //是否为debug状态,也可以不配置,默认为true } }
这个参数默认是不用配置的,debug中默认为true,release中默认为false。
但是有些特殊情况下为release模式又需要打印log就可以显示的设置这个值,比如preview预览版本的时候。
buildTypes { debug { … } release { … } }
productFlavors{ xiaomi{ … } tencent{ … } }
除了这些系统预设的字段外,我们自己还可以根据需要设置自己的字段,比如在测试环境和生产环境中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切换的时候都不用去切换值了。
在多渠道分发的时候,我们可以在不同的渠道使用不同的签名文件,但是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中都可以使用,配置成了一个通用的工具。
多渠道打包可以实现两个作用:
1) 可以实现渠道标识,根据不同的渠道加载不同的logo、资源等。
2) 可以实现在同一台手机上安装多个APK。
在AndroidManifest.xml 里添加渠道变量:
CHANNEL_VALUE}" />
在使用多渠道的时候,我们可以为不同的渠道设置不同的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指定一个完全不一样的包名
在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/...文件中可以找到。至此,基本的多渠道打包就可以啦。
其中buildTypes+productFlavors会生成Build Variants中的选项,选择不同的build variant的,则你在点击run、debug的时候就是执行的相应的variant。之前每次安装带签名的APK的时候都是通过打包好APK,然后传到手机上安装的。在这里我们在release中配置好打包签名信息之后(完整配置附在文章结尾),选择xiaomiRelease直接run就相当于你安装了一个带签名的APK,就是这么叼。
在程序Debug的时候,我们就不能在这台手机上执行Release,无法同时跑两个(或更多)应用。而我想要实现Debug的时候APP的名字和logo区分开来。有两种方法可以实现:
这种方法只适合做一些简单的个性化,比如修改APP名称、启动图标等。
首先修改manefest中的值:
然后在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源码做个性化操作,在维护起来也比较困难。
首先在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的个性化操作
首先配置多个签名信息
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 的使用。
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包。
//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') }
Gradle官方说明:
http://tools.android.com/tech-docs/new-build-system
http://tools.android.com/tech-docs/new-build-system/user-guide
参数说明:http://www.cnblogs.com/niray/p/5242985.html
多渠道签名成功校验方法:http://www.cnblogs.com/travellife/p/Gradle-shi-xian-Android-duo-qu-dao-ding-zhi-hua-da.html
Gradle基本知识:
http://blog.csdn.net/u010818425/article/details/52268126
http://www.cnblogs.com/dasusu/p/6628099.html
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/