1.使用配置文件管理app版本
因为Android Studio是使用gradle进行项目构建的,这使得通过配置文件进行版本管理成为可能。
使用配置文件管理app版本很简单,就是定义一个properties文件,里面有版本号、版本名等版本信息,只需要在build.gradle中引用该文件,使用该配置文件的属性,进行项目的版本号等版本信息的赋值,就可以实现版本号的动态控制(**注意:**在gradle文件中配置的版本号、版本名称是优于在manifest.xml中配置的,如果你在gradle文件中配置了版本信息,那么不管你是否也在manifest.xml文件中进行了配置,系统都不会再去manifest.xml中进行版本信息的读取了)。
要实现这个目标,首先,要新建一个properties文件(或者直接在android studio工作控件下的local.properties文件中进行配置也可以,本文的例子就是在该文件中进行配置的),内容如下:
#local.properties文件中自己定义的sdk位置,本来就有
sdk.dir=D\:\\Android\\sdk
#=====以下是自己定义的内容=====
# 打包的输出路径
appReleaseDir=D:/package/as/madq_
# APP版本号,用来升级使用
appVersionCode=2
# APP版本名称,最终打包使用
appVersionName=1.0.0.2
# app正式版包名后缀
appSuffixName=_release.apk
properties文件之后,在gradle中引用的方式如下:
// 默认版本号
ext.appVersionCode = 1
// 默认版本名
ext.appVersionName = "1.0.0.0"
// 默认apk输出路径
ext.appReleaseDir = "D:\\package\\as\\_"
// 默认正式包后缀名
ext.appSuffixName = "_release.apk"
// 加载版本信息配置文件方法
def loadProperties() {
def proFile = file("../local.properties")
Properties pro = new Properties()
proFile.withInputStream { stream->
pro.load(stream)
}
appReleaseDir = pro.appReleaseDir
appVersionCode = Integer.valueOf(pro.appVersionCode)
appVersionName = pro.appVersionName
appSuffixName = pro.appSuffixName
}
//加载版本信息
loadProperties()
上文的代码,实现了从配置文件读取属性,赋值给本地变量,这里chu,注意,本地变量一定要使用ext.xxx进行定义,如果使用def定义,是读取不到外部文件的属性的,运行会报属性找不到的错误;此外,还要注意,定以完方法之后,要调用一次,方法才会执行。加载到属性之后,只需要使用变量值设置版本号等信息就可以了:
defaultConfig {
...
versionCode appVersionCode
versionName appVersionName
...
}
这样就可以省去每次改动版本号,都得sync gradle的麻烦了,此外,还可以自己定义一些版本号自动更新策略,例如在某些gradle任务(通常是aR任务)执行成功后,进行版本号+1操作等,这样整个版本管理都轻松不少。
2.自定义apk文件输出路径及apk文件名
使用android studio进行apk打包和使用eclipse不同,eclipse在签名打包的过程中会让你指定文件名和输出路径,但是android studio如果你不进行配置,文件名就是固定的,只会让你指定一个路径,而且为了避免前后打的包命名冲突,每次都得该apk文件名,很麻烦,使用gradle配置就能够解决这个问题。
可以看到,在前文中提到的配置文件中,除了版本信息,还配置了一个apk输出地址appReleaseDir 信息,要实现将apk输出到指定地址,需要如下操作:
applicationVariants.all { variant ->
variant.outputs.each { output ->
//开始输出,自定义输出路径
output.outputFile =
new File(appReleaseDir + getDate() +
"_v" + appVersionName +
variant.productFlavors[0].name +
appSuffixName)
}
}
在上面的代码中,组装apk文件名的时候,使用到了一个getDate方法,用于获取格式化时间,避免多次打包命名冲突的问题,此外在命名的时候还使用了variant.productFlavors[0].name,这是多渠道打包,用于标记该包是哪个渠道的,下文会具体说多渠道打包,这里先不用管,指定apk输出路径和文件名就是这么简单。
def getDate() {
def date = new Date()
def formattedDate = date.format('yyyy_MM_dd_HHmm')
return formattedDate
}
3.gradle配置多渠道打包
在国内,打包做的最好的莫过于友盟了,本文就介绍使用友盟进行多渠道打包,使用友盟进行多渠道打包,可以实现统计应用在每个渠道市场被下载次数的功能。在gradle进行多渠道打包配置,可以一次性打出每个渠道的包,省的为每个市场一个一个打。试想一下,国内应用市场最少十几二十个,如果手动为每个市场单独打包,程序员非得哭晕在厕所啊。使用gradle配置,可以一个任务打出所有渠道的包,结合上文介绍,可以让所有的release包以指定apk文件名输出到指定路径,想想还是非常爽的,而且结合Jenkins自动构建平台,还可以将打出的包发布到指定服务器,供用户通过连接或者二维码下载,相当方便。通过友盟进行多渠道打包,主要有以下两步:
a.在manifest.xml文件中进行UMENG_CHANNEL的配置
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
b.在gradle中替换manifest.xml中声明的占位符
// 友盟多渠道打包
productFlavors {
// 360手机助手
_360 { }
// 91手机助手
_91 {}
// 应用汇
_yingyonghui {}
// 豌豆荚
_wandoujia { }
// 百度手机助手
_baidu { }
...
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
其实对productFlavors 的配置还有另一种方式,即:
productFlavors {
_wandoujia {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
}
_360 {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
}
}
4.gradle构建android用到的所有配置如下:
apply plugin: 'com.android.application'
// 默认版本号
ext.appVersionCode = 1
// 默认版本名
ext.appVersionName = "1.0.0.0"
// 默认apk输出路径
ext.appReleaseDir = "D:\\package\\as\\mdq_"
// 默认正式包后缀名
ext.appSuffixName = "_release.apk"
// 加载版本信息配置文件方法
def loadProperties() {
def proFile = file("../local.properties")
Properties pro = new Properties()
proFile.withInputStream { stream->
pro.load(stream)
}
appReleaseDir = pro.appReleaseDir
appVersionCode = Integer.valueOf(pro.appVersionCode)
appVersionName = pro.appVersionName
appSuffixName = pro.appSuffixName
}
// 加载版本信息
loadProperties()
// 应用相关配置
android {
compileSdkVersion 18
buildToolsVersion "21.1.2"
defaultConfig {
// 应用id,即包名
applicationId "ab.cd"
// 最低适配版本,低于此版本的手机无法安装
minSdkVersion 16
// 目标版本,即在该版本上做了充分测试,应用最适用的版本
targetSdkVersion 22
// 版本号,每打一次包加1
versionCode appVersionCode
// 版本名,例如1.0.1,通常用三位,表示主版本号.分版本号.补丁号(小版本号)
versionName appVersionName
// dex突破65535限制,当app方法数超过限制,会采用多dex打包
multiDexEnabled true
// 默认打包渠道是友盟
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
}
// 禁止Lint出错导致打包异常终止
lintOptions {
disable 'MissingTranslation', 'ExtraTranslation'
abortOnError false
ignoreWarnings true
}
//签名信息
signingConfigs {
// debug签名信息
debugConfig {
storeFile file("C:\\debug.keystore")
storePassword "123456"
keyAlias "debug"
keyPassword "123456"
}
// 发布版签名
releaseConfig {
storeFile file("C:\\release.keystore")
storePassword "123456"
keyAlias "release"
keyPassword "123456"
}
}
buildTypes {
// debug构建配置
debug {
// 显示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
// apk包名称后缀,用来区分release和debug
versionNameSuffix "_debug"
// 不混淆
minifyEnabled false
// 不压缩优化
zipAlignEnabled false
// 不进行资源优化(删除无用资源等)
shrinkResources false
// 使用的签名信息
signingConfig signingConfigs.debugConfig
}
// release构建配置
release {
// 正式版不显示log
buildConfigField "boolean", "LOG_DEBUG", "false"
// 进行混淆
minifyEnabled true
// 进行压缩优化
zipAlignEnabled true
// 进行资源优化,移除无用的resource文件
shrinkResources true
// 使用的签名信息
signingConfig signingConfigs.releaseConfig
// 使用的混淆规则文件,前面是系统默认的文件,会全部混淆,
// 后面是自定义不混淆的文件(domain,android四大组件,自定义view等一般是不能混淆的)
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
//应用编译完成,自定义apk输出位置及文件名
applicationVariants.all { variant ->
variant.outputs.each { output ->
//开始输出,自定义输出路径
output.outputFile = new File(appReleaseDir + getDate() + "_v" +
appVersionName + variant.productFlavors[0].name + appSuffixName)
}
}
}
}
// 打包忽略掉第三方jar相同的文件 打包排除以下文件,屏蔽因as自身bug,在没有重复引用jar时,提示jar重复引用的问题
packagingOptions {
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/notice.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/dependencies.txt'
exclude 'META-INF/LGPL2.1'
}
// 友盟多渠道打包
productFlavors {
// 使用注释掉的这种方式也可以实现多渠道打包,这样就不用下面的productFlavors.all函数了
// 如果只使用占位信息定义,如wandoujia{},则需要productFlavors.all函数同意标识
// wandoujia {
// manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
// }
// 360手机助手
_360 { }
// 91手机助手
_91 {}
// 应用汇
_yingyonghui {}
// 豌豆荚
_wandoujia { }
// 百度手机助手
_baidu { }
// 安智市场
_anzhi { }
// 机锋
_jifeng { }
// 魅族市场
_meizu { }
// 小米市场
_xiaomi { }
// google商店
_googleplay { }
// 安卓市场
_anzhuoshichang { }
// 华为应用商店
_huawei { }
// 淘宝手机助手
_taobao { }
}
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
// 获取格式化时间,用来标识打包时间,同时避免命名冲突
def getDate() {
def date = new Date()
def formattedDate = date.format('yyyy_MM_dd_HHmm')
return formattedDate
}
dependencies {
compile project(':andBase')
compile project(':initActivity')
compile files('libs/android-async-http-1.4.4.jar')
compile files('libs/baidumapapi_v3_2_0.jar')
compile files('libs/easemobchat_2.1.8.jar')
compile files('libs/fastjson-1.1.31.jar')
compile files('libs/HCNetSDK.jar')
compile files('libs/jsr305-2.0.1.jar')
compile files('libs/locSDK_3.3.jar')
compile files('libs/PlayerSDK.jar')
compile files('libs/umeng-analytics-v5.5.3.jar')
compile files('libs/universal-image-loader-1.9.3.jar')
compile files('libs/xUtils-2.6.14.jar')
}
// 使用自己的LockHunter进行文件解锁,
// as中clean的时候总是提示删不掉这个文件那个文件,这里自己的clean任务就可以删除
// 前提是安装了lockhunter
task clean(type: Exec) {
ext.lockhunter = "D:\\Program Files\\LockHunter.exe"
def buildDir = file("build")
commandLine 'cmd', "$lockhunter", '/delete', '/silent', buildDir
}
5.其它可参考配置博客地址
赞赏一下