抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

BuglyHotfix 与 AndResGuard 整合 以及批量生成补丁脚本

背景说明

就是搞了些麻烦事,不分享出来抛砖引玉,估计会更麻烦。

本文是有 demo 的,并已开源,在文章末尾。

项目文件说明

  • source
    将官方的一些源码以及 test apk 单独存放了下来。
  • script
    • andresguard-support.gradle
      腾讯 AndResGuard 资源混淆配置文件(与官方无异)
    • tinker-support.gradle
      腾讯 Bugly 热修复配置文件(与官方无异)
    • andres-tinker-support.gradle
      用于支持资源混淆的补丁生成,主要是对补丁以及路径生成等做了处理。
    • tool.gradle
      一些 task ,用于py 脚本以及混淆包生成任务。
    • tinkercli
      • tinker-patch-cli-1.9.14.9-all.jar
      • tinker-patch.py
        一个简单脚本,批量对比,批量生成补丁。(没有 Bugly 的 MF 文件,无法被 Bugly 版本管理)
  • tinker-patch.py
    一个简单脚本,基于 gradle buildTinkerRelease task 批量生成补丁,由于是使用 tinker-support task,所以生成的补丁支持 Bugly 版本管理

Tinker 与 AndResGuard 混淆配合使用说明

因使用到了AndResGuard,除了接入外需要特殊处理。主要代码块位于andres-tinker-support.gradle中的该部分:

def tinkerPatchTask = project.tasks.findByName("tinkerPatch${taskName.capitalize()}")
       if (tinkerPatchTask) {
           tinkerPatchTask.doFirst {
               def buildOutDir = "${buildDir.absolutePath}/outputs/apk/${taskName}"
               def buildApkPath = "${buildOutDir}/AndResGuard_${rootProject.ext.outputFileName}/${rootProject.ext.outputFileName}_7zip_aligned_signed.apk"
               println("change tinkerPatchTask buildApkPath to resugurad output ${buildApkPath}")
               if (!"${rootProject.ext.newApk}".isEmpty()) {
                   //替换为指定 apk
                   tinkerPatchTask.buildApkPath = "${rootProject.ext.newApk}"
              } else {
                   //替换tinker release产生的apk 为res混淆后的 apk
                   tinkerPatchTask.buildApkPath = buildApkPath
              }
          }
           //region 关键代码
           tinkerPatchTask.dependsOn resGuardTask
      } else {
           println("tinkerPatchTask not found")
           return
      }

原理: 由于tinkerPatch task无法进行拆分,只能在打包时插入资源混淆的任务,在完成后将tinkerPatchTask所指向的buildApkPath进行变更,这样才能达到对资源混淆包的对比。

  • 取消对混淆的支持
    在生成debug包补丁时不会有影响,不需要时,则注释project.afterEvaluate整个代码块即可。

Tinker 批量生成补丁包制作说明

主要依赖工具:tinker-patch-cli-1.9.14.9-all.jar

关于该 lib 的官方说明:命令行接入

tinker-patch-cli编译

github官方地址下载 tinker-dev 的 release 包:

https://github.com/Tencent/tinker

使用 Android Studio 打开,编译时需要注意先对下载下来的代码进行 git 仓库初始化:

git init
git add .
git commit -m "init"
task buildTinkerSdk(type: Copy, dependsOn: [clean, shadowJar])

完成后会生成到buildSdk/build目录。整个目录都是需要的东西。

等待项目初始化完成后,找到tinker-build/tinker-patch-cli/build.gradle,在gradle中找到task buildTinkerSdk并运行。

不然的话会一直停留在Check git-diff changed files,导致gradle无法进行初始化编译。

tinker-patch-cli参数配置

tinker_config.xml修改

参数与gradle版本一致,但需要配置sign,7z压缩目录。
代码块如下:(7z需要根据平台的不同而变更,这里是windows配置, 其他平台注释这行使用官方默认即可)

<!--if you don't set sevenZip path, we just use 7za to try-->
    <sevenZipPath value="SevenZip-1.1.16-windows-x86_64.exe"/>
<issue id="sign">
    <!--the signature file path, in window use \, in linux use /, and the default path is the running location-->
    <path value="release.keystore"/>
    <!--storepass-->
    <storepass value="testres"/>
    <!--keypass-->
    <keypass value="testres"/>
    <!--alias-->
    <alias value="testres"/>
</issue>

需要注意:

  • 其中的isProtectedApp属性修改。根据使用情况的不同,需要进行不同的配置。
  • 密匙的配置不支持相对路径,所以尽量使用默认的相对路径,即放在同一目录,若密匙变更则需要一同变更。

tinker_proguard.pro修改

需要加入基准包的mapping混淆文件,以及其余对于混淆的要求。

-applymapping path/to/mapping.txt 

注:加入mapping文件步骤已在脚本中集成,无需手动处理。

主要脚本tinker-patch.py说明

脚本整体比较简单,主要是四个函数:

  • get_prop
    用于获取传参,后续需要添加参数可更改此处。
  • fetch_file
    用于匹配mapping文件与基准包apk位置。
    若基准包目录格式更改可更改此处,目前基准包的要求为:

需要用到的文件(必不可缺):图中红框选中文件,混淆结果mapping,txt、混淆后的apk some-app-resguard.apk

mapping = apk + '/mapping.txt'
old_path = apk + '/' + base_name + '-resguard.apk'
  • release_patch
    使用tinker-patch-cli命令打补丁,若后续tinker-patch-cli命令变更可更改此处。
  • copy_patch
    将当前基准包在out目录生成的补丁包转移,以避免下一个基准包的补丁将其覆盖。若需要改变输出目录则更改此处。
  • 其他py文件为官方提供,可自行查看,一般不需要。

