Android 使用Jenkins 自动化多渠道打包并且分发到蒲公英、下发到钉钉通知【即拿即用】

前言

一、tomcat 安装启动
二、jenkins war 包下载并安装
三、jenkins 配置教程
四、jenkins items 工程配置
五、android gradle 脚本编码
六、分发到蒲公英脚本编码以及通知钉钉逻辑编码

前言

Android 在每个版本测试阶段,通常会因为修复BUG 去验证,都会打出大量的apk,为了方便开发人员和测试人员,就需要我们把打包权利交于测试人员,让他们拥有打包->下载安装一条龙。这样不仅仅方便了测试和开发,在版本流程上也会规范一下,还起到了版本归档的作用。下面就是从tomcat 到自动发布蒲公英以及通知钉钉群(也有人用飞书)的流程。

一、tomcat 安装启动

  • 1、tomcat 下载

tomcat 下载地址

下载后配置环境变量
在这里插入图片描述

修改端口号路径:

F:\Program Files\Apache Software Foundation\Tomcat 9.0\conf\server.xml

<Connector executor="tomcatThreadPool"
               port="9090" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->

验证tomcat

cd 进入F:\Program Files\Apache Software Foundation\Tomcat 9.0\bin\startup.bat
.
启动startup.bat,然后浏览器输入localhost:9090

在这里插入图片描述

二、jenkins war 包下载并安装

jenkins 下载地址
在这里插入图片描述
这里可以下载jenkins任意版本,建议下载最新的。

在这里插入图片描述
点击jenkins.war 下载到本地。
将jenkins.war 文件放到一下tomcat 目录下:

F:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps…

启动tomcat 并在浏览器输入:
http://localhost:9090/jenkins

在这里插入图片描述
密码在对应的/home/tomcat/.jenkins/secrets/initialAdminPassword 目录下

在这里插入图片描述
进来后选择第一个安装推荐的插件

在这里插入图片描述

以上就是jenkins 安装的过程。

三、jenkins 配置教程

  • 1、设置ANDROID_HOME 环境变量(要和本身的安卓环境变量地址一样)

  • 2.、jdk 环境变量配置(jdk 环境变量要和Android Studio 中的一致,否则编译会失败)

  • 3、git 配置

  • 4、gradle 配置

  • step1:设置ANDROID_HOME 环境变量(要和本身的安卓环境变量地址一样)
    在这里插入图片描述
    点击Manage Jenkins 进入设置列表,并进入系统设置

在这里插入图片描述
设置ANDROID_HOME 环境变量

  • step2:、jdk 环境变量配置(jdk 环境变量要和Android Studio 中的一致,否则编译会失败)

进入全局设置

在这里插入图片描述
查看自己Android Studio 中jdk 的路径。
在这里插入图片描述

  • step3:、git 配置
    在这里插入图片描述

  • step4:、gradle 配置(要和Android 项目中 gradle 版本一致)
    在这里插入图片描述
    以上的配置就可以。

四、jenkins items 工程配置

  • step1:新建一个新的任务Item 步骤如下
    在这里插入图片描述

  • step2:配置
    在这里插入图片描述

任务配置会分为以下6点
在这里插入图片描述

  • 1、General(常规设置)

  • 2.、源码管理

  • 3、构建触发器

  • 4、构建环境

  • 5、构建步骤

  • 6、构建后操作
    对于android 自动化分发,只需要配置 1、2、5 就够用了。

  • step 1、General(常规设置)
    在这里插入图片描述
    设置一下参数

在这里插入图片描述

这里可以根据自己的需求选择其中一种参数配置。博主大部分使用Choice Parameter,主要是为了做一些标识和渠道分发的配置。

在这里插入图片描述

  • step 2、源码管理

设置git 仓库路径,并添加秘钥管理。
在这里插入图片描述

  • 5、构建步骤
    在这里插入图片描述

