Android——Gradle自动打包上传蒲公英并进行钉钉群提醒

开发测试阶段频繁打包未免太过枯燥,自动打包发布解放双手。上一节我们分析了fastlane打包发布的过程,嗯~总体来说有点繁琐,而且Android使用fast lane自动打包网上资料比较少,出错之后解决难度颇大。所以今天我们来用一种更简便的方式进行自动打包发布。

1、Gradle自动打包发布蒲公英

蒲公英上传Apk官方文档

apply from: './dingding.gradle'//引用钉钉通知方法

def apkFileName = "${buildTime()}_test_v${rootProject.ext.android["versionName"]}_release.apk"
def apkFilePath = "${projectDir.absolutePath}/build/outputs/apk/test/release/${apkFileName}"

private def uploadPGY(String filePath) {
    def stdout = new ByteArrayOutputStream()
    exec {
        executable = 'curl'
        args = ['-F', "file=@${filePath}", '-F', "_api_key=${rootProject.ext.pgy["apiKey"]}", rootProject.ext.pgy["uploadUrl"]]
        standardOutput = stdout
    }
    String output = stdout.toString()
    def parsedJson = new groovy.json.JsonSlurper().parseText(output)
    println parsedJson.data.buildQRCodeURL
    println "蒲公英上传完成 版本号:" + parsedJson.data.buildVersion

    println "开始发送钉钉群通知"
    def downloadUrl = "https://www.pgyer.com/" + parsedJson.data.buildShortcutUrl
    postDingMsg(parsedJson.data.buildQRCodeURL, downloadUrl)
}

task uploadApk(dependsOn: '打包Task名称') {//可以从Gradle的Task列表中选取,也可以点击查看Generate Signed Bundle/Apk执行的task是什么,复制出来就好了
    group = "publish"//GradleTask列表中会生成一个publish的分组,里面包含uploadApk,后续只要点击这个uploadTask任务就可以自动打包发布了
    doLast {
        println "打包完成: ${apkFilePath}"
        println "开始上传蒲公英"
        uploadPGY(apkFilePath)
    }
}

注:记得在app的build.gradle里面引入这个文件。

2、钉钉群通知

自定义钉钉机器人文档

1、创建自定义钉钉机器人

点击群组右上角设置按钮打开群设置->智能群助手->添加机器人->自定义机器人->填写机器人名称和关键词(这里的关键词后面发送钉钉群组消息的时候消息内容必须包含该关键词,否则发送不成功)
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

2、接入钉钉群通知
import groovy.json.JsonBuilder
import groovy.json.JsonOutput

def dingDingUrl = "https://oapi.dingtalk.com/robot/send?access_token=${rootProject.ext.dingding["accessToken"]}"

ext.postDingMsg = { String qrcodeUrl, String downloadUrl ->
    String allTips =
            "版本号:V${rootProject.ext.android["versionName"]}\n" +
            "下载地址:${downloadUrl}\n" 

    postAll(dingDingUrl, allTips, qrcodeUrl, downloadUrl)
}

//发送到群,@所有人
def postAll(url, tips, qrcodeUrl, downloadUrl) {
    JsonBuilder builder = new JsonBuilder()
    builder {
        msgtype 'link'
        link {
            title '新版本发布提醒'
            text tips
            messageUrl downloadUrl
            picUrl qrcodeUrl
        }
        at {
            isAtAll false   //@所有人(只有text,markdown,actioncard这三种消息类型支持@功能)
        }
    }
    String data = JsonOutput.prettyPrint(builder.toString())
    postDingDing(url, data)
}

//调用接口,发送消息
def postDingDing(urlString, msg) {
    if (msg == null) {
        return
    }
    HttpURLConnection conn = null
    OutputStream outputStream = null
    try {
        if (conn == null) {
            URL url = new URL(urlString)
            conn = (HttpURLConnection) url.openConnection()
        }
        if (conn != null) {
            conn.setReadTimeout(15000)
            conn.setConnectTimeout(15000)
            conn.setDoOutput(true)
            conn.setUseCaches(false)
            conn.setRequestProperty("Content-Type", "application/json; charset=utf-8")
        }

        if (conn == null) {
            return null
        }
        if (msg != null && msg.length() > 0) {
            DataOutputStream dataOutputStream = new DataOutputStream(conn.getOutputStream())
            byte[] t = msg.getBytes("utf-8")
            dataOutputStream.write(t)
            dataOutputStream.flush()
            dataOutputStream.close()

            int res = conn.getResponseCode()
            if (res == 200) {  //成功
                InputStream input = conn.getInputStream()
                StringBuffer sb = new StringBuffer()
                int ss
                while ((ss = input.read()) != -1) {
                    sb.append((char) ss)
                }
                println "发送消息成功: ${sb.toString()}"
            } else {
                println("发送消息失败: " + conn.getResponseCode())
            }
        }
    } catch (EOFException e) {
        e.printStackTrace()
    } catch (IOException e) {
        e.printStackTrace()
    } finally {
        if (outputStream != null) {
            try {
                outputStream.close()
            } catch (IOException e) {
                e.printStackTrace()
            }
        }
        if (conn != null) {
            conn.disconnect()
        }
    }
}

