【Gradle Task】一條龍發(fā)包(打包锈玉、加固爪飘、對(duì)齊簽名、多渠道)

加固使用的是樂(lè)固api拉背;多渠道為walle方案
腳本中使用了fir上傳师崎,以拿到apk的url給樂(lè)固使用
zipalign.exe與apksigner從sdk目錄/build-tools/xx.x.x(注意使用28.x的,支持v2+v1同時(shí)簽名椅棺;28+的增加了v3犁罩,暫未測(cè)試)下取得

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"       : "樂(lè)固id",
        "SecretKey"      : "樂(lè)固key",

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

        //本地APK的路徑, 用來(lái)計(jì)算MD5值
        "apkPath"        : "./app/build/outputs/apk/release/app_" + project.android.defaultConfig.versionName + ".apk",
        //加固后, 下載保存到本地路徑
        "downloadApkPath": "./channel/app_" + project.android.defaultConfig.versionName,

        //每隔多少秒, 查詢一次加固結(jié)果
        "pollTime"       : "10",

        //加固后, 返回的ItemId, 用來(lái)輪詢結(jié)果
        "ItemId"         : "",

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

/**
 * task.execute()方法報(bào)錯(cuò)齐蔽,所以暫時(shí)用dependsOn反向依賴,實(shí)際執(zhí)行順序依次為assembleRelease昼汗、uploadAPKtoFir肴熏、_leguJiaGu、_leguGetResult顷窒、multiAPK
 * 此文件大量報(bào)紅正常蛙吏,不影響執(zhí)行
 */

//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上傳憑證的各個(gè)字段
        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解析對(duì)象拿到的是Map, 集合對(duì)應(yīng)的是array, 按照這個(gè)規(guī)則取出我們需要的數(shù)據(jù)
        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"]

        //執(zhí)行上傳命令 注意路徑不能包含中文、空格
        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給樂(lè)固上傳用
        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任務(wù)狀態(tài): 1-已完成,2-處理中,3-處理出錯(cuò),4-處理超時(shí)
        println "加固處理中:" + DescribeShieldInstancesRequest.toJsonString(resp)
    }.dependsOn("uploadAPKtoFir")
}

//3.查詢加固結(jié)果
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后, 查詢加固狀態(tài):" + 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任務(wù)狀態(tài): 1-已完成,2-處理中,3-處理出錯(cuò),4-處理超時(shí)
            println DescribeShieldResultRequest.toJsonString(resp)
        }
    }.dependsOn("_leguJiaGu")
    doLast {
        println "加固結(jié)束"

        //zipalign
        println "開始zipalign對(duì)齊"
        def alignResult = ("./legu/zipalign -f -v 4 " + leguConfig.downloadApkPath + ".apk " + leguConfig.downloadApkPath + "_aligned.apk").execute().text
        println alignResult
        println "zipalign對(duì)齊完成"

        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.打多渠道包 執(zhí)行此task 自動(dòng)執(zhí)行 打包->上傳fir(拿到url給樂(lè)固用)->加固->對(duì)齊鞋吉、簽名->多渠道
task multiAPK(dependsOn: [_leguGetResult]) {
    doLast {
        println "生成渠道包..."
        def multiResult = ("java -jar ./channel/walle-cli-all.jar batch -f " +
                //渠道文件
                "./channel/channels.txt " +
                //源apk(樂(lè)固加固后的包)
                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進(jìn)制數(shù)字
    // 如果生成數(shù)字未滿32位,需要前面補(bǔ)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)
//    }
//}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谓着,一起剝皮案震驚了整個(gè)濱河市泼诱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赊锚,老刑警劉巖治筒,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異舷蒲,居然都是意外死亡耸袜,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門牲平,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)堤框,“玉大人,你說(shuō)我怎么就攤上這事纵柿◎谧ィ” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵昂儒,是天一觀的道長(zhǎng)沟使。 經(jīng)常有香客問(wèn)我,道長(zhǎng)渊跋,這世上最難降的妖魔是什么格带? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮刹枉,結(jié)果婚禮上叽唱,老公的妹妹穿的比我還像新娘。我一直安慰自己微宝,他們只是感情好棺亭,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蟋软,像睡著了一般镶摘。 火紅的嫁衣襯著肌膚如雪嗽桩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天凄敢,我揣著相機(jī)與錄音碌冶,去河邊找鬼。 笑死涝缝,一個(gè)胖子當(dāng)著我的面吹牛扑庞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拒逮,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼罐氨,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了滩援?” 一聲冷哼從身側(cè)響起栅隐,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玩徊,沒(méi)想到半個(gè)月后租悄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恩袱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年泣棋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憎蛤。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纪吮,靈堂內(nèi)的尸體忽然破棺而出俩檬,到底是詐尸還是另有隱情,我是刑警寧澤碾盟,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布棚辽,位于F島的核電站,受9級(jí)特大地震影響冰肴,放射性物質(zhì)發(fā)生泄漏屈藐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一熙尉、第九天 我趴在偏房一處隱蔽的房頂上張望联逻。 院中可真熱鬧,春花似錦检痰、人聲如沸包归。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)公壤。三九已至换可,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厦幅,已是汗流浹背沾鳄。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留确憨,地道東北人译荞。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缚态,于是被迫代替她去往敵國(guó)和親磁椒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360