Jenkins之自动进行360加固

android studio 下载 | 2019-01-25 10:12

本文大概两千三百字,看完只要区区九分钟。

项目中使用到了360加固与阿里的热修复方案,以前是人工去操作加固或生成补丁包,比较浪费开发人员的时间,并且在我的电脑上每次命令行执行完全量编译都会卡很久。前段时间项目不紧,所以抽时间完成了在Jenkins流水线上进行360加固与生成热修复补丁的步骤。本文主要介绍实现Jenkins上进行自动加固的过程。

先简单描述一下项目相关的一些背景条件:

项目采用git-flow分支模型,在release分支提测,并且当测试通过时需要对应版本的正式环境的apk进行冒烟测试。

由于用到了热修复,所以需要保存加固前的原apk及mapping文件。

Jenkins构建节点为mac电脑,我个人使用的是Ubuntu,所以加固的脚本需要至少在Mac/Linux上都可以正常执行。

安全起见,不导入签名到360加固中。

由于后续的热修复的需要,我在项目中约定,当版本发布时,在项目的release/文件夹下保存该版本的apk及mapping文件。在前面所述背景条件下,加固过程比起网上的多数文章会偏复杂一点,在项目中我需要依次实现以下过程:

构建正式环境的flavor,并将apk及mapping文件拷贝到release/文件夹下。

执行加固并签名。

将加固前apk,mapping文件及加固后的apk通过邮件发送出来。

考虑到对于我实现这些过程的容易程度,我决定第一步使用自定义gradle任务实现,第二、三步使用shell脚本实现,第四步使用Jenkins的发邮件功能实现。

我们项目中用于区分正式、测试、预发布环境是使用productFlavor来实现的,所以我在app的build.gradle中定义了以下任务:

