Gradle實(shí)現(xiàn)自動(dòng)化加固與多渠道打包

image

研究這個(gè)Gradle自動(dòng)化腳本初衷是為了實(shí)現(xiàn)自動(dòng)化打包、加固和增加多渠道风皿,從而一鍵完成應(yīng)用發(fā)布上架應(yīng)用市場(chǎng)前的所有操作河爹,以達(dá)到解放雙手和節(jié)約時(shí)間成本的效果。后期有考慮配合curl指令將打包好的apk自動(dòng)上傳到服務(wù)器或者托管平臺(tái)桐款,亦或可結(jié)合Jenkins自動(dòng)化構(gòu)建咸这、打包、上傳等魔眨,從而實(shí)現(xiàn)整個(gè)流程的自動(dòng)化目的媳维,不過(guò)目前最火的應(yīng)該是將GitLab Auto DevOps與Kubernetes集群配合使用,來(lái)實(shí)現(xiàn)持續(xù)化集成與自動(dòng)化部署遏暴,有興趣的可以自行去了解下侄刽。

App打包發(fā)布前準(zhǔn)備

通常我們App上架到應(yīng)用市場(chǎng)基本上都經(jīng)歷過(guò)以下流程,先本地打一個(gè)release包朋凉,然后通過(guò)在線加固或者下載加固工具進(jìn)行加固唠梨,由于加固會(huì)先剔除簽名信息,所以加固后要進(jìn)行再次簽名侥啤,然后生成多渠道包当叭,這樣基本上整個(gè)流程就結(jié)束了,畫(huà)了個(gè)思維導(dǎo)圖如下:


image

加固介紹

我的簡(jiǎn)單理解就是給原有的apk進(jìn)行加密和套殼盖灸,產(chǎn)生一個(gè)新的apk蚁鳖,然后運(yùn)行的時(shí)候會(huì)進(jìn)行解密相關(guān)的動(dòng)作,所以加固后的app一般會(huì)影響啟動(dòng)時(shí)間赁炎,網(wǎng)上也有很多加固平臺(tái)的對(duì)比醉箕,主要涉及啟動(dòng)時(shí)間钾腺、包體積大小、兼容性讥裤、安全性等等放棒。本次研究只是討論如何實(shí)現(xiàn)自動(dòng)化加固與多渠道打包思想,360加固并非最好選擇己英,加固主要是為了防止應(yīng)用在上線后被反編譯间螟、調(diào)試、破解损肛、二次打包和內(nèi)存截取等多種威脅厢破。

下載360加固保

本次Gradle自動(dòng)化實(shí)踐的步驟主要是基于360加固+騰訊的VasDolly多渠道打包。

  • 手動(dòng)下載
    官方主要提供Windows治拿、Mac摩泪、Linux三種版本,下載地址
  • 自動(dòng)下載
    如果是Mac系統(tǒng)的話劫谅,在Gradle中直接使用curl命令即可见坑,如果是Windwos需要下載安裝curl,curl主要是文件傳輸工具捏检,可以通過(guò)命令行可支持文件下載及傳輸荞驴。
/**
 * 自動(dòng)下載360加固保,也可以自己下載然后放到根目錄
 */
