介绍

  在介绍Bugly之前,需要先向大家简单介绍下一些热更新的相关内容。当前市面的热补丁方案有很多,其中比较出名的有阿里的AndFix、美团的Robust以及QZone的超级补丁方案。但它们都存在无法解决的问题,这也是Tinker面世的原因。Tinker目前已运行在微信的数亿Android设备上,相对于其它热更新方案,Tinker相对比较优秀。

什么是Tinker

  Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。


Tinker

QZone

AndFix

Robust

类替换

yes

yes

no

no

So替换

yes

no

no

no

资源替换

yes

yes

no

no

全平台支持

yes

yes

yes

yes

即时生效

no

no

yes

yes

性能损耗

较小

较大

较小

较小

补丁包大小

较小

较大

一般

一般

开发透明

yes

yes

no

no

复杂度

较低

较低

复杂

复杂

gradle支持

yes

no

no

no

Rom体积

较大

较小

较小

较小

成功率

较高

较高

一般

最高

总的来说:

  1. AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;
  2. Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;
  3. Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。

Tinker的已知问题

  1. Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity);
  2. 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码
  3. 在Android N上,补丁对应用启动时间有轻微的影响;
  4. 不支持部分三星android-21机型,加载补丁时会主动抛出"TinkerRuntimeException:checkDexInstall failed";
  5. 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

Bugly的介绍

  腾讯 Bugly,是腾讯公司为移动开发者开放的服务之一,面向移动开发者提供专业的 Crash 监控、崩溃分析等质量跟踪服务。Bugly 能帮助移动互联网开发者更及时地发现掌控异常,更全面的了解定位异常,更高效的修复解决异常。

Bugly目前包括三大服务:

  1. 异常上报;
  2. 运营统计;
  3. 应用升级(包括全量升级和热更新)。

Bugly的热更新

  热更新能力是Bugly为解决开发者紧急修复线上bug,而无需重新发版让用户无感知就能把问题修复的一项能力。Bugly目前采用微信Tinker的开源方案,开发者只需要集成我们提供的SDK就可以实现自动下载补丁包、合成、并应用补丁的功能,我们也提供了热更新管理后台让开发者对每个版本补丁进行管理。

为什么使用Bugly热更新?

  • 无需关注Tinker是如何合成补丁的
  • 无需自己搭建补丁管理后台
  • 无需考虑后台下发补丁策略的任何事情
  • 无需考虑补丁下载合成的时机,处理后台下发的策略
  • Bugly提供了更加方便集成Tinker的方式
  • Bugly通过HTTPS及签名校验等机制保障补丁下发的安全性
  • 丰富的下发维度控制,有效控制补丁影响范围
  • Bugly提供了应用升级一站式解决方案

Bugly热更新SDK的集成

集成Bugly热更新的详细操作可以参考官网的文档:


在这里就不一步一步地结合如何集成SDK,总的来说有一下几个步骤:
  1. 工程根目录build.gradle添加tinker-support插件依赖;
  2. app module的build.gradle添加SDK依赖;
  3. app目录下新建tinker-support.gradle文件,文件的内容文档有提供;
  4. 初始化SDK,改造application,虽然也可以不改造application,但是兼容性可能会出现问题,官方推荐改造application;
  5. AndroidManifest.xml配置相关权限,如果仅仅只是使用热更新而不需要使用到全量升级功能,不需要配置BetaActivity和FileProvider
  6. 混淆配置,在proguard-rules.pro文件中添加Bugly的混淆规则

集成热更新所遇到的相关问题:

  • 文档中提及使用插件的最新版本可以通过使用lastest.release拉取,如图:


image

但我添加依赖的时候,如下:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        // tinkersupport插件, 其中lastest.release指拉取最新版本,也可以指定明确版本号,例如1.0.4
        classpath "com.tencent.bugly:tinker-support:lastest.release"
    }
}复制代码

报了找不到该依赖的错误,原来是文档写错了,lastest应改成latest才对。

  • tinker-support.gradle中缺少supportHotplugComponent这项配置导致打补丁包的时候出现错误

  视频介绍和文档中都没有提及到tinker-support.gradle文件tinkerSupport { }中需要添加多supportHotplugComponent这项配置,按照文档来打补丁的时候,会出现如下错误:


image

看了错误提示,老是提示我说manifest was changed,可是我根本没有改动到manifest,一直很纳闷,直到看了demo后,才发现需要在tinkerSupport { }中,添加supportHotplugComponent这项配置,代码如下:

tinkerSupport { 

    ...

    supportHotplugComponent = true

}复制代码
  • thinkerId 的指定

  thinkerId需要是唯一的,在生成基准包和补丁包时都需要改动,官方推荐时间用git版本号或版本名生成,这里我自己使用的是versionName_MM-dd-HH-mm-ss这种格式,版本名+时间戳,比如1.0.5-1121-11-33-20,这和bakApk下生成基准包目录的时间戳类似,它是app-1121-11-33-20,这样查看起来一路了然,查看补丁包的YAPATCH文件也很清晰:

Created-Time: 2017-11-21 11:37:15.673
Created-By: YaFix(1.1)
YaPatchType: 2
VersionName: 1.0.5
VersionCode: 5
From: 1.0.5-1121-11-36-06
To: 1.0.5-1121-11-37-15复制代码

From 是基准包的tinkerId
To 是当前补丁包的tinkerId

生成上述格式tinkerId的代码如下:

tinkerSupport { 

    ...

    tinkerId = "${verName()}-${buildTime()}"

    ...

}

//获取版本名
def verName() {
    def versionPropsFile = file("../version.properties")
    if (versionPropsFile.canRead()) {
        Properties versionProps = new Properties()
        versionProps.load(new FileInputStream(versionPropsFile))

        return versionProps['VERSION_NAME']
    } else {
        throw new GradleException("Could not read gradle.properties")
    }
}

//获取构建时间
def buildTime() {
    return new Date().format("MMdd-HH-mm-ss", TimeZone.getTimeZone("GMT+8"))
}复制代码

需要在工程根目录下,和gradle.properties文件同目录下,新建version.properties文件,用于保存当前app的versionCode和versionName,文件的内容:

VERSION_NAME=1.0.5
VERSION_CODE=4复制代码

既然将versionCode和versionName配置在properties文件中,那么app module的gradle文件中,defaultConfig{}中指定版本名和版本号直接使用该配置

android {

    ...

    defaultConfig {
        ...

        versionCode verCode()
        versionName verName()

       ...
    }
 }复制代码

获取版本号的方法verCode(),代码如下:

//获取版本号
def verCode() {
    def versionPropsFile = file("../version.properties")
    if (versionPropsFile.canRead()) {
        Properties versionProps = new Properties()
        versionProps.load(new FileInputStream(versionPropsFile))

        def int verCode = versionProps['VERSION_CODE'].toInteger()
        return verCode;
    } else {
        throw new GradleException("Could not read gradle.properties")
    }
}复制代码