Android一键打包上传蒲公英并发送消息到企业微信

实现一键打包上传蒲公英并发送消息到企业微信,涉及到的点就是使用HttpURLConnection来进行数据的传输,两个gradle中方法的调用,以及企业微信api和蒲公英上传api的调用:

1.上传蒲公英的文件

import groovy.json.JsonSlurper
//需要通过这种方式才能去调用给到另外一个gradle中的方法,调用的也不是直接去调用给,也是通过ext中的方法函数调用
apply([plugin: 'com.android.application', from: project.uri(file("../packapk/send_message.gradle"))])

afterEvaluate {
    android.applicationVariants.each { variant ->
        String variantName = variant.name.capitalize()
        def task = tasks.create("apkUploadPGY${variantName}")
        task.group = "apk_upload"
        //依赖微信混淆打包
        task.dependsOn("resguard${variantName}")
        task.doLast {
            def currentBuildType = variant.buildType
            def changeLogPath = new File(project.rootDir, 'packapk/apk_changelog').path
            def apkFileDir = new File(
                    project.buildDir,
                    "outputs/apk/${variant.flavorName}/${variant.buildType.name}")
            def uploadFile = findApkFile(apkFileDir)
            if (uploadFile == null || !uploadFile.exists()) {
                throw new GradleException("Could not find apk!")
            }
            def appName = currentBuildType.getManifestPlaceholders()["APP_NAME"]
            def appUploadLog = currentBuildType.getManifestPlaceholders()["APK_UPLOAD_LOG"]
            def pgyToken = currentBuildType.getManifestPlaceholders()["PGY_TOKEN"]
            def wxPushToke = project.QYWX_ROBOT_KEY
            def versionCode = project.android.defaultConfig.versionName
            println("*********** start: uploadFile:${uploadFile.path} ******")
            def uploadApkUrl = uploadApk(uploadFile, pgyToken, appName + appUploadLog)
            if (uploadApkUrl != null) {
                project.ext.fun.call(wxPushToke, changeLogPath, appName, versionCode, appUploadLog, uploadApkUrl)
            }
            println("*********** end: 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
    }
}*/

/**
 * 查找apk文件,上面的方法因为我们项目中使用了资源混淆的处理,会导致在内层还有一个apk文件,所以这里直接找第一层的,根据需要修改使用方法即可
 * @param apkFile
 * @return
 */
static def findApkFile(File apkFile) {
    def files = apkFile.listFiles()
    for (int i = 0; i < files.length; i++) {
        if (files[i].name.endsWith(".apk")) {
            return files[i]
        }
    }
    return null
}

def uploadApk(File uploadApkFile, String apiKey, String titleName) {
    // 查找上传的 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);//换行!
        //添加备注说明更新
        sb.append(PREFIX).append(BOUNDARY).append(LINE_END);//分界符
        sb.append("Content-Disposition: form-data; name=\"" + "buildUpdateDescription" + "\"" + 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(titleName);//替换成你再蒲公英上申请的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 "------url:https://www.pgyer.com/${resp.data.buildShortcutUrl}"
                println "*************** upload finish ***************"
                return "https://www.pgyer.com/${resp.data.buildShortcutUrl}"
            } else {
                println "Upload request fail"
            }
        }
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null
}

2.企业微信消息的发送

这里需要在对应的群创建一个机器人,然后通过这个机器人进行消息的发送
其中的apk_chanagelog为一个自定义的文件,记录了当前版本更新的内容,这个内容后续会读取到企业微信发送消息中,告知相应的人员修改的内容:
seng_message.gradle:

import groovy.json.JsonBuilder

def sendMessage={
    wxPushToken,changeLogPath, appName,versionCode, uploadType, pgyUrl->
        //消息组成,Android+项目名称 +环境:url
        //更新日志
        if(wxPushToken == null){
            println("企业微信推送的token为空")
            return
        }
        def change_log = ""
        File file = new File(changeLogPath)
        def result = new StringBuilder()
        if (file.exists()) {
            try {
                //按行读取changelog的内容进行拼接
                BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))
                def lineContent
                while ((lineContent = br.readLine()) != null) {
                    result.append(System.lineSeparator() + lineContent)
                }
                br.close()
                change_log = result.toString()
            } catch (Exception e) {
                e.printStackTrace()
            }
        }
        URL url = new URL("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${wxPushToken}");
        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("Content-Type", "application/json;charset=utf-8");
        conn.connect();
        //groovy的语法进行Json相关的操作,这里是构建json字符串
        def json = new JsonBuilder()
        json {
            msgtype "text"
            text {
                content "${appName}-Android${versionCode}${uploadType}:\n${pgyUrl}\n${change_log}"
            }
        }
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), "UTF-8"));
        println(json.toString())
        writer.write(json.toString());
        writer.flush();
        int res = conn.getResponseCode();
        if (res == 200) {
            println("消息发送成功");
        } else {
            println ("消息发送失败")
        }
}
//创建一个ext的函数方法对象提供给外部使用
ext{
    fun = sendMessage
}

3.参数配置

参数的配置,其中使用到的参数包括PGY_TOKEN(蒲公英的上传token),APK_UPLOAD_LOG(版本的区分标示),APP_NAME(应用名称)

buildTypes{
    release{
        manifestPlaceholders=[
            PGY_TOKEN : "${project.PGY_APPID_RELEASE}",
            APK_UPLOAD_LOG : "生产",
            APP_NAME:"APP",
        ]
    }
     debug{
        manifestPlaceholders=[
            PGY_TOKEN : "${project.PGY_APPID_RELEASE}",
            APK_UPLOAD_LOG : "测试",
            APP_NAME:"APP",
        ]
    }
}

在项目的gradle.properties中的配置:

######上传蒲公英的TOKEN
#蒲公英测试环境的token
PGY_APPID_TEST=
#蒲公英正式环境的token
PGY_APPID_RELEASE=

######微信机器人消息发送
QYWX_ROBOT_KEY = 

消息拼接格式:
"appNameAndroid{appName}-Android{versionCode}{uploadType}:\n{pgyUrl}\n${change_log}"
名称-Android版本类别:
蒲公英的路径
更新日志内容