Android_Jenkins自动打包(2021最新版本)_自动上传蒲公英并钉钉通知

一.本文目标

Jenkins实现持续集成与自动打包
自定义gradle打包脚本
自动上传蒲公英并钉钉群通知

二.Jenkins持续集成与自动打包构建

一切重复的工作皆可自动化,大厂里面都有自动化包构建平台,大多是基于Jenkins持续集成方案来定制的.
Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,使软件的持续集成变成可能Jenkins提供数百个插件来支持构建,部署和自动化任何项目

Jenkins软件包安装与服务管理

  • 安装:brew install jenkins-lts
  • 开启服务:brew services start jenkins-lts
  • 重启服务:brew services restart jenkins-lts
  • 升级服务:brew upgrade jenkins-lts

安装Homebrew

Mac用户需要先安装Homebrew才能安装Jenkins,如果已安装请跳过

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

将以上命令粘贴至终端即可安装Homebrew。

服务启动,这个过程首次可能需要4-5分钟,耐心等待

三.在浏览器打开http://localhost:8080/

  • 需要安装一些插件,选择安装推荐的插件这个选项(这个过程建议翻墙比较快),过程需要30分钟左右
  • 账户创建,这里我们不创建新用户,点击右下角的使用admin账户继续,而admin账户的密码就存储在上面的红色字体文件中


  • 不用改实例配置,默认就好



主页面


四.插件安装--->环境配置--->创建Job--->获取蒲公英api_key--->获取钉钉webhool--->编写gradle打包脚本

1.插件安装

  • Multiple SCMs plugin -- 多仓库构建
  • Groovy Postbuild -- groovy脚本
  • build user vars plugin -- 获取当前登录用户
  • Upload to pgyer -- apk上传蒲公英
  • Locale plugin -- 本地语言

Manage Jenkins -->Manage Plugins 去进行安装上述插件,记得安装完成后重启服务

2.环境配置(jdk,git,gradle,android_sdk)

  • Manage Jenkins -->Global Tool Configuration

配置JDK

配置Git

配置gradle,建议和项目的gradle版本号保持一致

  • Manage Jenkins -->Configule System

配置android_sdk,下面的Locale记得写上zh_CN,这样就支持中文了

3.创建Job

  • 新建Item
  • 配置项目
    主页--->项目--->配置
    General的基本配置
    选中This project is parameterized,然后添加参数选择Choice Parameter,按照下面的格式填写
  • 源码管理配置
    需要添加项目在github或者gitlab的仓库url,然后添加私钥,当然也可以用账号密码,这里是私钥演示

点击添加按钮,注册凭证,可以直接选择username-password
也可以选择私钥,Mac的私钥是在 /Users/houyadong/.ssh/id_rsa 文件中

因为主项目是ASProj,然后引用了i-ui项目的一些模块,这里需要选择Additional Behaviours添加Check out to a sub-directory,然后添加仓库的本地子目录,如果项目是单工程结构,这一步可以跳过

  • 构建触发器
    可以执行周期性的构建项目,也可以当有新代码提交的时候去构建,也可以定时构建,这里面我们不需要这些
  • 构建环境
    勾选图中两个,在构建的时候在控制台中把时间戳打印上去,登录的用户信息设置到jenkins的环境变量里面
  • 构建
    自定义的打包脚本任务,打包之前先clean一遍,然后把堆栈信息给打印出来

点高级,需要勾选图中的pass all job parameters as Project properties,然后在输入框中输入图中内容
BUILD_TYPE:就是上面创建的debug包还是release包
BUILD_URL:指Jekins本次任务构建的url
JOB_NAME:本次构建的任务的名称
BRANCH_NAME:本次构建的仓库的分支
BUILD_USER:发起本次构建的当前用户

参数透传,这些key,还必须在项目根目录下的gradle.properties中去同样定义一份,值无所谓

4.获取蒲公英的apiKey

注册账号,然后API信息中获取

5.钉钉自定义消息格式

  • 创建钉钉群

智能群助手,自定义,复制粘贴webhook

一定要定义关键词,钉钉为了安全起见,在往这个webHook发送消息的时候,如果这个消息中不包含设置的关键词,钉钉群是收不到消息的.
所以一般设置关键词为项目名称然后自定义脚本中 添加 项目名称到消息体中,这样钉钉就能收到信息了

6.自定义打包脚本上传apk到蒲公英与钉钉群通知

在项目根目录下创建jenkins_package.gradle

import groovy.json.JsonSlurper
import java.text.SimpleDateFormat

task packageApk {
    println("BUILD_TYPE:" + BUILD_TYPE)
    File apkFileDir = null
    dependsOn("assemble" + BUILD_TYPE.capitalize())
   if(BUILD_TYPE.equals("release")){
        apkFileDir = new File(project.buildDir, "outputs/apk/release")
    }else{
        apkFileDir = new File(project.buildDir, "outputs/apk/debug")
    }
    doLast {
        def uploadFile = findApkFile(apkFileDir)
        println("uploadFile:" + uploadFile.name)
        uploadApk(uploadFile)
    }
}

def findApkFile(File apkFile) {
    println("apkFile:" + apkFile.name)
    if (apkFile.isDirectory()) {
        def files = apkFile.listFiles()
        for (int i = 0; i < files.length; i++) {
            def findFile = findApkFile(files[i])
            if (findFile != null) {
                return findFile
            }
        }
    } else if (apkFile.name.endsWith(".apk")) {
        return apkFile
    } else return null
}

