Tinker + Bugly + Jenkins 爬坑之路

前阵子 Android 端的线上崩溃比较多,热修复被提上日程。实现方案是 Tinker,Jenkins 打包,最后补丁包上传到 Bugly 进行分发。主要在 Jenkins 打包这一块爬了不少坑,现记录下来,供大家参考。

1. Tinker + Bugly热修复实现

首先是本地实现,按照官方文档,只要一步一步按照文档来,这个步骤还是比较容易的,这里就不再赘述了,不懂的可以先参考官方文档:Bugly Android热更新使用指南Bugly Android热更新详解。这里贴一下接入流程:

  • 打基准包安装并上报联网(注:填写唯一的 tinkerId)
  • 对基准包的 bug 修复(可以是 Java 代码变更,资源的变更)
  • 修改基准包路径、修改补丁包 tinkerId、mapping 文件路径(如果开启了混淆需要配置)、resId 文件路径
  • 执行 buildTinkerPatchRelease 打 Release 版本补丁包
  • 选择 app/build/outputs/patch目录 下的补丁包并上传(注:不要选择 tinkerPatch 目录下的补丁包,不然上传会有问题
  • 编辑下发补丁规则,点击立即下发
  • 杀死进程并重启基准包,请求补丁策略( SDK 会自动下载补丁并合成)
  • 再次重启基准包,检验补丁应用结果
  • 查看页面,查看激活数据的变化

这里说一下使用指南中的第三步:初始化 SDK,我这里使用的是 enableProxyApplication = false 的方式,原本想用 enableProxyApplication = true 的这种比较灵活的方式,但是程序编译报错,没时间去深究报错的原因,加上直接继承的方式接入也没什么代价,就没管是为什么了,知道原因的可以顺手告知下。 ┑( ̄Д  ̄)┍

一通撸下来还是比较容易的,完成代码的接入后,先打个包(基准包),安装到手机上运行一遍,使程序联网上报到 Bugly。之后,再按照打基准包的基线版本,修改 tinker-support.gradle 文件中的 baseApkDir 参数,然后就可以打补丁包了。

2. 结合 Jenkins 所遇到的坑

先说明一下我司使用 Jenkins 打包 apk 的背景知识。Jenkins 打包 apk 使用的是 Ant 插件,打包脚本由于公司项目的原因,不方便展示出来,大家如果有疑问的话,可以在评论里说明,本人会私下里帮助大家解决。

下面爬坑 /(ㄒoㄒ)/~~

坑1 ? 打补丁包时,基准包哪里找?

由于公司 Jenkins 的打包策略是,在构建之前,先执行 clean 命令,这也就意味着,像本地打包一样在 app/build/bakApk/app-xxxx-xx-xx-xx 目录下找到基准包已是不可能。那怎么办,没有基准包怎么打增量包?苦思良久,愚笨的我最终想到,在项目工程路径下创建一个文件夹,要打增量包时,将基准包拷贝到该文件夹,然后上传 SVN。这时,旁边同学来了句:可以找运维同学,双方约定一个目录,打基准包时将基准包由脚本拷贝过去,打补丁包时从约定的目录取就行((? _ ?) 我咋就想不到...)。

然后屁颠屁颠的跑去找运维同学,沟通后发现,Jenkins 每次打包都会在 Jenkins 目录下的 /jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 保存一份 apk 文件的副本。路径中 构件编号 如图所示:

接下来,打补丁包时将 tinker-support.gradle 文件中的 baseApkDir 参数修改为 /jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 即可。代码如下:

/**
 * 此处填写每次构建生成的基准包目录,注意变量要自定义
 */
def baseApkDir = "${rootProject.projectDir}/../../jobs/${pipeline名称}/builds/${baseApkBuildNumber}/archive/app/build/outputs/apk/kungeek/release"

坑2 ? Linux 下文件拷贝通配符问题

由于构建基准包的同时生成的 mapping 文件(如果开启了混淆需要配置)、resId 文件在构建补丁包时也需要用到,所以,在构建基准包时,需要将这两个文件拷贝到 /jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 目录下,拷贝代码如下:

<!--复制 tinker 生成的文件(apk文件、mapping.txt、R.txt)-->
<copy todir="../../../../jobs/pipeline名称/builds/${env.BUILD_NUMBER}/archive/app/build/outputs/apk/kungeek/release/" flatten="true">
    <fileset dir="${android.root}/app/build/bakApk/">
        <include name="*/*" />
    </fileset>
</copy>

注1:代码中相对路径问题读者有疑问的话,麻烦再评论去提问。

注2:代码中构建编码使用到了 Jenkins 的环境变量,需要先在 Ant 的构建脚本文件的 project 的标签下添加 <property environment="env"/> 来导入。

这里遇到的坑是:因为 Tinker 构建的 apk 文件是存放在 app-xxxx-xx-xx-xx 目录下,所以需要使用通配符来辅助复制文件,运维同学原本是想将通配符加到 fileset 中形成以后完整的路径,经过一段痛苦的尝试以及百度后发现,通配符只能在 include 标签中使用。(ノへ ̄、)

坑3 ? 构建补丁包完成后找不到生成的补丁包?

踩过前面一个一个的坑,终于在 Jenkins 上打了基准包之后,/jobs/pipeline名称/builds/构建编号/archive/app/build/outputs/apk/kungeek/release/ 目录下有了 基准包 apk 文件mapping 文件resId 文件

接下来,我以为,只需要配置好基准包的构件编号等相关配置参数,再构建补丁包就没问题了。然后 Jenkins 在构建好补丁包 apk 文件后,展示成果时报出的 apk 文件未找到 给了我当头一棒,依然失败。挫败感油然而生~~~

之后,经运维同学确认,Jenkins 构建期间是有在 app/build/outputs/patch 目录下生成 patch_signed_7zip.apk 文件的,但是构建完成之后,又没了。然后我试着看了下构建过程中执行的命令,长这样的:

sh gradlew clean buildTinkerPatchRelease  --stacktrace
sh gradlew checklist

执行了 buildTinkerPatchRelease 后,还执行的 checklist 任务,难道是执行 checklist 时把 patch 给清空了,之后我尝试把这个命令注释掉,再次打补丁包时成功。果然是这个 checklist 惹的事啊,事后发现,打补丁包后,再次执行 gradle task,基本都会清空 patch 目录,这是个坑,大家记得避免。

坑4 ? 一个项目中多个 application 时,打补丁包不成功?

我们知道,在 Android Studio 中,一个 project 可以有多个 module,包括 application 类型的 module,一般情况下,执行 gradlew assembleRelease 任务会将所有的 APP 都打包,这里打基准包也没问题,但是打补丁包时就不行了,只能成功一个。

这里提供分开打包一个方案:在每个 application 的 build.gradle 中配置 productFlavors,且每个 application 的命名都得不一样,这样,针对不同的 APP 就会产生不同的构建 task,比如:在 A 的 build.gradle 中配置名为 a_app,则回产生一个名为 buildTinkerPatchA_appRelease 的 task,最终使用此 task 来打补丁包即可。

那么问题来了,最终打包的形式是什么呢?是这样?

sh gradlew buildTinkerPatchA_appRelease buildTinkerPatchA_appRelease

还是这样?

sh gradlew buildTinkerPatchA_appRelease
sh gradlew buildTinkerPatchA_appRelease

都不是,这两种方式其实和不配置 productFlavors 的打包方式是一样的,那么如何打包呢?

答案是在 Ant 的打包脚本中,执行多次打包,关键代码如下:

<!--构建APP a-->
<exec dir="." executable="bash" failonerror="false">
    <arg value="generated_apk_hotfix.sh"/>
    <arg value="buildTinkerPatchApp_aRelease"/>
</exec>
?
<!--构建APP b-->
<exec dir="." executable="bash" failonerror="false">
    <arg value="generated_apk_hotfix.sh"/>
    <arg value="buildTinkerPatchApp_bRelease"/>
</exec>

构建脚本 generated_apk_hotfix.sh 文件关键代码如下:

#!/bin/sh
command=$1;
?
# 增量包需分开打包,否则会失败
sh gradlew ${command} --stacktrace

3. 总结

上面说到的坑只有 4 点,但实际上也遇到过挺多小问题的,但那些就不用多说了,很容易解决。

最后,总结一下结合 Jenkins 构建补丁包的思路。

首先,约定好基线版本的基准包 apk 包、mapping 文件、R.txt 文件的存放路径,打基准包时将这三个文件存入该目录。如果跟本文一样存放在 Jenkins 的 pipeline 构建目录下的话,记得要调整 pipeline 的清理策略,否则等需要打补丁包的时候,发现基线版本 apk 包什么的被清理掉就尴尬了,我这里是考虑到重复利用空间,所以放入此目录下。

其次,通过约定的路径,找到基准包、mapping 文件、R.txt 文件,打补丁包。这里需要确定一个找到基准包的策略,比如,我这里是通过构建编号来匹配存放基准包的路径,然后通过固定命名格式(如:app_release_版本号.apk)来匹配基准包以及 mapping 文件和 R.txt 文件,如此下来,我只需要确定基线版本的版本号和构建编号即可。

最后,贴一下我最终的 tinker-support.gradle 文件代码内容,大家有需要的可以参考:

apply plugin: ‘com.tencent.bugly.tinker-support‘
?
def bakPath = file("${buildDir}/bakApk/")
?
/** 基准包的 Jenkins 构建编号*/
def baseApkBuildNumber = project.property("baseApkBuildNumber")
/** 基准包的版本号*/
def baseApkVersion = project.property("baseApkVersion")
?
/**
 * 此处填写每次构建生成的基准包目录
 */
def baseApkDir = "${rootProject.projectDir}/../../jobs/Android_Trunk/builds/${baseApkBuildNumber}/archive/app/build/outputs/apk/release"
?
/** 基准包的 apk 文件名*/
def baseApkFileName = "app-v${baseApkVersion}"
?
/**
 * 对于插件各参数的详细解析请参考
 */
tinkerSupport {
?
    // 开启tinker-support插件,默认值true
    enable = true
?
    // tinkerEnable功能开关
    tinkerEnable = true
?
    // 指定归档目录,默认值当前module的子目录tinker
    autoBackupApkDir = "${bakPath}"
?
    autoGenerateTinkerId = true
?
    // 打基准包时生成 R.txt、mapping.txt 文件名的前缀
    // rootProject.ext.android_version 指打包时的版本号
    targetFileNamePrefix = "app-v${rootProject.ext.android_version}"
?
    // 是否启用覆盖tinkerPatch配置功能,默认值false
    // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
    overrideTinkerPatchConfiguration = true
    // 编译补丁包时,必需指定基线版本的apk,默认值为空
    // 如果为空,则表示不是进行补丁包的编译
    // @{link tinkerPatch.oldApk }
    baseApk = "${baseApkDir}/${baseApkFileName}.apk"
?
    // 对应tinker插件applyMapping
    baseApkProguardMapping = "${baseApkDir}/${baseApkFileName}-mapping.txt"
?
    // 对应tinker插件applyResourceMapping
    baseApkResourceMapping = "${baseApkDir}/${baseApkFileName}-R.txt"
?
    tinkerId = "base-1.0.1"
?
//    buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
    // 是否开启加固模式,默认为false
    // isProtectedApp = true
?
    // 是否开启反射Application模式
    enableProxyApplication = false
?
    supportHotplugComponent = true
?
}
?
/**
 * 一般来说,我们无需对下面的参数做任何的修改
 * 对于各参数的详细介绍请参考:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    //oldApk ="${bakPath}/${appName}/app-release.apk"
?
    // tinkerEnable功能开关
    tinkerEnable = true
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }
?
    res {
        pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }
?
    packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
    }
    buildConfig {
        keepDexApply = false
    }
}

然后是维护在 gradle.properties 文件中的两个变量:

# 打增量包时基准包的 Jenkins 构建编号
baseApkBuildNumber = 1
# 打增量包时基准包的版本号
baseApkVersion = 1.0.0.197094

原文地址:https://www.cnblogs.com/cspecialy/p/9125861.html

时间: 2024-11-06 06:58:46

Tinker + Bugly + Jenkins 爬坑之路的相关文章

多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面大致的了解了Thread的一些方法和属性下面对一些方法进行运用看看具体效果<下面可能还是会贴很多的源代码,其实我是拒绝的,我只想贴每个方法的代码,但是有时候看到一个方法里面有调用了方法,但是笔者有没有给出来,很蛋疼,有种爽到一半的感觉,所以我还是会把它贴出来,希望一次就能挖到底,不论有没有全懂,但至

vue 爬坑之路---can&#39;t resolve &#39;sass-loader&#39;

环境设置好以后 本以为可以开心的写代码了, 谁料到如下报错,大概意思就是不能编译 sass-loader 这个玩意. 那怎么办?? 安装依赖,不然能怎么办? 第一个依赖: npm install sass-loader 第二个依赖: npm install node-sass 这样安装了之后,然后npm run dev  ,世界一片祥和~ vue 爬坑之路---can't resolve 'sass-loader' 原文地址:https://www.cnblogs.com/liuguoying/

安卓易学,爬坑不易——腾讯老司机的RecyclerView局部刷新爬坑之路

针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验.目前功能还在免费开放中. 点击地址:http://wetest.qq.com/cube立即体验! 作者:Hoolly,腾讯移动客户端开发工程师. 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处 WeTest导读 安卓开发者都知道,RecyclerView比ListView要灵活的多,但不可否认的里面的坑也同样埋了不少人.下面让我们看看腾讯开发工程

Vue 爬坑之路(二)—— 组件之间的数据传递

Vue 的组件作用域都是孤立的,不允许在子组件的模板内直接引用父组件的数据.必须使用特定的方法才能实现组件之间的数据传递. 首先用 vue-cli 创建一个项目,其中 App.vue 是父组件,components 文件夹下都是子组件. 一.父组件向子组件传递数据 在 Vue 中,可以使用 props 向子组件传递数据. 子组件部分: 这是 header.vue 的 HTML 部分,logo 是在 data 中定义的变量. 如果需要从父组件获取 logo 的值,就需要使用 props: ['lo

vue爬坑之路3

插值 文本 数据绑定最常见的形式就是使用"Mustache"语法(双大括号)的文本差值: <span>Message:{{msg}}</span> Mustache标签将会被替代为对应数据对象上msg属性的值.无论何时,绑定的数据对象上msg属性发生了改变,插值处的内容都会更新. 通过使用v-once指令,能一次性地插值,当数据改变时,插值处的内容不会更新.但要注意这会影响到该节点上所有的数据绑定: <span v-once>This will ne

多线程爬坑之路--并发,并行,synchonrized同步的用法

一.多线程的并发与并行: 并发:多个线程同时都处在运行中的状态.线程之间相互干扰,存在竞争,(CPU,缓冲区),每个线程轮流使用CPU,当一个线程占有CPU时,其他线程处于挂起状态,各线程断续推进. 并行:多个线程同时执行,但是每个线程各自有自己的CPU,不存在CPU资源的竞争,他们之间也可能存在资源的竞争. 并发发生在同一段时间间隔内,并行发生在同一时刻内.并发执行的总时间是每个任务的时间和,而并行则取决于最长任务的时间. 下面看一下A,B两个任务在并行和并发情况下是怎么执行的:[不考虑其他资

vue爬坑之路2

构造器 每个vue.js应用都是通过构造函数Vue穿件一个Vue的根实例启动的: var vm = new Vue({ //选项 }) 在实例化Vue时,需要传入一个选项对象,他可以包含数据,模板,挂在元素,方法,生命周期钩子等选项. vue构造器是可扩展的,实际上,所有的vue.js组件其实都是被扩展的vue实例. var MyComponent = Vue.extend({ //扩展选项 }) //所有的'MyComponent'实例都将以预定义的扩展选项被创建 var myComponen

多线程爬坑之路-ThreadLocal源码及原理的深入分析

ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its<tt>get</tt> or <tt>s

React Native 爬坑之路

1.react 基础 (创建组件及在浏览器上渲染组件) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <!-- 1.react.js是React的核心库 --> <script src="js/react.min.js" chars