找到刚才新建的机器人并打开
在这里插入图片描述
复制Webhook地址可以看到accessToken字段。下面我们来把需要用的参数配置一下。
群组消息类型可以参考官方文档根据自己的需求调整下。代码中默认为link类型。

3、相关配置

打开根目录的build.gradle并填写一下代码:

ext {
    android = [
            versionCode: 1,
            versionName: "1.0.0.0"
    ]

    pgy = [
            apiKey     : "xxx",
            uploadUrl  : "https://www.pgyer.com/apiv2/app/upload"
    ]

    dingding = [
            accessToken: "xxx",
    ]
}

4、效果图:

在这里插入图片描述

5、SSL Bug修改

最近给电脑(windows)上装了OpenSSL,自动上传调用cur命令出现报错。

curl: (35) schannel: next InitializeSecurityContext failed: Unknown error (0x80092013)

问题原因:

windows版本的curl依靠WinSSL / WinTLS (也就是微软的 Secure Channel,见 https://msdn.microsoft.com/en-us/library/aa380123.aspx )来提供 https 支持,而Linux版本的curl依靠的是openssl 的版本,两者表现并不一致。

由于window本来内置了ssh,这次我又安装了OpenSSL猜测两者出现了冲突。

解决办法:

给curl添加--ssl-no-revoke参数,关闭证书吊销检查功能,修改后的curl上传代码如下:

private def uploadPGY(String filePath) {
    def stdout = new ByteArrayOutputStream()
    exec {
        executable = 'curl'
        args = ['--ssl-no-revoke','-F', "file=@${filePath}", '-F', "_api_key=${rootProject.ext.pgy["apiKey"]}", rootProject.ext.pgy["uploadUrl"]]
        standardOutput = stdout
    }
    String output = stdout.toString()
    def parsedJson = new groovy.json.JsonSlurper().parseText(output)
    println parsedJson.data.buildQRCodeURL
    println "蒲公英上传完成 版本号:" + parsedJson.data.buildVersion
}

6、钉钉消息@某人

官方文档:https://open.dingtalk.com/document/group/robot-message-type-staff-information-in-an-enterprise
支持钉钉消息@某人的消息格式有三种:textmarkdownactioncard。其中只有text会高亮,其他两种不会。

//发送到群,@所有人
def postAtSomeone(url, tips, qrcodeUrl, downloadUrl) {
    //发送@某人消息
    JsonBuilder atPersonBuilder = new JsonBuilder()
    atPersonBuilder {
        msgtype 'text'
        text {
            content "更新内容:\n${rootProject.ext.android["versionUpdateRemark"]}\n" +
                    "@zfndm1v81"
        }
        at {
            isAtAll false   //@所有人
            atDingtalkIds (["zfndm1v81"])//打开钉钉群,找到要@的人,复制他的钉钉号即可
            //atMobiles (["135********"])
        }
    }
    String personData = JsonOutput.prettyPrint(atPersonBuilder.toString())
    postDingDing(url, personData)
}

注意:
1、官方文档中@某人有两种方式,一是配置这个人的钉钉userid,另一个是配置这个人的手机号,前提要保证配置的userid或者手机号一定在这个群里,否则无效。
2、官方文档中的用户id的配置方式为atUserIds,经测试发现这个配置项无效,需改为atDingtalkIds 可能文档忘了更新吧。
3、在at中配置需要@人的信息之后,需要在发送的消息中加上@xx,否则@无效。

  • text消息:在content中拼接@xx
  • markdown消息|actionCard消息:在text中拼接@xx

4、atDingtalkIdsatMobiles 可以同时配置。
5、注意gradle中的数组配置方式([])。groovy的语法我也不是很懂,想详细了解的同学自行百度。