Gradle 相关总结
APT 和 AGPTransform 区别
Gradle+Transform+Asm自动化注入代码
Android 360加固+Walle多渠道自动化打包上传蒲公英
概述
我们的目标是全自动化,并且在每个团队成员的电脑上都能够实现一行命令执行,不需要做额外的配置。在测试app项目过程中,通常都是需要开发打测试包给到测试,但是无论是iOS还是Android的打包过程都是相当漫长的,频繁的回归测试需要频繁的打包,对于开发同学影响还是蛮大的。因此在这种情况下,开发通常都会搭建一个简单的自动化打包平台(Jenkins),自动化构建打包或者上传到蒲公英,firm等分发平台。作为测试也需要了解相关的知识,用以优化提高开发测试效率。
前期技术调研
技术方案 | 优点 | 缺点 |
---|---|---|
Android原生方案 | 通过PrpductFlovers进行变体打包,变体与变体之间构建灵活 | 每个变体都是assebleXXXRelease重新打包加固,打包速度慢 |
美团walle | 每个渠道是通过解apk之后,插入渠道信息,再重新签名 打包速度快 | 加固需要自己实现或者其他第三方方案 |
360加固保 | 同walle方案类似,打包速度快;可以加固多渠道打包一体化 | 渠道之间的差异需要获取meta-data硬编码 |
腾讯 VasDolly | 每个渠道是通过解apk之后,插入渠道信息,再重新签名 打包速度快 | 加固需要自己实现或者其他第三方方案 |
下面是来自VasDolly实现原理的表格,目前市面上的多渠道打包工具主要有packer-ng-plugin和美团的Walle。
多渠道打包工具对比 | VasDolly | packer-ng-plugin | Walle |
---|---|---|---|
V1签名方案 | 支持 | 支持 | 不支持 |
V2签名方案 | 支持 | 不支持 | 支持 |
已有注释块的APK | 支持 | 不支持 | 不支持 |
根据已有APK生成渠道包 | 支持 | 不支持 | 不支持(这个Walle可以通过命令行支持) |
命令行工具 | 支持 | 支持 | 支持 |
强校验 | 支持 | 不支持 | 不支持 |
多线程加速打包 | 支持 | 不支持 | 不支持 |
而我们使用的360加固包+Walle结合使用,看到网上很多文章说Walle打多渠道包之后加固,导致渠道信息丢失,首先360加固保加固会破坏签名结构,而Walle是直接把去打信息放到安全区,所以会导致失败。
实现
找到360加固宝的zip以及文档:360加固保下载地址
因为我们需要把自动加固和多渠道打包做到自动化,所以我们需要使用360加固宝的命令行工具:命令行工具文档
通过文档,找到几个关键的命令行
登录:java -jar jiagu.jar –login
导入签名:java -jar jiagu.jar -importsign
导入渠道列表文件:java -jar jiagu.jar -importmulpkg
加固 多渠道打包:java -jar jiagu.jar -jiagu-autosign -automulpkg
多渠道打包 美团Walle
使用Gradle的打多渠道包的方式,Walle并没有提供输入需要打多渠道包apk的路径,所以我们使用命令行工具
java -jar walle-cli-all.jar batch -f [渠道配置文件] [待打包的apk路径] [打包输出的路径]
walle-cli-all.jar文件下载地址:官方:walle-cli-all.jar, 其他开发提供的编译版本
官方的版本打完包会发现在系统9.0(P)下无法正常安装, 相关问题可以查看Issue, 当然你也可以自己拉取源码编译。
下面是我们配置的gradle代码:
apply from: '../gradle/andResGuard.gradle'
class RepackageExt {
String pgy_uploadUrl
String pgy__api_key
int pgy_buildInstallType
String pgy_buildPassword
String jjiagu_arPath
String jiagu_account
String jiagu_password
String channel_wallJarPath
String channel_file
}
def repackageExt = getExtensions().create("repackageExt", RepackageExt)
ext {
println(">>>>>>Repackage ext 配置阶段")
//蒲公英配置
uploadUrl = ""
_api_key = ""
buildInstallType = 2
buildPassword = ""
account = "" //360加固账号
password = "" //360加固密码
signPassword = ""//签名pass ,这里的变量会替换app中同名的变量的值
signAlias = ""//签名key
keyPath = ""
rootPath = "" //apk原始路径,实际上这里的加固和多渠道包也是放在同一个目录下
debugRootPath = "" //apk原始路径,实际上这里的加固和多渠道包也是放在同一个目录下
jarPath = "" //360加固执行命令的jar包路径
wallJarPath = ""// Walle执行命令的jar包路径
channelFile = ""//多渠道配置文件
login = "" //360登录命令
importKey = "" //360 导入签名信息命令
checkSignInfo = "" //360 检查签名信息
initJiaguService = "" //初始化360加固服务配置
println(">>>>>>Repackage ext 配置阶段结束")
}
/**
* Gradle执行配置阶段之后,对我们的配置进行初始化
*/
project.afterEvaluate {
println(">>>>>配置阶段完成时候执行")
jarPath = repackageExt.jjiagu_arPath
wallJarPath = repackageExt.channel_wallJarPath
channelFile = repackageExt.channel_file
uploadUrl = repackageExt.pgy_uploadUrl
_api_key = repackageExt.pgy__api_key
buildInstallType = repackageExt.pgy_buildInstallType
buildPassword = repackageExt.pgy_buildPassword
account = repackageExt.jiagu_account
password = repackageExt.jiagu_password
initApkOutDir()
initSignInfo()
initCmd()
//正式服
project.tasks.getByName('resguardRelease') { Task task ->
task.dependsOn(assembleCleanApkDirRelease)
}
project.tasks.getByName('assembleQihuJiaGuRelease') { Task task ->
task.dependsOn(resguardRelease)
}
project.tasks.getByName('assembleWalleChannelsRelease') { Task task ->
task.dependsOn(assembleQihuJiaGuRelease)
}
project.tasks.getByName('assembleUploadPGYRelease') { Task task ->
task.dependsOn(assembleWalleChannelsRelease)
}
//测试服
project.tasks.getByName('assembleUploadPGYDebug') { Task task ->
task.dependsOn(resguardDebug)
}
}
/**
* 定义apk删除目录任务
*/
project.task("assembleCleanApkDirRelease") { Task task ->
task.setGroup("publishApks")
task.doLast {
def apkRootPath = file(rootPath).getParentFile().path
println "==================>开始执行删除apk输出目录任务${apkRootPath}"
delete(apkRootPath)
println "==================>删除apk输出目录任务完成${apkRootPath}"
}
}
/**
* 加固
*
* rootPath: 原始apk输出的路径
*
*/
project.task("assembleQihuJiaGuRelease") { Task task ->
task.setGroup("publishApks")
task.doLast {
println "开始加固任务==================>${rootPath}"
startJiaGu(file(rootPath))
// 360加固我是用自动加固,所以后缀会有_sign
def jiaGuApk = findApkFile(file(rootPath).getParentFile().path, "_sign")
println "加固完成任务==================>${jiaGuApk.path}"
}
}
/**
* 定义Gardle Task,walle多渠道打包
*/
project.task("assembleWalleChannelsRelease") { Task task ->
task.setGroup("publishApks")
task.doLast {
// 360加固我是用自动加固,所以后缀会有_sign
def needChannelApk = findApkFile(file(rootPath).getParentFile().path, "_sign")
println "开始渠道打包任务==================>${needChannelApk.path}"
if (needChannelApk != null) {
// 官方的版本打完包会发现在系统9.0(P)下无法正常安装相关问题可以查看Issue(https://github.com/Meituan-Dianping/walle/issues/264)
//"java -jar {walle-cli-all.jar文件路径} batch -f {渠道文件路径} {要加渠道的apk文件路径} {渠道包的输出路径}"
def channelCmd = "java -jar ${wallJarPath} batch -f ${channelFile} ${needChannelApk.path} ${needChannelApk.getParentFile().path}"
executeJiaGuCMD(channelCmd)
}
println "渠道打包任务完成==================>${needChannelApk.path}"
}
}
/**
* 上传蒲公英
*
* 这里仅仅只选择dev渠道上传蒲公英
*
*/
project.task("assembleUploadPGYRelease") { Task task ->
task.setGroup("publishApks")
task.doLast {
def needUploadApk = findApkFile(file(rootPath).getParentFile().path, "_dev")
println "开始上传蒲公英任务==================>${needUploadApk.path}"
if (needUploadApk != null) {
uploadPGY(needUploadApk.path)
}
println "上传蒲公英任务完成==================>${needUploadApk.path}"
}
}
/**
* 上传测试包蒲公英
*/
project.task("assembleUploadPGYDebug") { Task task ->
task.setGroup("publishApks")
task.doLast {
println "开始上传蒲公英任务==================>${debugRootPath}"
uploadPGY(debugRootPath)
println "上传蒲公英任务完成==================>${debugRootPath}"
}
}
/**
* 初始化打包APK的输出路径
*/
private void initApkOutDir() {
def android = project.getExtensions().getByType(AppExtension)
android.applicationVariants.all { variant ->
def apkRootPath = variant.packageApplicationProvider.get().outputDirectory
variant.packageApplicationProvider.get().outputScope.apkDatas.forEach { apkData ->
if (variant.buildType.name == "release") {
rootPath = "${apkRootPath}\\${apkData.outputFileName}"
println ">>>>release${rootPath}"
}
if (variant.buildType.name == "debug") {
debugRootPath = "${apkRootPath}\\${apkData.outputFileName}"
println ">>>>debug${debugRootPath}"
}
}
}
def versionName = android.getDefaultConfig().versionName
println("rootPath>>>>>${rootPath} , ${debugRootPath} ${versionName}")
}
/**
* 初始化签名信息
*/
private void initSignInfo() {
def android = project.getExtensions().getByType(AppExtension)
android.getSigningConfigs().each { SigningConfig signingConfig ->
if (signingConfig.name == "release") {
signPassword = signingConfig.keyPassword
signAlias = signingConfig.keyAlias
keyPath = signingConfig.storeFile
println("signingConfig>>>${signingConfig}")
}
}
}
/**
* 初始化加固打包命令
*/
private void initCmd() {
login = "java -jar ${jarPath} -login ${account} ${password}"
importKey = "java -jar ${jarPath} -importsign ${keyPath} ${signPassword} ${signAlias} ${signPassword}"
checkSignInfo = "java -jar ${jarPath} -showsign"
initJiaguService = "java -jar ${jarPath} -config"
}
private void startJiaGu(File needJiaGuApkFile) {
// 登录360加固保
executeJiaGuCMD(login)
// 导入签名信息
executeJiaGuCMD(importKey)
// 查看360加固签名信息
executeJiaGuCMD(checkSignInfo)
// 初始化加固服务配置,后面可不带参数
executeJiaGuCMD(initJiaguService)
// 执行加固,然后自动签名,若不采取自动签名,需要自己通过build-tools命令自己签名
def startJiaGu = "java -jar ${jarPath} -jiagu ${needJiaGuApkFile.path} ${needJiaGuApkFile.getParentFile().path} -autosign"
executeJiaGuCMD(startJiaGu)
}
/**
* 根据后缀查找匹配的apk文件
* @param path
* @param suffix
* @return
*/
private static File findApkFile(path, suffix) {
def dir = new File(path)
return dir.listFiles().find { it.isFile() && it =~ /.*${suffix}\.apk/ }
}
/**
* 执行命令
* @param cmd 命令
*/
private void executeJiaGuCMD(String cmd) {
println cmd
def process = cmd.execute()
println process.text
process.waitFor() // 用以等待外部进程调用结束
println process.exitValue()
}
//上传蒲公英托管 curl --help查看命令 -F指定HTTP分段POST数据(H)
private def uploadPGY(String filePath) {
def stdout = new ByteArrayOutputStream()
exec {
//设置要使用的可执行文件的名称。
executable = 'curl'
args = ['-F', "file=@${filePath}", '-F', "_api_key=${_api_key}", '-F', "buildInstallType=${buildInstallType}", '-F', "buildPassword=${buildPassword}", "${uploadUrl}"]
//设置输出流以使用执行命令的进程的标准输出。该过程完成后,流将关闭,修改命令输出的地方,默认为控制台
standardOutput = stdout
}
String output = stdout.toString()
def parsedJson = new JsonSlurper().parseText(output)
println output
println "应用二维码地址:" + parsedJson.data.buildQRCodeURL
println "版本号:" + parsedJson.data.buildVersion
}
gradle中主要流程是:
1、经过微信的resguardRelease打包,输出结果;
2、将resguardRelease输出的apk文件使用360加固保进行加固;
3、对加固完成的apk文件,使用Walle打多渠道包;
4、最后一步就是上传蒲公英托管平台;
熟悉gradle应该知道,gradle的执行过程。
/**
* Gradle执行配置阶段之后,对我们的配置进行初始化
*/
project.afterEvaluate {
initApkOutDir()
initSignInfo()
initCmd()
}
对于我们的打包gradle,首先我们会在配置阶段做一些初始化操作,如:project.afterEvaluate
闭包就是在配置完成时h,比如:获取打包apk的路径和apk签名文件信息等等,代码如下:会调用这个闭包,接下来就是获取相关的数据的代码。
/**
* 初始化打包APK的输出路径
*/
void initApkOutDir() {
def android = project.getExtensions().getByType(AppExtension)
android.applicationVariants.all { variant ->
def apkRootPath = variant.packageApplicationProvider.get().outputDirectory
variant.packageApplicationProvider.get().outputScope.apkDatas.forEach { apkData ->
rootPath = "${apkRootPath}\\${apkData.outputFileName}"
}
}
println("rootPath>>>>>${rootPath}")
}
/**
* 初始化签名信息
*/
void initSignInfo() {
def android = project.getExtensions().getByType(AppExtension)
android.getSigningConfigs().each { SigningConfig signingConfig ->
if (signingConfig.name == "release") {
signKeyPassword = signingConfig.keyPassword
signkeyAlias = signingConfig.keyAlias
keyPath = signingConfig.storeFile
println("signingConfig>>>${signingConfig}")
}
}
}
/**
* 初始化加固打包命令
*/
void initCmd() {
login = "java -jar ${jarPath} -login ${account} ${password}"
importKey = "java -jar ${jarPath} -importsign ${keyPath} ${signKeyPassword} ${signkeyAlias} ${signKeyPassword}"
checkSignInfo = "java -jar ${jarPath} -showsign"
initJiaguService = "java -jar ${jarPath} -config"
}
最后就是我们上传蒲公英的代码:
//上传蒲公英托管 curl --help查看命令 -F指定HTTP分段POST数据(H)
private def uploadPGY(String filePath) {
def stdout = new ByteArrayOutputStream()
exec {
//设置要使用的可执行文件的名称。
executable = 'curl'
args = ['-F', "file=@${filePath}", '-F', "_api_key=${_api_key}", '-F', "buildInstallType=${buildInstallType}", '-F', "buildPassword=${buildPassword}", "${uploadUrl}"]
//设置输出流以使用执行命令的进程的标准输出。该过程完成后,流将关闭,修改命令输出的地方,默认为控制台
standardOutput = stdout
}
String output = stdout.toString()
def parsedJson = new JsonSlurper().parseText(output)
println output
println "应用二维码地址:" + parsedJson.data.buildQRCodeURL
println "版本号:" + parsedJson.data.buildVersion
}
当然除了这种直接在Gradle文件中处理的方式,我觉得可以将这些库整合成插件会比较方便使用,在我们项目中已经把AndResGuard+ 360+Walle+蒲公英
整合到一个插件中了,到此我们的自动化加固打包已经结束了。