一般咱们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
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自动化加固与多渠道打包就随手点个赞吧,你的点赞是我写做的动力!