android.applicationVariants.all { variant ->    if (variant.buildType.name == "release") {        def variantName = variant.flavorName.capitalize()        def releaseDir = new File(rootProject.rootDir, "release")        Task releaseTask = task "release${variantName}"(dependsOn: "assemble${variantName}Release") {            group = 'publish'            description "Copy the output apk and mapping file to target folder for ${variant.name}"            inputs.files(variant.outputs[0].outputFile, variant.mappingFile)            outputs.dir releaseDir        }        releaseTask.doLast {            // 需要保留 mapping.txt 文件用于热修复            if (variant.buildType.minifyEnabled) {                println "copy ${variant.mappingFile}"                copy {                    from variant.mappingFile                    into releaseDir                    rename {                        "mapping-${variant.name}-${variant.versionName}.txt"                    }                }            }            println "copy ${variant.outputs[0].outputFile}"            copy {                from variant.outputs[0].outputFile                into releaseDir                rename {                    "IOP-${variant.name}-${variant.versionName}.apk"                }            }        }    } }我解释一下上面的代码,很简单很好理解。这里对于所有的productFlavor的release构建类型,创建了对应的依赖该assemble任务的任务,任务名称为release${variantName}。比如productFlavor名称为Official,则这里创建的任务名称为releaseOfficial,并且依赖于assembleOfficialRelease任务,其中assembleOfficialRelease即我们用于构建apk的任务。在任务里我指定了任务所在分组及描述,这是为了能够在执行./gradlew app:tasks或在Android Studio右边的Gradle面板中显示出该任务来。另外我指定了inputs及outputs,这是为了实现UP-TO-DATE的效果,即如果对应的assemble任务的输出结果没有变,并且这里的outputs没有变,就不会再重复拷贝。然后我通过调用这个任务的doTask方法,给这个任务增加了一个Action,把apk及mapping文件拷到项目根目录下的release文件夹中并重新命名。到此这个过程就完成了,也就是我们只需要执行./gradlew releaseOfficial,就会进行Official的构建,并把其apk和mapping拷贝到release文件夹下。

注:关于Gradle编写自定义任务的详细介绍,可参考我翻译的Gradle用户指南:《第五十七章. 编写自定义任务类》()。

我这里是采用shell脚本实现,脚本文件将位于项目根目录的buildsystem文件夹下。我把这一过程拆分为五部分。

执行releaseOfficial进行构建。

获取加固程序。

执行加固。

拷贝加固前后相关文件到指定文件夹下,以便于后续的邮件发送。

由于我这里的shell脚本是通过Jenkinsfile来调用的,所以并不知道生成的apk名称。因此我的思路是,先创建一个文件,然后再调用构建任务,最后找出release文件夹下比刚才所创建文件新的apk文件,即这次构建所生成的apk文件。脚本如下:

#!/bin/bashcd `dirname $0`/../ projectDir=`pwd` buildDir="$projectDir/build"# release Official apkcd $buildDirtouch timestampFileecho "Build release official apk"cd $projectDir# invoke gradlew to build the official apk./gradlew releaseOfficial || exit 1# find the target apktargetApk=`find $projectDir/release -name "*.apk" -newer $buildDir/timestampFile`if [ ! -n "$targetApk" ]; then  echo "Apk is not changed. Exit"  exit 0fi获取加固程序360加固为windows, linux及mac分别提供了对应平台的加固助手。我下载了这三个平台的加固助手后,发现主要是java/bin里面的文件不同,这里的文件会依赖于具体平台的可执行文件。所以我的思路是,把这三个平台的文件都打包到一起,然后在调用的时候,先判断当前的操作系统,再拷贝对应的文件到java/bin目录下。经测试,在linux/mac上方案通过。

我在github上创建了对应的项目,将集成的文件上传并打了1.0的tag。通过-jiagu/archive/1.0.zip这个地址可以下载到项目的zip包。为避免每次构建都去重新下载,在实际的项目中,我又加上了七牛的cdn地址,这里略过,还是采用github的地址。将它下载下来后,解压并执行项目里的init.sh脚本,把平台相关的文件拷贝到java/bin目录下。这一过程完整脚本如下:

# download the programcd ~/ jgCacheDir=".android/jiagu"jgVersion="1.0"jgFolder=360-jiagu-$jgVersionjgZipName=$jgFolder.zipif [ ! -d "$jgCacheDir" ]; then  mkdir $jgCacheDirficd $jgCacheDirif test -e "$jgZipName"; then  zflag=" -z $jgZipName"else  zflag=""fi# 这里需要把地址换成自己的cdn地址,以避免每次构建都去下载curl -L -e ";auto" -o $jgZipName $zflag -jiagu/archive/$jgVersion.zip# extractcd $buildDirecho "Extract jiagu.zip"unzip -oq ~/$jgCacheDir/$jgFolder -d .if [ ! -d "jiagu" ]; then  mv $jgFolder jiaguelse  cp -rf $jgFolder/* jiagu/ficd jiagu && ./init.sh || exit 1cd $buildDir/jiagu上面下载使用了curl而不是wget,是因为我发现在公司的mac机器上,没有wget,于是只好使用curl来代替wget。注意这里,应该把地址换成自己的cdn地址,不然可能每次都会去重新下载。

在执行加固前需要进行账号登录。我这里把账号及密码配置在Jenkins的环境变量中。由于个人不是很信任360,所以并没有把签名证书导入进去,这里只是进行加固。这部分的脚本如下:

# loginif [ ! -n "$1" ] || [ ! -n "$2" ]; then  username=$JIAGU_USERNAME  password=$JIAGU_PASSWORDelse  username=$1  password=$2fijava -jar jiagu.jar -login $username $password# remove unnecessary service configjava -jar jiagu.jar -config -# processfor file in `find output -name "*.apk"`; do  rm $file;doneecho "Start process $targetApk"java -jar jiagu.jar -jiagu $targetApk output || exit 1enhancedApk=`find output -name "*.apk"`在上面的脚本中,所有增强服务我都没有选,这一步可根据自己需要去配置。

我自己写过一个签名的脚本,这里我把脚本放在同一目录下,所以直接调用该脚本签名,如下:

# signecho "Sign $enhancedApk"$projectDir/buildsystem/signIOP.sh $enhancedApk签名脚本的实现可以参考我写的签名脚本,项目地址为:。

接下来,是把文件拷贝到archives文件夹下,并保存当前git提交信息。

# cp the archives to folderif [ ! -d "archives" ]; then  mkdir archiveselse  rm archives/*ficp $enhancedApk archives/for file in `find $projectDir/release -type f -newer $buildDir/timestampFile`; do  cp $file archives/donegit log -n 1 > archives/git-info.txt完整脚本可参考我的gist:

最后是修改我们的Jenkinsfile,调用加固脚本,并发送邮件。在这里,增加一个Enhance的stage,如下:

def getChangeString() {    def changeString = ""    def changeLogSets = currentBuild.changeSets    for (int i = 0; i < changeLogSets.size(); i++) {        def entries = changeLogSets[i].items        for (int j = 0; j < entries.length; j++) {            def entry = entries[j]            truncated_msg = entry.msg            changeString += " - ${truncated_msg}\\\\n"        }    }    if (!changeString) {        changeString = " - No new changes"    }    return changeString }def enhanceEmailBody() {    return """<p>IOP-Android加固成功。</p>           <p>更新日志:<br/>           ${getChangeString().replaceAll("\\\\\\\\n", "")}</p>           <p>附件说明如下:</p>             <ol>               <li>文件名带jiagu的,是加固后的包,用于分发。</li>               <li>文件名不带jiagu的,与mapping开头的txt文件,用于生成补丁。</li>               <li>git-info.txt文件为本次构建代码的最新git信息。</li>             </ol>           </p>           """}大功告成。