Android 自动构建-签名信息及文件拷贝

根据项目需求,现要在团队内部搭建一个统一的打包平台,实现对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"
    }
}

看到这里,点个赞吧

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 164,104评论 24 696
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 128,922评论 18 137
  • 1.介绍 如果你正在查阅build.gradle文件的所有可选项,请点击这里进行查阅:DSL参考 1.1新构建系统...
    Chuckiefan阅读 10,939评论 8 72
  • 时间:早上:5:01-5:36 地点:600育心东方红大学早课群 现在开始孝经的明经分享。接着刚才讲的,“鼎折足,...
    上海雯雯妈M6阅读 184评论 0 0
  • 1、学了国学后孩子更懂礼貌 益谦文化的老师称孩子学国学,强调的是孝道、礼仪,一些孩子从原来的不太懂礼貌变得有礼貌了...
    禹音阅读 147评论 0 0
  • 小会子哟 2014/7/20看完了《傲慢与偏见》的电影,矫情的改了QQ签名。我说:爱应如达西,不浮华,不张扬,踏踏...
    会子阅读 5,749评论 11 44
  • 0830(D31)亲爱的师师,我知道你身上有许多美好的特质,今天我欣赏你, 执行力强,雷厉风行,早晨4:50醒来,...
    师师的成长记录阅读 62评论 0 0