一般咱們App上架到應用市場基本上都經歷過如下流程,先本地打一個release包,而後經過在線加固或者下載加固工具進行加固,因爲加固會先剔除簽名信息,因此加固後要進行再次簽名,而後生成多渠道包,這樣基本上整個流程就結束了,畫了個思惟導圖以下: html
個人簡單理解就是給原有的apk進行加密和套殼,產生一個新的apk,而後運行的時候會進行解密相關的動做,因此加固後的app通常會影響啓動時間,網上也有不少加固平臺的對比,主要涉及啓動時間、包體積大小、兼容性、安全性等等。本次研究只是討論如何實現自動化加固與多渠道打包思想,360加固並不是最好選擇,加固主要是爲了防止應用在上線後被反編譯、調試、破解、二次打包和內存截取等多種威脅。java
本次Gradle自動化實踐的步驟主要是基於360加固+騰訊的VasDolly多渠道打包。android
/**
* 自動下載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'
//將解壓後的文件開啓讀寫權限,防止執行 Jar 文件沒有權限執行,windows若沒有權限須要本身手動改
if (!isWindows()) {
def cmd = "chmod -R 777 ${packers["unzipPath"]}"
println cmd
cmd.execute().waitForProcessOutput(System.out, System.err)
}
}
}
複製代碼
gradle其實爲咱們提供了一系列相關的任務,以下圖ios
咱們執行加固前是須要拿到一個release包的,因此咱們能夠利用assembleRelease
在加固前先執行
assembleRelease
這個Task。
task packersNewRelease {
group 'packers'
//能夠利用task的依賴關係先執行打包
dependsOn 'assembleRelease'
}
複製代碼
所謂自動執行加固,無非就是幾行命令,360加固保提供了一套命令行進行加固git
特別提醒,此處360配置可選項的加強服務有bug,已經跟官方溝通,他們須要在下個版本修復,當前存在bug的版本3.2.2.3(2020-03-16),命令行目前沒法只選擇盜版監測 github
/**
* 對於release apk 進行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 {
// 導入簽名信息
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 {
// 初始化加固服務配置,後面可不帶參數
executable = 'java'
args = ['-jar', packers["jarPath"], '-config']
println 'packers===init 360 services'
}
exec {
// 執行加固,而後自動簽名,若不採起自動簽名,須要本身經過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"]}"
}
複製代碼
關於自動簽名,其實360在加固的時候提供了自動簽名的配置選項,若是你不想將簽名文件上傳給360,在加固後能夠本身選擇手動簽名,由於這涉及到安全性的問題,此版本我採起的是360自動簽名,若是你們想本身手動簽名,下面我給出一套方案,主要是利用 zipalign
和 apksigner
命令 他們都是位於SDK文件中的build-tools目錄中,咱們執行自動化簽名須要gradle配置好路徑。json
zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
複製代碼
apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk
複製代碼
apksigner verify my-app-release.apk
複製代碼
關於多渠道打包,咱們以前項目一直使用的是騰訊的VasDolly,故咱們這次是採起VasDolly命令,可是須要先下載VasDolly.jar,至於放在什麼位置沒有要求,只須要gradle配置好路徑便可,我直接是放在項目根目錄。也可使用360的多渠道加固,實際上整套均可以使用360加固提供的命令。windows
/**
* 騰訊channel從新構建渠道包
*/
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加固須要配置帳號與密碼,這些都屬於敏感信息,google官方不建議直接放在gradle中,它是以純文本記錄在gradle中的,建議存儲在properties文件中。api
// 把敏感信息存放到自定義的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相關的配置
app = [
//默認release apk的文件路徑,由於加固是基於release包的
releasePath : "${project.buildDir}/outputs/apk/release",
//對release apk 加固後產生的加固apk地址
packersPath : "${project.buildDir}/outputs/packers",
//加固後進行騰訊多渠道打包的地址
channelPath : "${project.buildDir}/outputs/channels",
//騰訊VasDolly多渠道打包jar包地址
vasDollyPath: "../VasDolly.jar"
]
// 360加固配置
packers = [account : properties['ACCOUNT360'], //帳號
password : properties['PASSWORD360'], //密碼
zipPath : "${project.rootDir}/jiagu/360jiagu.zip", //加固壓縮包路徑
unzipPath : "${project.rootDir}/jiagu/360jiagubao/", //加固解壓路徑
jarPath : "${project.rootDir}/jiagu/360jiagubao/jiagu/jiagu.jar", //執行命令的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下載地址
]
複製代碼
apply from: "${project.rootDir}/packers.gradle"
複製代碼
def dest = "A"
複製代碼
使用ext擴展塊,一次擴展多個屬性
ext {
account = "XXXX"
password = "XXXXX"
}
複製代碼
單引號不支持插值
def name = '張三'
雙引號支持插值
def name = "我是${'張三'}"
三個單引號支持換行
def name = """ 張三 李四 """
複製代碼
// 這兩種寫法等價
println('A')
println 'A'
複製代碼
repositories {
println "A"
}
repositories() { println "A" }
repositories({println "A" })
複製代碼
task B {
// TaskB依賴TaskA,故會先執行TaskA
dependsOn A
//其次執行packersRelease
doLast {
println "B"
}
}
複製代碼
//taskB必須老是在 taskA 以後運行, 不管 taskA 和 taskB 是否將要運行
taskB.mustRunAfter(taskA)
//沒有msut那麼嚴格
taskB.shouldRunAfter (taskA)
複製代碼
// 使用一個相對路徑
File configFile = file('src/config.xml')
// 使用一個絕對路徑
configFile = file(configFile.absolutePath)
// 使用一個項目路徑的文件對象
configFile = file(new File('src/config.xml'))`
複製代碼
// 對文件集合進行迭代
collection.each {File file ->
println file.name
}
複製代碼
copy {
from 源文件地址
into 目標目錄地址
rename(「原文件名」, "新文件名字")
}
複製代碼
這個功能準備在下篇文章更新,咱們能夠經過curl命令上傳到本身的服務器,若是你在測試階段能夠上傳到蒲公英或者fir.im託管平臺,目前他們都提供了相關的操做方式,這樣基本上整個自動化的目的就完成了,固然你也能夠選擇Jenknis自動化構建、打包及上傳。安全
方式一:fir-CLI 命令行工具上傳
$ fir p path/to/application -T YOUR_FIR_TOKEN
方式二:API 上傳
經過curl命令調用相關的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
複製代碼
curl -F "file=@/tmp/example.ipa" -F "uKey=" -F "_api_key=" https://upload.pgyer.com/apiv1/app/upload
複製代碼
咱們的需求是須要打兩批包,用於老後臺與新後臺,老後臺的包必須加上app-前綴,因此有三個任務packersNewRelease
執行正常的加固打包用於新後臺,packersOldRelease
用於打包加前綴app-名稱用於老後臺,packersRelease
這個任務用於一鍵同時打包成老後臺與新後臺。
爲了可以讓你們嘗試自動化gradle腳本帶來的便利之處,下面我貢獻上本身的整個gradle源碼,須要的能夠拿走去研究,如存在問題也但願多多交流。
/**
* @author hule
* @date 2020/04/15 13:42
* description:360自動加固+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相關的配置
app = [
//默認release apk的文件路徑,由於加固是基於release包的
releasePath : "${project.buildDir}/outputs/apk/release",
//對release apk 加固後產生的加固apk地址
packersPath : "${project.buildDir}/outputs/packers",
//加固後進行騰訊多渠道打包的地址
channelPath : "${project.buildDir}/outputs/channels",
//騰訊VasDolly多渠道打包jar包地址
vasDollyPath: "../VasDolly.jar"
]
// 360加固配置
packers = [account : properties['ACCOUNT360'], //帳號
password : properties['PASSWORD360'], //密碼
zipPath : "${project.rootDir}/jiagu/360jiagu.zip", //加固壓縮包路徑
unzipPath : "${project.rootDir}/jiagu/360jiagubao/", //加固解壓路徑
jarPath : "${project.rootDir}/jiagu/360jiagubao/jiagu/jiagu.jar", //執行命令的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加固,適用於新後臺打包
*/
task packersNewRelease {
group 'packers'
dependsOn 'assembleRelease'
doLast {
//刪除加固後的渠道包
deleteFile()
// 下載360加固文件
download360jiagu()
// 尋找打包文件release apk
def releaseFile = findReleaseApk()
if (releaseFile != null) {
//執行加固簽名
packers360(releaseFile)
//對加固後的apk從新用騰訊channel構建渠道包
reBuildChannel()
} else {
println 'packers===can\'t find release apk and can\'t excute 360 jiagu'
}
}
}
/**
* 適用於老後臺,老後臺須要在渠道apk的名稱增長前綴 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()
}
// 對文件集合進行迭代
channelFile.listFiles().each { File file ->
copy {
from file.absolutePath
into oldChannelFile.absolutePath
rename(file.name, "app-${file.name}")
}
}
println 'packers===packersOldRelease sucess'
}
}
}
/**
* 加固後,打新版本的渠道包時,同時生成老版本的渠道包
*/
task packersRelease {
group 'packers'
dependsOn packersNewRelease
dependsOn packersOldRelease
packersOldRelease.mustRunAfter(packersNewRelease)
doLast {
println "packers===packersRelease finished"
}
}
/**
* 對於release apk 進行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 {
// 導入簽名信息
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 {
// 初始化加固服務配置,後面可不帶參數
executable = 'java'
args = ['-jar', packers["jarPath"], '-config']
println 'packers===init 360 services'
}
exec {
// 執行加固
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"]}"
}
/**
* 自動下載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'
//將解壓後的文件開啓讀寫權限,防止執行 Jar 文件沒有權限執行,windows須要本身手動改
if (!isWindows()) {
def cmd = "chmod -R 777 ${packers["unzipPath"]}"
println cmd
cmd.execute().waitForProcessOutput(System.out, System.err)
}
}
}
/**
* 騰訊channel從新構建渠道包
*/
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系統
* @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'
}
/**
* 首先打一個release包,而後找到當前的文件進行加固
* @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
}
複製代碼
本篇文章分享是基於360加固與騰訊VasDolly多渠道打包的自動化實踐,提供的只是一種方式,不限於這兩個平臺,其餘平臺無非也就是更換一下加固與多渠道打包的命令,喜歡這篇gradle自動化加固與多渠道打包就隨手點個贊吧,你的點贊是我寫做的動力!