Android 360加固+Walle多渠道自动化打包上传蒲公英

2020-06-25  本文已影响0人  有没有口罩给我一个

概述

我们的目标是全自动化,并且在每个团队成员的电脑上都能够实现一行命令执行,不需要做额外的配置。在测试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 <username> <password>
导入签名:java -jar jiagu.jar -importsign <keystore_path> <keystore_password> <alias<alias_password>
导入渠道列表文件:java -jar jiagu.jar -importmulpkg <mulpkg_path>
加固 多渠道打包:java -jar jiagu.jar -jiagu <inputAPKpath> <outputpath> -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+蒲公英整合到一个插件中了,到此我们的自动化加固打包已经结束了。

上一篇下一篇

猜你喜欢

热点阅读