def download360jiagu() {
    // 下載360壓縮包
    File zipFile = file(packers["zipPath"])
    if (!zipFile.exists()) {
        if (!zipFile.parentFile.exists()) {
            zipFile.parentFile.mkdirs()
            println("packers===create parentFile jiagu ${zipFile.parentFile.absolutePath}")
        }
        // 加固保的下載地址
        def downloadUrl = isWindows() ? packers["jiagubao_windows"] : packers["jiagubao_mac"]
        // mac自帶curl命令 windows需要下載curl安裝
        def cmd = "curl -o ${packers["zipPath"]} ${downloadUrl}"
        println cmd
        cmd.execute().waitForProcessOutput(System.out, System.err)
    }
    File unzipFile = file(packers["unzipPath"])
    if (!unzipFile.exists()) {
        //解壓 Zip 文件
        ant.unzip(src: packers["zipPath"], dest: packers["unzipPath"], encoding: "GBK")
        println 'packers===unzip 360jiagu'
        //將解壓后的文件開(kāi)啟讀寫(xiě)權(quán)限未檩,防止執(zhí)行 Jar 文件沒(méi)有權(quán)限執(zhí)行戴尸,windows若沒(méi)有權(quán)限需要自己手動(dòng)改
        if (!isWindows()) {
            def cmd = "chmod -R 777 ${packers["unzipPath"]}"
            println cmd
            cmd.execute().waitForProcessOutput(System.out, System.err)
        }
    }
}

打一個(gè)release包

gradle其實(shí)為我們提供了一系列相關(guān)的任務(wù)粟焊,如下圖

image

我們執(zhí)行加固前是需要拿到一個(gè)release包的冤狡,所以我們可以利用assembleRelease在加固前先執(zhí)行assembleRelease這個(gè)Task。

task packersNewRelease {
    group 'packers'
    //可以利用task的依賴(lài)關(guān)系先執(zhí)行打包
    dependsOn 'assembleRelease'
    }

自動(dòng)執(zhí)行加固

所謂自動(dòng)執(zhí)行加固项棠,無(wú)非就是幾行命令悲雳,360加固保提供了一套命令行進(jìn)行加固

image
特別提醒,此處360配置可選項(xiàng)的增強(qiáng)服務(wù)有bug香追,已經(jīng)跟官方溝通合瓢,他們需要在下個(gè)版本修復(fù),當(dāng)前存在bug的版本3.2.2.3(2020-03-16)透典,命令行目前無(wú)法只選擇盜版監(jiān)測(cè)
/**
 *  對(duì)于release apk 進(jìn)行360加固
 */
def packers360(File releaseApk) {
    println 'packers===beginning 360 jiagu'
    def packersFile = file(app["packersPath"])
    if (!packersFile.exists()) {
        packersFile.mkdir()
    }
    exec {
        // 登錄360加固保
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-login', packers["account"], packers["password"]]
        println 'packers===import 360 login'
    }
    exec {
        // 導(dǎo)入簽名信息
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-importsign', signing["storeFile"],
                signing["storePassword"], signing["keyAlias"], signing["keyPassword"]]
        println 'packers===import 360 sign'
    }
    exec {
        // 查看360加固簽名信息
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-showsign']
        println 'packers===show 360 sign'
    }
    exec {
        // 初始化加固服務(wù)配置,后面可不帶參數(shù)
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-config']
        println 'packers===init 360 services'
    }
    exec {
        // 執(zhí)行加固晴楔,然后自動(dòng)簽名,若不采取自動(dòng)簽名峭咒,需要自己通過(guò)build-tools命令自己簽名
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-jiagu', releaseApk.absolutePath, app["packersPath"], '-autosign']
        println 'packers===excute 360 jiagu'
    }
    println 'packers===360 jiagu finished'
    println "packers===360 jiagu path ${app["packersPath"]}"
}

自動(dòng)簽名

關(guān)于自動(dòng)簽名税弃,其實(shí)360在加固的時(shí)候提供了自動(dòng)簽名的配置選項(xiàng),如果你不想將簽名文件上傳給360凑队,在加固后可以自己選擇手動(dòng)簽名则果,因?yàn)檫@涉及到安全性的問(wèn)題,此版本我采取的是360自動(dòng)簽名,如果大家想自己手動(dòng)簽名西壮,下面我給出一套方案遗增,主要是利用 zipalignapksigner命令
他們都是位于SDK文件中的build-tools目錄中,我們執(zhí)行自動(dòng)化簽名需要gradle配置好路徑款青。

  • 對(duì)齊未簽名的apk
zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
  • 使用你的私鑰為apk簽名
apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk
  • 驗(yàn)證apk是否已經(jīng)被簽名
apksigner verify my-app-release.apk

基于加固Apk自動(dòng)實(shí)現(xiàn)多渠道

關(guān)于多渠道打包做修,我們之前項(xiàng)目一直使用的是騰訊的VasDolly,故我們此次是采取VasDolly命令可都,但是需要先下載VasDolly.jar缓待,至于放在什么位置沒(méi)有要求,只需要gradle配置好路徑即可渠牲,我直接是放在項(xiàng)目根目錄旋炒。

image
/**
 * 騰訊channel重新構(gòu)建渠道包
 */
def reBuildChannel() {
    File channelFile = file("${app["channelPath"]}/new")
    if (!channelFile.exists()) {
        channelFile.mkdirs()
    }
    def cmd = "java -jar ${app["vasDollyPath"]} put -c ${"../channel.txt"} ${outputpackersApk()} ${channelFile.absolutePath}"
    println cmd
    cmd.execute().waitForProcessOutput(System.out, System.err)
    println 'packers===excute VasDolly reBuildChannel'
}

敏感信息存取

我們都知道,簽名需要簽名文件签杈,密碼瘫镇、別名等等文件,360加固需要配置賬號(hào)與密碼答姥,這些都屬于敏感信息铣除,google官方不建議直接放在gradle中,它是以純文本記錄在gradle中的鹦付,建議存儲(chǔ)在properties文件中尚粘。

// 把敏感信息存放到自定義的properties文件中
def propertiesFile = rootProject.file("release.properties")
def properties = new Properties()
properties.load(new FileInputStream(propertiesFile))