Gradle 选择之前系统设置中配置好的。
而Task 名称是Android build.gradle 写好的某个任务,下面会有说到。
这里有一个配置非常重要,就是Jenkins 透传(意思是,Jenkins 选中的参数,可以透传到Android 项目中使用)
点开高级设置如下:
在这里插入图片描述
设置好上面后,点击保存
在这里插入图片描述
至此Jenkins 的配置已经全部结束,下面回到android 的脚本配置

五、android gradle 脚本编码

  • 2.透传设置(重要)
    在项目gradle.properties 文件中配置之前在jenkins 设置的透传参数
# jenkins 透传
PRODUCT_FLAVORS=Quest_Self_all_21100_26
BUILD_TYPE=Debug
TY_URL="https://ecx.ctyun.cn"
  • 2.productFlavors 多渠道配置
    下面是我的三个渠道

    flavorDimensions("app", "manufacturer", "version")
    productFlavors {
    
    
        _all {
    
    
            dimension "manufacturer"
        }
        quest_Self {
    
    
            dimension "app"
            applicationId "com.xxx"
            versionCode getVersionCode()
            resValue "string", "app_use_name", appName(true)
            manifestPlaceholders = [app_icon: "@mipmap/ic_launcher"]
            buildConfigField "int", "customType", "0"
        }

        quest_TY {
    
    
            dimension "app"
            applicationId "com.xxx"
            versionCode getVersionCode()
            resValue "string", "app_use_name", appName(false)
            manifestPlaceholders = [app_icon: "@mipmap/icon_luancher"]
            buildConfigField "int", "customType", "2"
        }
        quest_HW {
    
    
            dimension "app"
            applicationId "com.xxx"
            versionCode getVersionCode()
            resValue "string", "app_use_name", appName(true)
            manifestPlaceholders = [app_icon: "@mipmap/ic_launcher"]
            buildConfigField "int", "customType", "3"
        }
        _21100_26 {
    
    
            targetSdkVersion 31
            versionName getVersionName()
            dimension "version"
        }

    }

  • **3.task packageApk 打包设置 **
    task packageApk {
    
    
        println("BUILD_TYPE:" + BUILD_TYPE)
        File apkFileDir = null
        //这一步就是根据透传的渠道名拼接上打包类型(debug\release)组成打包脚本,这里其实就已经完成打包了
        dependsOn("assemble" + PRODUCT_FLAVORS.capitalize() + BUILD_TYPE.capitalize())
        //下面就是涉及上传到蒲公英了
        //因为透传的参数刚好和分发的名字一样,因为apk 存放的路径是小写,所以转换一下大小写(大写是因为task会自动将首位字母转成大写)
        def filePath = PRODUCT_FLAVORS.capitalize().replace("Q", "q")
        if (BUILD_TYPE.toLowerCase() == "release") {
    
    
            apkFileDir = new File(project.buildDir, "outputs/apk/" + filePath + "/release")
        } else {
    
    
            apkFileDir = new File(project.buildDir, "outputs/apk/" + filePath + "/debug")
        }
        println("uploadFile" + apkFileDir.path)
        doLast {
    
    
            rootProject.ext.copyApk(apkFileDir)
        }
    }

六、分发到蒲公英脚本编码以及通知钉钉逻辑编码

新建一个gradle 文本取名upload.gradle

在这里插入图片描述

import groovy.json.JsonSlurper

import java.text.SimpleDateFormat

ext.copyApk = {
    
     file ->
    def mFile = findApkFile(file)
    uploadApk(mFile)
}


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

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");//替换成蒲公英上申请的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();
                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("替换成钉钉Webhook地址").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 = "### ${PRODUCT_FLAVORS}构建成功ljl" //这句话非常重要,需要将钉钉自定义关键词拼接起来,要识别。
    def _content = new StringBuffer()
    _content.append("\n\n### 构建成功")
//    _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("*************** 钉钉消息已发送 ***************")
}

//获取当前时间
static 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

}

钉钉webhook地址获取如下:
新建一个群,

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上就是整个流程。谢谢支持!有问题可以留言或者私信

猜你喜欢

转载自blog.csdn.net/Android_LeeJiaLun/article/details/127239497
今日推荐