Tinker 补丁生成

为更符合业务需求,在除了编译生成补丁,还需要有更便捷的批量基于多个基准包生成补丁的方式。

  • 即时编译生成方式
    • 每次只能对一个基准包进行补丁生成,且只能以当前分支的代码来进行对比生成补丁。
    • 每次都会对代码重新release混淆后才能进行对比,同一份代码可能出现每次编译dex分包均不同的情况。(会产生补丁差异)(或者指定某个固定基准包,忽略编译时生成的最新包,但编译过程却无法省略)
    • 支持 bugly 平台管理。
  • tinker-patch-cli脚本批量方式
    • 不限制基准包版本与数量,只需要确保新包比基准包都要新即可。
    • 单纯只进行包对比,不涉及编译,速度更快,更稳定。
    • 不支持支持 bugly 平台管理。
  • 其他方法1
    找到 tinker-support 插件对于 MF 文件的处理,自行在tinker-patch-cli的补丁生成后,使用 jar uf添加进补丁包。(暂未实现)
  • 其他方法2
    修改 tinker 插件源码,使其不必要每次都先打包。此外目前 tinkerPatch 也不支持在 task 中连续循环执行,仅能执行一次。(暂未实现)

即时编译生成方式

即时编译相关配置主要在config.gradle中,有一些参数需要配置。

//此处填写每次需要进行补丁构建的基准包目录
baseDir = "${rootDir}/out/some-app_2.0_3_20201205173139"
//用于指定新基准包 不使用编译时生成的基准包(tinker 每次任务都会重新生成一个)
newApk = ""

在正常发版release时,主要需要修改的为:isProtectedApp,加固包与非加固包,需要区分。

在补丁生成时,主要修改的为baseDir,指针对某个基准包进行补丁生成。

基准包格式:(需要R文件,以往包不适用)

补丁包生成涉及文件:混淆结果mapping,txt、混淆后的apk some-app-resguard.apk、以及R.txt

打补丁包:

运行完成,则会拷贝至out/最新基准包版本/patch_signed_7zip.apk

这种方式生成的支持 Bugly 管理。

该方式还有一个批量执行的版本,脚本在根目录/tinker-patch.py,主要代码:

def release_patch(old_path):
  if platform.system() == "Windows":
      cmd_head = "gradlew.bat"
  else:
      cmd_head = "./gradlew"
  if new_apk == '':
      patch_cmd = cmd_head + ' app:initDir -PmultiApkDir=' + old_path + ' app:buildTinkerPatchRelease' # --info
  else:
      patch_cmd = cmd_head + ' gradlew app:initDir -PmultiApkDir=' + old_path + ' -PnewApkPath=' + new_apk + ' app:buildTinkerPatchRelease' # --info

  print("patch_cmd: "+patch_cmd)
   
  p = subprocess.Popen(patch_cmd, shell=(platform.system() != "Windows"), stdout=subprocess.PIPE,
                        stderr=subprocess.STDOUT)
  while p.poll() is None:
      line = p.stdout.readline()
      line = line.strip()
      if line:
          print(line)
  if p.returncode == 0:
      print('Subprogram success')
  else:
      print('Subprogram failed')
  return

我没特意学过 py,需要什么查什么,这里存在一些问题,比如 subprocess 使用 ctrl c 无法中断。不过并不影响补丁的生成。

可以指定最新的基准包,忽略每次编译生成的包。

python3 tinker-patch.py -d path/to/BuglyHotfix-AndResDemo/out -n path/to/some-app_2.0_3_last-resguard.apk

也可以不指定,每次和新编译生成的包对比。

python3 tinker-patch.py -d path/to/BuglyHotfix-AndResDemo/out 

运行完成会按旧基准包名称存放。

注意:即便只有一个基准包,也需要包括在一个目录中,且基准包文件格式以及相对路径必须符合上图(或者自行修改匹配规则)

补丁包生成涉及文件:混淆结果mapping,txt、混淆后的apk some-app-resguard.apk、以及R.txt

tinker-patch-cli 脚本批量方式

脚本存放位置:项目根目录/script/tinkercli/tinker-patch.py

使用方式:

python .\tinker-patch.py -d 旧基准包目录 -n 新包apk路径

例:

python .\tinker-patch.py -d path\to\out -n path\to\new.apk

out目录图例:

需要用到的文件:图中红框选中文件,混淆结果mapping,txt、混淆后的apk some-app-resguard.apk

需要注意的是:即便只有一个基准包,也需要包括在一个目录中,且基准包文件格式以及相对路径必须符合上图。

执行命令后,将会在tinkercli/patch生成对应版本的补丁包,格式如下:

生成补丁包后需进行另存管理,否则下次对同样基准包处理时将被覆盖。

这种方式生成的不支持 Bugly 管理。

歪门邪道

由于 bugly 补丁生成需要基于项目,不想去改 tinker 源码去除 release 依赖,于是~ 自己新建了一个空项目,替换 tinkerpatch 生成的 apk 路径为指定的 apk 路径,从而用于减少补丁生成时间。

比如我分享的就做了支持,可以删除一些官方的 c++代码,使补丁生成得更快一些。

脚本以及项目源码

Github:

https://github.com/Anr-C/BuglyHotfix-AndResDemo


本站由以下主机服务商提供服务支持:

0条评论

发表评论