美文网首页工具类Android进阶之路Android开发
【Gradle Task开发】一条龙发包(打包、加固、对齐签名、

【Gradle Task开发】一条龙发包(打包、加固、对齐签名、

作者: JeahWan | 来源:发表于2020-08-06 16:44 被阅读0次

    加固使用的是乐固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开发】一条龙发包(打包、加固、对齐签名、

        本文链接:https://www.haomeiwen.com/subject/tdctdktx.html