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"
    }
}

看到这里,点个赞吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,472评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • 1.介绍 如果你正在查阅build.gradle文件的所有可选项,请点击这里进行查阅:DSL参考 1.1新构建系统...
    Chuckiefan阅读 11,963评论 8 72
  • 时间:早上:5:01-5:36 地点:600育心东方红大学早课群 现在开始孝经的明经分享。接着刚才讲的,“鼎折足,...
    上海雯雯妈M6阅读 364评论 0 0
  • 1、学了国学后孩子更懂礼貌 益谦文化的老师称孩子学国学,强调的是孝道、礼仪,一些孩子从原来的不太懂礼貌变得有礼貌了...
    禹音阅读 282评论 0 0