Android 自动构建-签名信息及文件拷贝
2016-07-29 本文已影响553人
花开堪折枝
根据项目需求,现要在团队内部搭建一个统一的打包平台,实现对Android项目的打包。而且为了方便团队内部的测试包分发。
打包平台使用的是Jenkins,在构建前面我们面临的两个问题就是:
- 签名文件如何配置(开发人员是不可以见的)
- 打包出来的apk如何的再发布和出问题如何回滚到上一次的apk
TODO
- 版本号管理
环境搭建
自行在查询,因为大家的平台可能都不同,现在这类的文章已经很多了,这里就不进行展开了。
签名文件管理
这个需要达到两个目标且不能去修改配置相关信息和使用简单
- 开发人员可以使用自己的签名信息替换
- 配置人员只需要配置一次
设计思路:
通过在打包的机器添加一个环境变量来判断是否是打包机器,这样就可以使用试用两份配置,部分代码如下(后面会提供全部):
File propFile = file(isJenkins ? configPath : 'apk.properties');
if (propFile.exists()) {
def Properties props = new Properties()
props.load(new FileInputStream(propFile))
if (props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
//配置签名信息
} else {
throw new IllegalArgumentException('apk.properties')
}
} else {
println "apk.properties not exist"
android.buildTypes.release.signingConfig = null
}
apk存储
每天、每次构建出来的apk都有记录,方便获取当前软件最新的apk,所以一共保存了三个地方。
- apk存储的根目录, 永远都是最新的apk,方便集成发布工具
- apk存储的根目录/日期 , 每日构建的apk,方便查询指定日期的apk
- apk存储的根目录/日期/项目名称, 当前项目每次构建的apk,完整的记录。
完整脚本信息如下:
打包运行:
gradle clean publishRelease
APK_NAME=test.apk //apk输入名称
APK_DIR=d:/test //保存目录
STORE_FILE=u2020.keystore
STORE_PASSWORD=android
KEY_ALIAS=android
KEY_PASSWORD=android
def storeDir = System.getenv("STORE_ROOT") //配置文件存储路径
def os = System.getProperty("os.name") // 系统类型
def isJenkins = "true".equals(System.getenv("IS_JENKINS")) //是否是打包机
def projectRootName = project.rootProject.name
if (isJenkins && storeDir == null){
throw new NullPointerException('storeDir is null, you need add it in system env ' +
'with key: STORE_ROOT')
}
project.ext{
projectName = projectRootName
apkName = projectName + ".apk"
apkRootDir = null
outputPath = null
sourceApk = null
sourceApkName = null
}
/**
* 默认打包完把apk存储的目录
* win :d:/
* mac :/Download
* linux : /opt
*/
if (project.ext.apkRootDir == null){
if (os == null || os.indexOf('windows')){
project.ext.apkRootDir = 'd:/'
}else if (os.indexOf('linux') > 0){
project.ext.apkRootDir = '/opt'
}else if (os.indexOf('windows')){
project.ext.apkRootDir = '/Download'
}
}
def configPath = storeDir + project.ext.projectName + "/apk.properties"
println "config path: " + configPath
File propFile = file(isJenkins ? configPath : 'apk.properties');
if (propFile.exists()) {
def Properties props = new Properties()
props.load(new FileInputStream(propFile))
if (props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
project.ext.apkName = props['APK_NAME']
project.ext.apkRootDir = props['APK_DIR']
if (hasProperty('PROJECT_NAME')){
project.ext.projectName = PROJECT_NAME
}
println "apkName: " + project.ext.apkName
println "apk store dir: " + project.ext.apkRootDir
println "projectName: " + project.ext.projectName
} else {
throw new IllegalArgumentException('invalid args in apk.properties')
}
} else {
println "apk.properties not exist"
if (isJenkins){
throw new FileNotFoundException("File not found at: " + configPath)
}else{
android.buildTypes.release.signingConfig = null
}
}
def buildDay = new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("GMT+8"))
def buildTime = new Date().format("yyyy-MM-dd HH.mm.ss", TimeZone.getTimeZone("GMT+8"))
android.applicationVariants.all { variant ->
if (variant.buildType.name == 'release') {
variant.outputs.each { output ->
project.ext.sourceApk = output.outputFile.getAbsolutePath();
project.ext.sourceApkName = output.outputFile.getName()
}
}
}
task publishRelease(dependsOn: 'assembleRelease') << {
//构建最新的,固定位置,方便拷贝
def onlyApkDir = project.ext.apkRootDir + "/" ;
def onlyApk = onlyApkDir + project.ext.projectName + ".apk";
//每日保存最新的一个
def dayApkDir = project.ext.apkRootDir + "/" + buildDay + "/";
def dayApk = dayApkDir + project.ext.projectName + ".apk";
//保留每次打包的apk
def timesApkDir = project.ext.apkRootDir + "/" + buildDay + "/" + project.ext.projectName + "/";
def srcApk = project.ext.sourceApk
def srcName = project.ext.sourceApkName
//clean
File dk = new File(dayApk)
if (dk.exists()){
dk.delete()
}
File ok = new File(onlyApk)
if (ok.exists()){
ok.delete()
}
copy{
from srcApk
into onlyApkDir + project.ext.apkName
rename srcName, project.ext.apkName
}
copy{
from srcApk
into dayApkDir
rename srcName, project.ext.apkName
}
copy{
from srcApk
into timesApkDir
rename srcName, buildTime + ".apk"
}
}
看到这里,点个赞吧