Android热修复方案研究(含AOP知识)

版权声明:本文为博主原创文章,欢迎转载,转载请注明博主和原文链接。 https://blog.csdn.net/u014077888/article/details/50580477

一、概述

  以前对Android 的热修复方案有一些了解,知道几个有名的开源方案,原理大概理解,但是没有整理汇总一下,上周听了玉斌大哥在公司做的分享后,感受颇多觉得写篇博客记一下,不能浪费。

  热修复是指在不发新版的情况下修复线上的紧急 bug,长久以来做移动开发的人员都羡慕做后端或者做 web 前端的人员可以随时发布来修复 bug。那么 移动开发有没有这样的方案呢?

  1. Hybrid

如:PhoneGap、国内的 AppCan。本质还是利用了 Web 开发在更新上的优势,但体验不如 Native。

  1. ReactNative

像Web一样可以随时发布,又有原生的体验,所以这个方案优势挺大,有很多项目都在尝试。

  1. Native HotFix

可以不用发新版本,直接下发补丁包修复 bug,纯原生体验。

iOS有JSPatch、Android 有 AndFix、Nuwa、DroidFix 、HotFix等

二、Android Native HotFix

下面按照原理分类为

(一) Dex分包方案(QZone的方案)(如 NuwaDroidFixHotFixRocooFix

  原理:如果多个 dex 里有相同的class,那么虚拟机会优先使用最先加载的 dex 中的 class。所以将有问题的类打包到一个新dex(pathc.dex),然后下发给 app,app 启动时将该patch.dex插入到其他 dex 前面优先加载。就达到了 替换有问题 class 的目的。

这里有两个难点:

1. 改变dex加载顺序

2. class_ispreverified

错误信息:java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation

原因

这里写图片描述

  dex转化成odex时会执行dvmVerifyClass进行类的校验,如 B的引用和B都在一个 dex 里,则 B 会被打上class_ispreverified标记,但是我们优先加载 A’,A’和B在两个不同的 dex,但 B 却打上了class_ispreverified标识所以就报错了。

  两个相关联的类在不同的dex中并且有class_ispreverified就会报错,谷歌MultiDex拆分dex的时候就会处理这种情况如果A 和 B 不在同一个 Dex 就不会给 B标记class_ispreverified。

  但我们是热修复,patch.dex 是事后打的 dex,class.dex已经安装的用户手机里了,所以我们无法修改 B 的标识。

解决方案:

这里写图片描述

我们发布版本打包时,将所有的类都引用一个单独dex的一个类,这个所有的类都不会标记class_ispreverified了,当然这也就失去了class_ispreverified的意义,会有性能上的影响。

更新:还有一个巧妙的方案来防止class_ispreverified,就是在工程里放入一个android.jar 已经存在的类,这样会导致multiple class declarations 从而不会再对class 校验。比如使用com.android.internal.util.Predicate,这样类的特点是特别简单没有额外引用,然后它从android api 8 就存在了。

QZone 安卓App热补丁动态修复技术介绍
深度理解Android InstantRun原理以及源码分析

(二)smali diff方案(阿里的方案)(以 AndFix为例)

1. 原理

这里写图片描述

2. 方法替换(AOP)

  搞过 Android Aop 的同学知道,我们可以在执行一个方法的前插入另一个方法,运用这个思路,我们可以把有 bug 的方法替换成我们下发的新方法。

下边是 AndFix 使用 JNI hook 方法的一段代码,供大家理解。另一个实现AOP 的开源方案
dexposed

这里写图片描述

Android AOP 常用有的方法有 JNI HOOK 和 静态织入。
常用的静态织入的库有:
1、APT 是代码生成,java文件 生成 java文件。使用的annotation类型是SOURCE.
2、Aspectj是静态织入。静态织入:指在编译时期就织入,即:编译出来的class文件,字节码就已经被织入了。AspectJ就是一个编译器,把java文件编译成想要的class文件。使用的annotation类型是SOURCE.
3、ASM或者Javassist 是Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。使用的annotation类型是class.

3. ApkPatch tool

  AndFix 提供了一个打包 patch 的工具,这个工具将旧Apk和新Apk做 diff 得出一个patch apk。

熟悉增量更新的同学应该知道,增量更新也是做 diff 求增量包,但是他是在字节码层面上求增量包,增量包+旧 apk 合并成新 apk。

  而ApkPatch是通过对比两个 apk 反编译后的 smali 文本文件来求 diff 的。所以他是从 class 层面上求 diff。得到的patch就是标准的 dex 包。 该 dex 可直接被 load。

4. 以前注意事项

  1. 一个 patch 包要修复哪些类的哪些方法,这个路径信息会被打包到 patch.apk 的META-INFO里

  2. patch.apk在打包时要使用相同的签名。

  3. 混淆的情况:打包 patch.apk 时要使用主apk混淆时导出的 mapping 文件,防止主apk和patch.apk 由于混淆而无法对应相应的类和函数。

  4. 目前 apkpatch 没有开源,还有一些小 bug,如不支持 mulitdex。虽然没有开源但还是有方法修复的,比如反编译导出源码、还有 javasist 神器。具体怎么修复以后再说,也可能官方会修复。

(三)微信的方案

AndFix 由于影响正向开发过程(只能修改方法、不能修改 field、不能新增类等问题)、库本身难于维护以及发现的莫名其妙的 bug(不同rom对dex的校验机制不一致)。
nuwa 仅支持更新 Java 代码,不能更新资源和 so 文件。

全量替换新的Dex。即我们完全使用了新的Dex,那样既不出现Art地址错乱的问题,在Dalvik也无须插桩。当然考虑到补丁包的体积,我们不能直接将新的Dex放在里面。但我们可以将新旧两个Dex的差异放到补丁包中,最简单我们可以采用BsDiff算法。

Android Patch 方案与持续交付

微信Android热补丁实践演进之路

Android 混合编译的坑

开源项目Tinker_imitator

阅读更多

没有更多推荐了,返回首页