关于Andriod热更新

项目场景:

  • 紧急发现了一个bug,影响用户体验,阻断项目流程。这个时候,只能紧急发布一个强制更新的新版本,让用户升级。
  • 最近百团大战开始。需要增加一个活动弹窗入口,越快越好。这个时候,只能紧急发布一个强制更新的新版本,让用户升级。

存在需求:

1
可不可以不让用户重新安装就可以解决上述场景?

什么是热更新:

1
让应用能够在无需重新安装的情况实现更新,帮助应用快速建立动态修复能力。

从上面的定义来看,热补丁节省Android大量应用市场发布的时间。同时用户也无需重新安装,只要上线就能无感知的更新。

热更新的原理:

现在市面上主流的几大热更新技术:

  • 淘宝 Dexposed
  • 支付宝 AndFix
  • Qzone 超级热补丁
  • 微信 Tinker

Dexposed

基于 Xposed 实现的无侵入的运行时 AOP (Aspect-oriented Programming) 框架,可以实现在线修复 Bug,修复粒度方法级别,这也就意味着我们没有办法进行类的增减操作。而且由于对 ART 虚拟机不支持,导致其对 Android 5.0、6.0 均不支持,使用局限性太大。

AndFix

native hook 方式,其核心部分在 JNI 层对方法进行替换,替换有问题的方法,修复粒度方法级别,无法在类中新增和删减字段,可以做到即时生效。也就是运行时生效。但是因为它的核心部分在JNI,所以会出现很多适配兼容的问题。因为国内的rom厂商多才多艺.

超级热补丁

使用新的 ClassLoader 加载 patch.dex,hack 默认的 ClassLoader,替换有问题的类,修复粒度类级别,一般无法做到即时生效,需要在应用下一次启动时生效。但是在art虚拟机中,如果改变了类变量,和方法名,有可能导致内存错乱的问题,没有开源这个项目。但在github上的Nuwa采用了相同的方式,这个是开源。

Tinker

dex 文件全量替换,基于 DexDiff 技术,对比修复前后的 dex 文件,生成 patch.dex,再根据 patch.dex 更新有问题的 dex 文件。简单来说,在编译时通过新旧两个Dex生成差异patch.dex。在运行时,将差异patch.dex重新跟原始安装包的旧Dex还原为新的Dex。这个过程可能比较耗费时间与内存,所以我们是单独放在一个后台进程:patch中。为了补丁包尽量的小,微信自研了DexDiff算法,它深度利用Dex的格式来减少差异的大小。

热更新方案的比较:

Tables Tinker Qzone Andfix Dexposed
类替换 yes yes no no
lib替换 yes no no no
资源替换 yes yes no no
全平台支持 yes yes yes no
即时生效 no no yes yes
性能损耗 较小 较大 较小 较小
补丁包大小 较小 较大 一般 一般
开发透明 yes yes no no
复杂度 较低 较低 复杂 复杂
gradle支持 yes yes no no
接口文档 丰富 一般 一般 较少
占rom体积 较大 较小 较小 较小
成功率 较好 最高 一般 一般

热更新的使用场景:

热补丁技术也可以理解为一个动态修改代码与资源的通道,它适合于修改量较少的情况。

我们看一下微信的版本升级的情况:

Tables 普通升级 布丁升级
数据大小 33M 145K
更新速度 10天 1天(70%)
自动升级 wifi 移动网络

以Android用户的升级习惯,即使是相对活跃的微信也需要10天以上的时间去覆盖50%的用户。使用补丁技术,我们能做到1天覆盖70%以上。这也是基于补丁体积较小,可以直接使用移动网络下载更新。

热更新使用限制

  • 补丁只能针对单一客户端版本,随着版本差异变大补丁体积也会增大;
  • 补丁不能支持所有的修改,例如AndroidManifest;
  • 补丁无论对代码还是资源的更新成功率都无法达到100%。

如何在一个项目中增加热更新功能?

  • 在工程目录 build.gradle 文件中添加插件依赖。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    // tinkersupport插件,其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.0.8
    classpath "com.tencent.bugly:tinker-support:latest.release"
    }
    }
  • 在app module 下的build.gradle 文件中添加 配置

    1
    2
    3
    4
    5
    6
    dependencies {
    compile "com.android.support:multidex:1.0.1"
    compile 'com.tencent.bugly:crashreport_upgrade:latest.release'
    }
    // 依赖插件脚本
    apply from: 'tinker-support.gradle'
  • 在同级目录下创建 tinker-support.gradle

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    apply plugin: 'com.tencent.bugly.tinker-support'

    def bakPath = file("${buildDir}/bakApk/")

    /**
    * 此处填写每次构建生成的基准包目录
    */
    def baseApkDir = "app-0912-17-04-44"
    /**
    * 对于插件各参数的详细解析请参考
    */
    tinkerSupport {

    // 开启tinker-support插件,默认值true
    enable = true

    //自动生成tinkerId,无须关注此。默认为false
    //autoGenerateTinkerId = true

    tinkerEnable = true

    // 指定归档目录,默认值当前module的子目录tinker
    autoBackupApkDir = "${bakPath}"

    // 是否启用覆盖tinkerPatch配置功能,默认值false
    // 开启后tinkerPatch配置不生效,即无需添加tinkerPatch
    overrideTinkerPatchConfiguration = true

    // 编译补丁包时,必需指定基线版本的apk,默认值为空
    // 如果为空,则表示不是进行补丁包的编译
    // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/com.nongfenqi.sherlock-release-v2.3.2_32.apk"
    // 对应tinker插件applyMapping
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"

    // 对应tinker插件applyResourceMapping
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"

    // 构建基准包和补丁包都要指定不同的tinkerId,并且必须保证唯一性
    tinkerId = "2.3.2-0912-patch"

    // 构建多渠道补丁时使用
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

    // 是否启用加固模式,默认为false.(tinker-spport 1.0.7起支持)
    // isProtectedApp = true

    // 是否开启反射Application模式
    enableProxyApplication = false

    }

    /**
    * 一般来说,我们无需对下面的参数做任何的修改
    * 对于各参数的详细介绍请参考:
    * 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 = 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"
    // path = "/usr/local/bin/7za"
    }
    buildConfig {
    keepDexApply = false
    //tinkerId = "1.0.1-base"
    //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt" // 可选,设置mapping文件,建议保持旧apk的proguard混淆方式
    //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt" // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
    }
    }
  • 权限配置以及activity配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_LOGS" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>


    <activity
    android:name="com.tencent.bugly.beta.ui.BetaActivity"
    android:theme="@android:style/Theme.Translucent" />
  • 混淆配置

1
2
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}

本文转载自:https://www.dazhuanlan.com/2019/12/31/5e0b56c7943ed/

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

0%