【Gradle Task】一条龙发包(打包、加固、对齐签名、多渠道)

加固使用的是乐固api;多渠道为walle方案
脚本中使用了fir上传,以拿到apk的url给乐固使用
zipalign.exe与apksigner从sdk目录/build-tools/xx.x.x(注意使用28.x的,支持v2+v1同时签名;28+的增加了v3,暂未测试)下取得

import com.tencentcloudapi.common.*
import com.tencentcloudapi.common.exception.*
import com.tencentcloudapi.common.profile.*
import com.tencentcloudapi.ms.v20180408.*
import com.tencentcloudapi.ms.v20180408.models.*
import okhttp3.OkHttpClient
import okhttp3.Request

import java.security.MessageDigest
import java.util.concurrent.TimeUnit

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.squareup.okhttp3:okhttp:4.4.0"
        classpath "com.google.code.gson:gson:2.8.6"
        classpath "com.tencentcloudapi:tencentcloud-sdk-java:3.0.93"
    }
}

ext.leguConfig = [
        "SecretId"       : "乐固id",
        "SecretKey"      : "乐固key",

        //需要加固的APK url地址
        "apkUrl"         : "",

        //本地APK的路径, 用来计算MD5值
        "apkPath"        : "./app/build/outputs/apk/release/app_" + project.android.defaultConfig.versionName + ".apk",
        //加固后, 下载保存到本地路径
        "downloadApkPath": "./channel/app_" + project.android.defaultConfig.versionName,

        //每隔多少秒, 查询一次加固结果
        "pollTime"       : "10",

        //加固后, 返回的ItemId, 用来轮询结果
        "ItemId"         : "",

        //加固成功的下载地址
        "downloadUrl"    : ""
]

/**
 * task.execute()方法报错,所以暂时用dependsOn反向依赖,实际执行顺序依次为assembleRelease、uploadAPKtoFir、_leguJiaGu、_leguGetResult、multiAPK
 * 此文件大量报红正常,不影响执行
 */

//1.fir上传 拿到rul
task uploadAPKtoFir() {
    def fir_api_token = "fir-api-token"
    doFirst {
        //清空旧文件
        File outputDir = new File(rootDir.getAbsolutePath() + '\\channel\\output');
        outputDir.deleteDir();
        //清理旧的apk
        FileTree tree = fileTree(rootDir.getAbsolutePath() + "\\channel")
        tree.each { File file ->
            if (file.toString().endsWith(".apk")) {
                file.println()
                delete file
            }
        }

        println "即将上传到fir..."

        //获取fir上传凭证的各个字段
        def appInfo = ("curl -X POST -d type=android&" +
                "bundle_id=$project.android.defaultConfig.applicationId&" +
                "api_token=$fir_api_token " +
                "http://api.bq04.com/apps").execute().text

        //json解析对象拿到的是Map, 集合对应的是array, 按照这个规则取出我们需要的数据
        def appInfoBean = new groovy.json.JsonSlurper().parseText(appInfo)
        def key = appInfoBean["cert"]["binary"]["key"]
        def url = appInfoBean["cert"]["binary"]["upload_url"]
        def token = appInfoBean["cert"]["binary"]["token"]

        //执行上传命令 注意路径不能包含中文、空格
        def apkFile = project.android.applicationVariants[1].outputs.first().outputFile
        println "apk路径:" + apkFile
        def result = ("curl -X POST --form file=@$apkFile" +
                " -F token=$token" +
                " -F key=$key" +
                " -F x:version=$project.android.defaultConfig.versionName" +
                " -F x:build=$project.android.defaultConfig.versionCode" +
                " $url").execute().text
        //赋值apkUrl给乐固上传用
        leguConfig.apkUrl = new groovy.json.JsonSlurper().parseText(result)["download_url"]
        println "上传完成"
    }.dependsOn("assembleRelease")
}

//2.提交加固
task _leguJiaGu() {
    doFirst {
        Credential cred = new Credential(leguConfig.SecretId, leguConfig.SecretKey)
        HttpProfile httpProfile = new HttpProfile()
        httpProfile.setEndpoint("ms.tencentcloudapi.com")

        ClientProfile clientProfile = new ClientProfile()
        clientProfile.setHttpProfile(httpProfile)

        MsClient client = new MsClient(cred, "", clientProfile)

        def req = new CreateShieldInstanceRequest()
        def appInfo = new com.tencentcloudapi.ms.v20180408.models.AppInfo()
        appInfo.AppUrl = leguConfig.apkUrl
        appInfo.AppMd5 = getFileMd5(leguConfig.apkPath)

        println "apk路径:" + leguConfig.apkPath
        println "apkMD5:" + appInfo.AppMd5

        def serviceInfo = new com.tencentcloudapi.ms.v20180408.models.ServiceInfo()
        serviceInfo.ServiceEdition = "basic"
        serviceInfo.SubmitSource = "RDM-rdm"
        serviceInfo.CallbackUrl = ""

        req.AppInfo = appInfo
        req.ServiceInfo = serviceInfo

        CreateShieldInstanceResponse resp = client.CreateShieldInstance(req)

        leguConfig.ItemId = resp.ItemId
        //Progress任务状态: 1-已完成,2-处理中,3-处理出错,4-处理超时
        println "加固处理中:" + DescribeShieldInstancesRequest.toJsonString(resp)
    }.dependsOn("uploadAPKtoFir")
}