ext {
    // 簽名配置
    signing = [keyAlias     : properties['RELEASE_KEY_ALIAS'],
               keyPassword  : properties['RELEASE_KEY_PASSWORD'],
               storeFile    : properties['RELEASE_KEYSTORE_PATH'],
               storePassword: properties['RELEASE_STORE_PASSWORD']
    ]

    // app相關(guān)的配置
    app = [
            //默認(rèn)release apk的文件路徑,因?yàn)榧庸淌腔趓elease包的
            releasePath : "${project.buildDir}/outputs/apk/release",
            //對(duì)release apk 加固后產(chǎn)生的加固apk地址
            packersPath : "${project.buildDir}/outputs/packers",
            //加固后進(jìn)行騰訊多渠道打包的地址
            channelPath : "${project.buildDir}/outputs/channels",
            //騰訊VasDolly多渠道打包jar包地址
            vasDollyPath: "../VasDolly.jar"
    ]

    // 360加固配置
    packers = [account          : properties['ACCOUNT360'], //賬號(hào)
               password         : properties['PASSWORD360'],  //密碼
               zipPath          : "${project.rootDir}/jiagu/360jiagu.zip",  //加固壓縮包路徑
               unzipPath        : "${project.rootDir}/jiagu/360jiagubao/",  //加固解壓路徑
               jarPath          : "${project.rootDir}/jiagu/360jiagubao/jiagu/jiagu.jar",  //執(zhí)行命令的jar包路徑
               channelConfigPath: "${project.rootDir}/jiagu/Channel.txt",  //加固多渠道
               jiagubao_mac     : "https://down.360safe.com/360Jiagu/360jiagubao_mac.zip",  //加固mac下載地址
               jiagubao_windows : "https://down.360safe.com/360Jiagu/360jiagubao_windows_64.zip" //加固widnows下載地址
    ]

gradle相關(guān)基礎(chǔ)

  • gradle腳本插件的引用
apply from: "${project.rootDir}/packers.gradle"
  • 局部變量
 def dest = "A"
  • 擴(kuò)展屬性
使用ext擴(kuò)展塊敲长,一次擴(kuò)展多個(gè)屬性
ext {
    account = "XXXX"
    password = "XXXXX"
}
  • 字符串相關(guān)
單引號(hào)不支持插值
def name = '張三'
雙引號(hào)支持插值
def name = "我是${'張三'}"
三個(gè)單引號(hào)支持換行
def name = """
張三
李四
"""
  • 可有可無(wú)的圓括號(hào)
// 這兩種寫(xiě)法等價(jià)
println('A')
println 'A'
  • 閉包作為方法的最后一個(gè)參數(shù)
repositories {
    println "A"
}
repositories() { println "A" }
repositories({println "A" })
  • task依賴(lài)
task B {
    // TaskB依賴(lài)TaskA郎嫁,故會(huì)先執(zhí)行TaskA
    dependsOn A
    //其次執(zhí)行packersRelease
    doLast {
        println "B"
    }
}
  • task排序
//taskB必須總是在 taskA 之后運(yùn)行, 無(wú)論 taskA 和 taskB 是否將要運(yùn)行
taskB.mustRunAfter(taskA)
//沒(méi)有msut那么嚴(yán)格
taskB.shouldRunAfter (taskA)
  • 文件定位
// 使用一個(gè)相對(duì)路徑
File configFile = file('src/config.xml')
// 使用一個(gè)絕對(duì)路徑
configFile = file(configFile.absolutePath)
// 使用一個(gè)項(xiàng)目路徑的文件對(duì)象 
configFile = file(new File('src/config.xml'))`
  • 文件遍歷
// 對(duì)文件集合進(jìn)行迭代
collection.each {File file ->
    println file.name
}
  • 文件復(fù)制重命名
 copy {
        from 源文件地址
        into 目標(biāo)目錄地址
        rename(“原文件名”, "新文件名字")  
    }

自動(dòng)上傳到服務(wù)器

這個(gè)功能準(zhǔn)備在下篇文章更新,我們可以通過(guò)curl命令上傳到自己的服務(wù)器祈噪,如果你在測(cè)試階段可以上傳到蒲公英或者fir.im托管平臺(tái)泽铛,目前他們都提供了相關(guān)的操作方式,這樣基本上整個(gè)自動(dòng)化的目的就完成了辑鲤,當(dāng)然你也可以選擇Jenknis自動(dòng)化構(gòu)建盔腔、打包及上傳。

  • 發(fā)布應(yīng)用到fir.im托管平臺(tái) 入口
方式一:fir-CLI 命令行工具上傳  
$ fir p path/to/application -T YOUR_FIR_TOKEN
方式二:API 上傳
通過(guò)curl命令調(diào)用相關(guān)的api
1.獲取憑證
curl -X "POST" "http://api.bq04.com/apps" \
     -H "Content-Type: application/json" \
     -d "{\"type\":\"android\", \"bundle_id\":\"xx.x\", \"api_token\":\"aa\"}"
2.上傳apk
curl   -F "key=xxxxxx"              \
       -F "token=xxxxx"             \
       -F "file=@aa.apk"            \
       -F "x:name=aaaa"             \
       -F "x:version=a.b.c"         \
       -F "x:build=1"               \
       -F "x:release_type=Adhoc"   \  #type=ios 使用
       -F "x:changelog=first"       \
       https://up.qbox.me
  • 發(fā)布應(yīng)用到蒲公英 入口
curl -F "file=@/tmp/example.ipa" -F "uKey=" -F "_api_key=" https://upload.pgyer.com/apiv1/app/upload
image

整體效果

我們的需求是需要打兩批包月褥,用于老后臺(tái)與新后臺(tái)弛随,老后臺(tái)的包必須加上app-前綴,所以有三個(gè)任務(wù)packersNewRelease執(zhí)行正常的加固打包用于新后臺(tái)宁赤,packersOldRelease用于打包加前綴app-名稱(chēng)用于老后臺(tái)舀透,packersRelease這個(gè)任務(wù)用于一鍵同時(shí)打包成老后臺(tái)與新后臺(tái)。

image

同時(shí)可以在gradle控制臺(tái)查看打包任務(wù)的輸出日志礁击,如下:

image

gradle自動(dòng)化源碼

為了能夠讓大家嘗試自動(dòng)化gradle腳本帶來(lái)的便利之處盐杂,下面我貢獻(xiàn)上自己的整個(gè)gradle源碼逗载,需要的可以拿走去研究,如存在問(wèn)題也希望多多交流链烈。

/**
 * @author hule
 * @date 2020/04/15 13:42
 * description:360自動(dòng)加固+Vaslloy多渠道打包
 */

// 把敏感信息存放到自定義的properties文件中
def propertiesFile = rootProject.file("release.properties")
def properties = new Properties()
properties.load(new FileInputStream(propertiesFile))

ext {
    // 簽名配置
    signing = [keyAlias     : properties['RELEASE_KEY_ALIAS'],
               keyPassword  : properties['RELEASE_KEY_PASSWORD'],
               storeFile    : properties['RELEASE_KEYSTORE_PATH'],
               storePassword: properties['RELEASE_STORE_PASSWORD']
    ]

    // app相關(guān)的配置
    app = [
            //默認(rèn)release apk的文件路徑厉斟,因?yàn)榧庸淌腔趓elease包的
            releasePath : "${project.buildDir}/outputs/apk/release",
            //對(duì)release apk 加固后產(chǎn)生的加固apk地址
            packersPath : "${project.buildDir}/outputs/packers",
            //加固后進(jìn)行騰訊多渠道打包的地址
            channelPath : "${project.buildDir}/outputs/channels",
            //騰訊VasDolly多渠道打包jar包地址
            vasDollyPath: "../VasDolly.jar"
    ]

    // 360加固配置
    packers = [account          : properties['ACCOUNT360'], //賬號(hào)
               password         : properties['PASSWORD360'],  //密碼
               zipPath          : "${project.rootDir}/jiagu/360jiagu.zip",  //加固壓縮包路徑
               unzipPath        : "${project.rootDir}/jiagu/360jiagubao/",  //加固解壓路徑
               jarPath          : "${project.rootDir}/jiagu/360jiagubao/jiagu/jiagu.jar",  //執(zhí)行命令的jar包路徑
               channelConfigPath: "${project.rootDir}/jiagu/Channel.txt",  //加固多渠道
               jiagubao_mac     : "https://down.360safe.com/360Jiagu/360jiagubao_mac.zip",  //加固mac下載地址
               jiagubao_windows : "https://down.360safe.com/360Jiagu/360jiagubao_windows_64.zip" //加固widnows下載地址
    ]
}

/**
 *  360加固,適用于新后臺(tái)打包
 */
task packersNewRelease {
    group 'packers'
    dependsOn 'assembleRelease'
    doLast {
        //刪除加固后的渠道包
        deleteFile()
        // 下載360加固文件
        download360jiagu()
        // 尋找打包文件release apk
        def releaseFile = findReleaseApk()
        if (releaseFile != null) {
            //執(zhí)行加固簽名
            packers360(releaseFile)
            //對(duì)加固后的apk重新用騰訊channel構(gòu)建渠道包
            reBuildChannel()
        } else {
            println 'packers===can\'t find release apk and can\'t excute 360 jiagu'
        }
    }
}

/**
 * 適用于老后臺(tái),老后臺(tái)需要在渠道apk的名稱(chēng)增加前綴 app-
 */
task packersOldRelease {
    group 'packers'
    doLast {
        File channelFile = file("${app["channelPath"]}/new")
        if (!channelFile.exists() || !channelFile.listFiles()) {
            println 'packers==== please excute pakcersNewRelease first!'
        } else {
            File oldChannelFile = file("${app["channelPath"]}/old")
            if (!oldChannelFile.exists()) {
                oldChannelFile.mkdirs()
            }
            // 對(duì)文件集合進(jìn)行迭代
            channelFile.listFiles().each { File file ->
                copy {
                    from file.absolutePath
                    into oldChannelFile.absolutePath
                    rename(file.name, "app-${file.name}")
                }
            }
            println 'packers===packersOldRelease sucess'
        }
    }
}

/**
 *  加固后强衡,打新版本的渠道包時(shí)擦秽,同時(shí)生成老版本的渠道包
 */
task packersRelease {
    group 'packers'
    dependsOn packersNewRelease
    dependsOn packersOldRelease
    packersOldRelease.mustRunAfter(packersNewRelease)
    doLast {
        println "packers===packersRelease finished"
    }
}

/**
 *  對(duì)于release apk 進(jìn)行360加固
 */
def packers360(File releaseApk) {
    println 'packers===beginning 360 jiagu'
    def packersFile = file(app["packersPath"])
    if (!packersFile.exists()) {
        packersFile.mkdir()
    }
    exec {
        // 登錄360加固保
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-login', packers["account"], packers["password"]]
        println 'packers===import 360 login'
    }
    exec {
        // 導(dǎo)入簽名信息
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-importsign', signing["storeFile"],
                signing["storePassword"], signing["keyAlias"], signing["keyPassword"]]
        println 'packers===import 360 sign'
    }
    exec {
        // 查看360加固簽名信息
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-showsign']
        println 'packers===show 360 sign'
    }
    exec {
        // 初始化加固服務(wù)配置,后面可不帶參數(shù)
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-config']
        println 'packers===init 360 services'
    }
    exec {
        // 執(zhí)行加固
        executable = 'java'
        args = ['-jar', packers["jarPath"], '-jiagu', releaseApk.absolutePath, app["packersPath"], '-autosign']
        println 'packers===excute 360 jiagu'
    }
    println 'packers===360 jiagu finished'
    println "packers===360 jiagu path ${app["packersPath"]}"
}

/**
 * 自動(dòng)下載360加固保,也可以自己下載然后放到根目錄
 */
def download360jiagu() {
    // 下載360壓縮包
    File zipFile = file(packers["zipPath"])
    if (!zipFile.exists()) {
        if (!zipFile.parentFile.exists()) {
            zipFile.parentFile.mkdirs()
            println("packers===create parentFile jiagu ${zipFile.parentFile.absolutePath}")
        }
        // 加固保的下載地址
        def downloadUrl = isWindows() ? packers["jiagubao_windows"] : packers["jiagubao_mac"]
        // mac自帶curl命令 windows需要下載curl安裝
        def cmd = "curl -o ${packers["zipPath"]} ${downloadUrl}"
        println cmd
        cmd.execute().waitForProcessOutput(System.out, System.err)
    }
    File unzipFile = file(packers["unzipPath"])
    if (!unzipFile.exists()) {
        //解壓 Zip 文件
        ant.unzip(src: packers["zipPath"], dest: packers["unzipPath"], encoding: "GBK")
        println 'packers===unzip 360jiagu'
        //將解壓后的文件開(kāi)啟讀寫(xiě)權(quán)限漩勤,防止執(zhí)行 Jar 文件沒(méi)有權(quán)限執(zhí)行感挥,windows需要自己手動(dòng)改
        if (!isWindows()) {
            def cmd = "chmod -R 777 ${packers["unzipPath"]}"
            println cmd
            cmd.execute().waitForProcessOutput(System.out, System.err)
        }
    }
}

/**
 * 騰訊channel重新構(gòu)建渠道包
 */
def reBuildChannel() {
    File channelFile = file("${app["channelPath"]}/new")
    if (!channelFile.exists()) {
        channelFile.mkdirs()
    }
    def cmd = "java -jar ${app["vasDollyPath"]} put -c ${"../channel.txt"} ${outputpackersApk()} ${channelFile.absolutePath}"
    println cmd
    cmd.execute().waitForProcessOutput(System.out, System.err)
    println 'packers===excute VasDolly reBuildChannel'
}

/**
 *  是否是windows系統(tǒng)
 * @return
 */
static Boolean isWindows() {
    return System.properties['os.name'].contains('Windows')
}

/**
 * 尋找本地的release  apk
 * @return true
 */
def deleteFile() {
    delete app["channelPath"]
    delete app["packersPath"]
    println 'packers===delete all file'
}

/**
 * 首先打一個(gè)release包,然后找到當(dāng)前的文件進(jìn)行加固
 * @return releaseApk
 */
def findReleaseApk() {
    def apkDir = file(app["releasePath"])
    File releaseApk = apkDir.listFiles().find { it.isFile() && it.name.endsWith(".apk") }
    println "packers===find release apk ${releaseApk.name}"
    return releaseApk
}
/**
 *  加固輸出并且重新命名
 * @return packersApk
 */
def outputpackersApk() {
    File oldApkDir = file(app["packersPath"])
    File oldApk = oldApkDir.listFiles().find { it.isFile() && it.name.contains("jiagu") }
    println "packers===output pacckers sourceApk ${oldApk.name}"
    copy {
        from app["packersPath"] + File.separator + oldApk.name
        into app["packersPath"]
        rename(oldApk.name, "release.apk")
        println 'packers===output pacckers renameApk release.apk'
    }
    File newApk = oldApkDir.listFiles().find { it.isFile() && it.name.equals("release.apk") }
    println "packers===output packers renameApk${newApk.absolutePath}"
    return newApk.absolutePath
}

結(jié)語(yǔ)

本篇文章分享是基于360加固與騰訊VasDolly多渠道打包的自動(dòng)化實(shí)踐越败,提供的只是一種方式触幼,不限于這兩個(gè)平臺(tái),其他平臺(tái)無(wú)非也就是更換一下加固與多渠道打包的命令究飞,喜歡這篇gradle自動(dòng)化加固與多渠道打包就隨手點(diǎn)個(gè)贊吧置谦,你的點(diǎn)贊是我寫(xiě)作的動(dòng)力!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末亿傅,一起剝皮案震驚了整個(gè)濱河市媒峡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葵擎,老刑警劉巖谅阿,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異酬滤,居然都是意外死亡签餐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)敏晤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贱田,“玉大人缅茉,你說(shuō)我怎么就攤上這事嘴脾。” “怎么了蔬墩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵译打,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我拇颅,道長(zhǎng)奏司,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任樟插,我火速辦了婚禮韵洋,結(jié)果婚禮上竿刁,老公的妹妹穿的比我還像新娘。我一直安慰自己搪缨,他們只是感情好食拜,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著副编,像睡著了一般负甸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痹届,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天呻待,我揣著相機(jī)與錄音,去河邊找鬼队腐。 笑死蚕捉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的柴淘。 我是一名探鬼主播鱼冀,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼悠就!你這毒婦竟也來(lái)了千绪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤梗脾,失蹤者是張志新(化名)和其女友劉穎荸型,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體炸茧,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑞妇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了梭冠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辕狰。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖控漠,靈堂內(nèi)的尸體忽然破棺而出蔓倍,到底是詐尸還是另有隱情,我是刑警寧澤盐捷,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布偶翅,位于F島的核電站,受9級(jí)特大地震影響碉渡,放射性物質(zhì)發(fā)生泄漏聚谁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一滞诺、第九天 我趴在偏房一處隱蔽的房頂上張望形导。 院中可真熱鬧环疼,春花似錦、人聲如沸朵耕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)憔披。三九已至等限,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芬膝,已是汗流浹背望门。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锰霜,地道東北人筹误。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像癣缅,于是被迫代替她去往敵國(guó)和親厨剪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容