def uploadApk(File uploadApkFile) {
    // 查找上传的 apk 文件, 这里需要换成自己 apk 路径
    println("uploadApk:" + uploadApkFile.absolutePath + "--" + uploadApkFile.exists())
    if (uploadApkFile == null || !uploadApkFile.exists()) {
        throw new RuntimeException("apk file not exists!")
    }
    println "*************** upload start ***************"

    String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
    String PREFIX = "--", LINE_END = "\r\n";
    String CONTENT_TYPE = "multipart/form-data"; // 内容类型

    try {
        URL url = new URL("https://www.pgyer.com/apiv2/app/upload");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(30000);
        conn.setConnectTimeout(30000);
        conn.setDoInput(true); // 允许输入流
        conn.setDoOutput(true); // 允许输出流
        conn.setUseCaches(false); // 不允许使用缓存
        conn.setRequestMethod("POST"); // 请求方式
        conn.setRequestProperty("Charset", "UTF-8"); // 设置编码
        conn.setRequestProperty("connection", "keep-alive");
        conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);
        DataOutputStream dos = new DataOutputStream(conn.getOutputStream());

        StringBuffer sb = new StringBuffer();
        sb.append(PREFIX).append(BOUNDARY).append(LINE_END);//分界符
        sb.append("Content-Disposition: form-data; name=\"" + "_api_key" + "\"" + LINE_END);
        sb.append("Content-Type: text/plain; charset=UTF-8" + LINE_END);
        //sb.append("Content-Transfer-Encoding: 8bit" + LINE_END);
        sb.append(LINE_END);
        sb.append("*********************************************");//替换成你再蒲公英上申请的apiKey
        sb.append(LINE_END);//换行!


        if (uploadApkFile != null) {
            /**
             * 当文件不为空,把文件包装并且上传
             */
            sb.append(PREFIX);
            sb.append(BOUNDARY);
            sb.append(LINE_END);
            /**
             * 这里重点注意: name里面的值为服务器端需要key 只有这个key 才可以得到对应的文件
             * filename是文件的名字,包含后缀名的 比如:abc.png
             */
            sb.append("Content-Disposition: form-data; name=\"file\"; filename=\"" + uploadApkFile.getName() + "\"" + LINE_END);
            sb.append("Content-Type: application/octet-stream; charset=UTF-8" + LINE_END);
            sb.append(LINE_END);
            dos.write(sb.toString().getBytes())

            InputStream is = new FileInputStream(uploadApkFile)
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = is.read(bytes)) != -1) {
                dos.write(bytes, 0, len);
            }
            is.close();
            dos.write(LINE_END.getBytes());
            byte[] end_data = (PREFIX + BOUNDARY + PREFIX + LINE_END).getBytes();

            dos.write(end_data);
            dos.flush();
            /**
             * 获取响应码 200=成功 当响应成功,获取响应的流
             */
            int res = conn.getResponseCode();
            if (res == 200) {
                println("Upload request success");
                BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()))
                StringBuffer ret = new StringBuffer();
                String line
                while ((line = br.readLine()) != null) {
                    ret.append(line)
                }
                String result = ret.toString();
                println("Upload result : " + result);

                def resp = new JsonSlurper().parseText(result)
                println result
                println "*************** upload finish ***************"
                sendMsgToDing(resp.data)
            } else {
                //发送钉钉 消息--构建失败
            }
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

def sendMsgToDing(def data) {
    def conn = new URL("*********************************************").openConnection()//替换成自己的钉钉webHook的url
    conn.setRequestMethod('POST')
    conn.setRequestProperty("Connection", "Keep-Alive")
    conn.setRequestProperty("Content-type", "application/json;charset=UTF-8")
    conn.setConnectTimeout(30000)
    conn.setReadTimeout(30000)
    conn.setDoInput(true)
    conn.setDoOutput(true)
    def dos = new DataOutputStream(conn.getOutputStream())

    def downloadUrl = "https://www.pgyer.com/" + data.buildShortcutUrl
    def qrCodeUrl = "![](" + data.buildQRCodeURL + ")"
    def detailLink = "[项目地址](${BUILD_URL})"

    def _title = "### 【${JOB_NAME}】构建成功"
    def _content = new StringBuffer()
    _content.append("\n\n### ${JOB_NAME}构建成功")
    _content.append("\n\n构建版本:${BRANCH_NAME}")
    _content.append("\n\n构建类型:${BUILD_TYPE}")
    _content.append("\n\n下载地址:" + downloadUrl)
    _content.append("\n\n" + qrCodeUrl)
    _content.append("\n\n构建用户:${BUILD_USER}")
    _content.append("\n\n构建时间:" + getNowTime())
    _content.append("\n\n查看详情:" + detailLink)
    def json = new groovy.json.JsonBuilder()
    json {
        msgtype "markdown"
        markdown {
            title _title
            text _content.toString()
        }
        at {
            atMobiles([])
            isAtAll false
        }
    }

    println(json)
    dos.writeBytes(json.toString())
    def input = new BufferedReader(new InputStreamReader(conn.getInputStream()))
    String line = ""
    String result = ""
    while ((line = input.readLine()) != null) {
        result += line
    }
    dos.flush()
    dos.close()
    input.close()
    conn.connect()
    println(result)

    println("*************** 钉钉消息已发送 ***************")
}

//获取当前时间
def getNowTime() {
    def str = "";
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    Calendar lastDate = Calendar.getInstance();
    str = sdf.format(lastDate.getTime());
    return str;
}

在app中的build.gradle中添加

apply from: '../jenkins_package.gradle'

五.Jenkins执行打包

项目 -->Build with parameters --> 选择debug或release -- >开始构建

可以在控制台看打包输出

打包成功并且上传到了蒲公英

发送钉钉群消息

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

推荐阅读更多精彩内容