//3.查询加固结果
task _leguGetResult() {
    doFirst {
        def resp
        def TaskStatus = 2
        def count = 1;
        while (TaskStatus == 2) {
            println ""

            def cred = new Credential(leguConfig.SecretId, leguConfig.SecretKey)
            def httpProfile = new HttpProfile()
            httpProfile.setEndpoint("ms.tencentcloudapi.com")

            def clientProfile = new ClientProfile()
            clientProfile.setHttpProfile(httpProfile)

            def client = new MsClient(cred, "", clientProfile)

            def params = "{\"ItemId\":\"" + leguConfig.ItemId + "\"}"
            def req = DescribeShieldResultRequest.fromJsonString(params, DescribeShieldResultRequest.class)

//            println leguConfig.pollTime + "s后, 查询加固状态:" + params
            println "加固中...第" + count + "次查询"
            Thread.sleep(Integer.parseInt(leguConfig.pollTime) * 1000L)
            resp = client.DescribeShieldResult(req)

            TaskStatus = resp.TaskStatus

            leguConfig.downloadUrl = resp.ShieldInfo.AppUrl

            count++
        }

        if (TaskStatus == 1) {
            println "加固成功下载地址:" + leguConfig.downloadUrl

            println "开始下载->" + file(leguConfig.downloadApkPath + ".apk").getAbsolutePath()
            downloadFile(leguConfig.downloadUrl, leguConfig.downloadApkPath + ".apk")
        } else {
            println "加固失败"
            //TaskStatus任务状态: 1-已完成,2-处理中,3-处理出错,4-处理超时
            println DescribeShieldResultRequest.toJsonString(resp)
        }
    }.dependsOn("_leguJiaGu")
    doLast {
        println "加固结束"

        //zipalign
        println "开始zipalign对齐"
        def alignResult = ("./legu/zipalign -f -v 4 " + leguConfig.downloadApkPath + ".apk " + leguConfig.downloadApkPath + "_aligned.apk").execute().text
        println alignResult
        println "zipalign对齐完成"

        println "开始签名..."
        //sign
        def signResult = ("java -jar ./legu/apksigner.jar sign --ks " +
                "./demo.jks " +
                "--ks-key-alias demo" +
                "--ks-pass pass:demo " +
                "--ks-pass pass:demo --out " +
                leguConfig.downloadApkPath + "_aligned_signed.apk " +
                leguConfig.downloadApkPath + "_aligned.apk").execute().text
        println signResult
        println "签名完成"

        //删除中间文件 并重命名最终apk 去掉后缀
        new File(leguConfig.downloadApkPath + ".apk").delete()
        new File(leguConfig.downloadApkPath + "_aligned.apk").delete()
        new File(leguConfig.downloadApkPath + "_aligned_signed.apk").renameTo(new File(leguConfig.downloadApkPath + ".apk"))
        new File(leguConfig.downloadApkPath + "_aligned_signed.apk").delete()
    }
}

//4.打多渠道包 执行此task 自动执行 打包->上传fir(拿到url给乐固用)->加固->对齐、签名->多渠道
task multiAPK(dependsOn: [_leguGetResult]) {
    doLast {
        println "生成渠道包..."
        def multiResult = ("java -jar ./channel/walle-cli-all.jar batch -f " +
                //渠道文件
                "./channel/channels.txt " +
                //源apk(乐固加固后的包)
                leguConfig.downloadApkPath + ".apk " +
                //输出目录
                "./channel/output").execute().text
        println multiResult
        println "打包完成,输出目录:" + project.getRootDir() + "\\channel\\output"
    }
}

static def getFileMd5(filePath) {
    def FILE_READ_BUFFER_SIZE = 16 * 1024
    MessageDigest digester = MessageDigest.getInstance("MD5")
    def stream = new FileInputStream(filePath)
    int bytesRead
    byte[] buf = new byte[FILE_READ_BUFFER_SIZE]
    while ((bytesRead = stream.read(buf)) >= 0) {
        digester.update(buf, 0, bytesRead)
    }
    def md5code = new BigInteger(1, digester.digest()).toString(16)// 16进制数字
    // 如果生成数字未满32位,需要前面补0
    for (int i = 0; i < 32 - md5code.length(); i++) {
        md5code = "0" + md5code
    }
    return md5code

}

//下载加固后的文件
static def downloadFile(url, filePath) {
    def clientBuilder = new OkHttpClient.Builder()
    clientBuilder.connectTimeout(10, TimeUnit.SECONDS)
    clientBuilder.readTimeout(60, TimeUnit.SECONDS)

    OkHttpClient client = clientBuilder.build()

    def request = new Request.Builder()
            .url(url)
            .get()
            .build()

    def response = client.newCall(request).execute()

    def write = new BufferedOutputStream(new FileOutputStream(filePath, false))
    def read = new BufferedInputStream(response.body().byteStream())

    def bytes = new byte[1024]
    def bytesRead = 0
    while ((bytesRead = read.read(bytes)) != -1) {
        write.write(bytes, 0, bytesRead)
    }
    read.close()
    write.flush()
    write.close()
}

//assembleRelease注入
//gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
////    println('taskGraph.afterTask')
//    taskGraph.getAllTasks().each { Task task ->
//        if (task.name.startsWith('assemble') && task.name.endsWith('Release')) {
//            task.doLast {
//                File apkFile = findNewestApk()
//                File leguFile = reinforce(apkFile)
//                File zipFile = zipalignApk(leguFile)
//                File signedApkFile = signApk(zipFile)
//                String oldFileName = leguFile.getPath()
//                apkFile.delete()
//                leguFile.delete()
//                zipFile.delete()
//                signedApkFile.renameTo(new File(oldFileName.replace("_zip_sgined", "")))
//            }
//        }
////        println('>>>>==' + task.name)
//    }
//}

你可能感兴趣的:(【Gradle Task】一条龙发包(打包、加固、对齐签名、多渠道))