Android热修复框架设计Gradle自动化多渠道打包
Gradle 是什么
Gradle 是 依赖管理 + 构建工具。它继承了 Ant 的灵活和 Maven 的生命周期管理,它最后被 google 作为了 Android 御用管理工具。它最大的区别是不用 XML 作为配置文件格式,采用了DSL格式,使得脚本更加简洁。 Ant 是最早的构建工具,基于 idea,好象是2000年有的,当时是最流行 java 构建工具,不过它的 XML 脚本编写格式让 XML 文件特别大。对工程构建过程中的过程控制特别好。 Maven 它是用来给 Ant 补坑的,Maven 第一次支持了从网络上下载的功能,仍然采用 xml 作为配置文件格式,它的问题是不能很好的相同库文件的版本冲突。Maven 专注的是依赖管理,构建神马的并不擅长。 构建工具 是什么 单个源码文件,你可以很轻松地 javac、gcc。然而项目结构复杂的时候,从源代码到实际产出的生成物之间需要经过一些列的转换操作,比如说编译、打包。而这一整个完整的过剩叫做"构建"。 Maven 的主要功能主要分为5点,分别是依赖管理系统、多模块构建、一致的项目结构、一致的构建模型和插件机制。 gradle相关基础
gradle脚本插件的引用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依赖task B { // TaskB依赖TaskA,故会先执行TaskA dependsOn A //其次执行packersRelease doLast { println "B" } }task排序//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("原文件名", "新文件名字") }代码示例自动下载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) } } }打一个release包
gradle其实为我们提供了一系列相关的任务,如下图
我们执行加固前是需要拿到一个release包的,所以我们可以利用assembleRelease在加固前先执行assembleRelease这个Task。 task packersNewRelease { group ‘packers’ //可以利用task的依赖关系先执行打包 dependsOn ‘assembleRelease’ } 自动执行加固
所谓自动执行加固,无非就是几行命令,360加固保提供了一套命令行进行加固
特别提醒,此处360配置可选项的增强服务有bug,已经跟官方沟通,他们需要在下个版本修复,当前存在bug的版本3.2.2.3(2020-03-16),命令行目前无法只选择盗版监测 /** 对于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配置好路径。 对齐未签名的apk zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk 使用你的私钥为apk签名 apksigner sign --ks my-release-key.jks --out my-app-release.apk my-app-unsigned-aligned.apk 验证apk是否已经被签名 apksigner verify my-app-release.apk 基于加固Apk自动实现多渠道
关于多渠道打包,我们之前项目一直使用的是腾讯的VasDolly,故我们此次是采取VasDolly命令,但是需要先下载VasDolly.jar,至于放在什么位置没有要求,只需要gradle配置好路径即可,我直接是放在项目根目录。也可以使用360的多渠道加固,实际上整套都可以使用360加固提供的命令。
腾讯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文件中。 // 把敏感信息存放到自定义的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 : "p r o j e c t . b u i l d D i r / o u t p u t s / a p k / r e l e a s e " , / / 对 r e l e a s e a p k 加 固 后 产 生 的 加 固 a p k 地 址 p a c k e r s P a t h : " {project.buildDir}/outputs/apk/release", //对release apk 加固后产生的加固apk地址 packersPath : "project.buildDir/outputs/apk/release",//对releaseapk加固后产生的加固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 : "p r o j e c t . r o o t D i r / j i a g u / 360 j i a g u . z i p " , / / 加 固 压 缩 包 路 径 u n z i p P a t h : " {project.rootDir}/jiagu/360jiagu.zip", //加固压缩包路径 unzipPath : "project.rootDir/jiagu/360jiagu.zip",//加固压缩包路径unzipPath:"{project.rootDir}/jiagu/360jiagubao/", //加固解压路径 jarPath : "p r o j e c t . r o o t D i r / j i a g u / 360 j i a g u b a o / j i a g u / j i a g u . j a r " , / / 执 行 命 令 的 j a r 包 路 径 c h a n n e l C o n f i g P a t h : " {project.rootDir}/jiagu/360jiagubao/jiagu/jiagu.jar", //执行命令的jar包路径 channelConfigPath: "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下载地址 ]自动上传到服务器
这个功能准备在下篇文章更新,我们可以通过curl命令上传到自己的服务器,如果你在测试阶段可以上传到蒲公英或者fir.im托管平台,目前他们都提供了相关的操作方式,这样基本上整个自动化的目的就完成了,当然你也可以选择Jenknis自动化构建、打包及上传。
发布应用到fir.im托管平台 入口 方式一: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自动化源码
为了能够让大家尝试自动化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 : "p r o j e c t . b u i l d D i r / o u t p u t s / a p k / r e l e a s e " , / / 对 r e l e a s e a p k 加 固 后 产 生 的 加 固 a p k 地 址 p a c k e r s P a t h : " {project.buildDir}/outputs/apk/release", //对release apk 加固后产生的加固apk地址 packersPath : "project.buildDir/outputs/apk/release",//对releaseapk加固后产生的加固apk地址packersPath:"{project.buildDir}/outputs/packers", //加固后进行腾讯多渠道打包的地址 channelPath : "${project.buildDir}/outputs/channels", //腾讯VasDolly多渠道打包jar包地址 vasDollyPath: "…/VasDolly.jar" ]// 360加固配置packers = [account : properties[‘ACCOUNT360’], //账号 认release apk的文件路径,因为加固是基于release包的 releasePath : "p r o j e c t . b u i l d D i r / o u t p u t s / a p k / r e l e a s e " , / / 对 r e l e a s e a p k 加 固 后 产 生 的 加 固 a p k 地 址 p a c k e r s P a t h : " {project.buildDir}/outputs/apk/release", //对release apk 加固后产生的加固apk地址 packersPath : "project.buildDir/outputs/apk/release",//对releaseapk加固后产生的加固apk地址packersPath:"{project.buildDir}/outputs/packers", //加固后进行腾讯多渠道打包的地址 channelPath : "${project.buildDir}/outputs/channels", //腾讯VasDolly多渠道打包jar包地址 vasDollyPath: "…/VasDolly.jar" ]// 360加固配置packers = [account : properties[‘ACCOUNT360’], //账号文末
以上步骤的前提是你需要配置好Gradle运行环境、熟悉Gradle的基本命令以及必要的耐心和不怕失败的勇气。
深入Android架构学习,可以私信我,发送"核心笔记"或"手册",即可获取Android架构相关学习路线及相套的学习资料!