Android 热修复原理篇及几大方案比较

来源:互联网 发布:剑灵秦夕颜捏脸数据图 编辑:程序博客网 时间:2024/06/11 23:53
      热修复说白了就是”即时无感打补丁”,比如你们公司上线一个app,用户反应有重大bug,需要紧急修复。2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案.如果按照通常做法,那就是程序猿加班搞定bug,然后测试,重新打包并发布。这样带来的问题就是成本高,效率低。于是,热修复就应运而生.一般通过事先设定的接口从网上下载无Bug的代码来替换有Bug的代码。这样就省事多了,用 户体验也好。目前热修复尽管有很多坑,做了好多工作,可能吃力不讨好,各种适配可能还是没修复线上的有些Bug。不过呢,对于一个产品有热修复毕竟是件好事。尤其是对于一个有众多用户的app(,一个bug不只是影响到几个几十个用户,一些创业公司的APP,崩溃或者bug可能直接导致用户卸载和永不使用,所以,就冲它有不用发版也可以解决我们线上的bug,我们的app也要适当考虑加入热修复。


我们知道Android系统也是仿照java搞了一个虚拟机,不过它不叫JVM,它叫Dalvik/ART VM他们还是有很大区别的(这是不是我们的重点, 点开是个拓展阅读)。我们只需要知道,Dalvik/ART VM 虚拟机加载类和资源也是要用到ClassLoader,不过Jvm通过ClassLoader加载的class字节码,而Dalvik/ART VM通过ClassLoader加载则是dex。

Android的类加载器分为两种,PathClassLoader和DexClassLoader,两者都继承自BaseDexClassLoader

PathClassLoader代码位于libcore\dalvik\src\main\Java\dalvik\system\PathClassLoader.java 
DexClassLoader代码位于libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java 
BaseDexClassLoader代码位于libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java

  • PathClassLoader
  • 用来加载系统类和应用类

  • DexClassLoader

    用来加载jar、apk、dex文件.加载jar、apk也是最终抽取里面的Dex文件进行加载.

    这里写图片描述

2.热修复机制

热修复就是利用dexElements的顺序来做文章,当一个补丁的patch.dex放到了dexElements的第一位,那么当加载一个bug类时,发现在patch.dex中,则直接加载这个类,原来的bug类可能就被覆盖了


看下PathClassLoader代码

public class PathClassLoader extends BaseDexClassLoader {    public PathClassLoader(String dexPath, ClassLoader parent) {        super(dexPath, null, null, parent);    }    public PathClassLoader(String dexPath, String libraryPath,            ClassLoader parent) {        super(dexPath, null, libraryPath, parent);    }} 

DexClassLoader代码

public class DexClassLoader extends BaseDexClassLoader {    public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);    }}

两个ClassLoader就两三行代码,只是调用了父类的构造函数.

public class BaseDexClassLoader extends ClassLoader {    private final DexPathList pathList;    public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(parent);        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();        Class c = pathList.findClass(name, suppressedExceptions);        if (c == null) {            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);            for (Throwable t : suppressedExceptions) {                cnfe.addSuppressed(t);            }            throw cnfe;        }        return c;    }

在BaseDexClassLoader 构造函数中创建一个DexPathList类的实例,这个DexPathList的构造函数会创建一个dexElements 数组

public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {        ...         this.definingContext = definingContext;        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();        //创建一个数组        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);        ...     }

然后BaseDexClassLoader 重写了findClass方法,调用了pathList.findClass,跳到DexPathList类中.

/* package */final class DexPathList {    ...    public Class findClass(String name, List<Throwable> suppressed) {            //遍历该数组        for (Element element : dexElements) {            //初始化DexFile            DexFile dex = element.dexFile;            if (dex != null) {                //调用DexFile类的loadClassBinaryName方法返回Class实例                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);                if (clazz != null) {                    return clazz;                }            }        }               return null;    }    ...} 

会遍历这个数组,然后初始化DexFile,如果DexFile不为空那么调用DexFile类的loadClassBinaryName方法返回Class实例. 
归纳上面的话就是:ClassLoader会遍历这个数组,然后加载这个数组中的dex文件. 
而ClassLoader在加载到正确的类之后,就不会再去加载有Bug的那个类了,我们把这个正确的类放在Dex文件中,让这个Dex文件排在dexElements数组前面即可.

CLASS_ISPREVERIFIED问题

根据QQ空间谈到的在虚拟机启动的时候,在verify选项被打开的时候,如果static方法、private方法、构造函数等,其中的直接引用(第一层关系)到的类都在同一个dex文件中,那么该类就会被打上CLASS_ISPREVERIFIED标志,且一旦类被打上CLASS_ISPREVERIFIED标志其他dex就不能再去替换这个类。所以一定要想办法去阻止类被打上CLASS_ISPREVERIFIED标志。

为了阻止类被打上CLASS_ISPREVERIFIED标志,QQ空间开发团队提出了一个方法是先将一个预备好的hack.dex加入到dexElements的第一项,让后面的dex的所有类都引用hack.dex其中的一个类,这样原来的class1.dex、class2.dex、class3.dex中的所有类都引用了hack.dex的类,所以其中的都不会打上CLASS_ISPREVERIFIED标志。

比如Qzon团队的 安卓App热补丁动态修复技术介绍  (这个一定要看!!! 他是热修复元老级文章,也是重点抄袭对象)

动态加载class文件,然后调用反射完成修复的原理:

Java程序在运行的时候,JVM通过类加载机制(ClassLoader)把class文件加载到内存中,只有class文件被载入内存,才能被其他class引用,使程序正确运行起来.

Java中的ClassLoader有三种.

1. Bootstrap ClassLoader 

由C++写的,由JVM启动.

启动类加载器,负责加载java基础类,对应的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等


2.Extension ClassLoader

Java类,继承自URLClassLoader

扩展类加载器,对应的文件是 %JRE_HOME/lib/ext 目录下的jar和class等


3.App ClassLoader

Java类,继承自URLClassLoader

系统类加载器,对应的文件是应用程序classpath目录下的所有jar和class等

这里要注意一点:只有被同一个类加载器实例加载并且文件名相同的class文件才被认为是同一个class.

下面来一个小例子:

因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader.\

如何自定义ClassLoader

新建一个类继承自java.lang.ClassLoader,重写它的findClass方法。--将class字节码数组转换为Class类的实例---调用loadClass方法即可

我先建一个叫Log的类,很简单,只有一句打印

  1. public class Log {  
  2.  
  3.    public static void main(String[] args) {  
  4.         System.out.println("调用成功");  
  5.    }  
  6. }    

  7. 把这个java文件放到D盘根目录,然后打开cmd,用javac命令把java文件转化为class文件

  1. 然后我新建一个MyClassLoader继承自ClassLoader
  2. public class MyClassLoader extends ClassLoader {  
  3.    @Override  
  4.    protected Class<?> findClass(String name) throws ClassNotFoundException {  
  5.        Class log = null;  
  6.        // 获取该class文件字节码数组  
  7.        byte[] classData = getData();  
  8.  
  9.        if (classData != null) {  
  10.            // 将class的字节码数组转换成Class类的实例  
  11.            log = defineClass(name, classData, 0, classData.length);  
  12.        }  
  13.        return log;  
  14.    }  
  15.  
  16.    private byte[] getData() {  
  17.        //指定路径  
  18.        String path = "D:/Log.class";  
  19.          
  20.        File file = new File(path);  
  21.        FileInputStream in = null;  
  22.        ByteArrayOutputStream out = null;  
  23.        try {  
  24.            in = new FileInputStream(file);  
  25.            out = new ByteArrayOutputStream();  
  26.  
  27.            byte[] buffer = new byte[1024];  
  28.            int size = 0;  
  29.            while ((size = in.read(buffer)) != -1) {  
  30.                out.write(buffer, 0, size);  
  31.            }  
  32.  
  33.        } catch (IOException e) {  
  34.            e.printStackTrace();  
  35.        } finally {  
  36.            try {  
  37.                in.close();  
  38.            } catch (IOException e) {  
  39.  
  40.                e.printStackTrace();  
  41.            }  
  42.        }  
  43.        return out.toByteArray();  
  44.    }  
  45. }  
  46. //最后测试一下,输出加载这个Log的class文件的加载器,并且利用反射调用它的mian方法.
  47. public class Test {  
  48.  
  49.    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {  
  50.        MyClassLoader myClassLoader = new MyClassLoader();  
  51.        //查找Log这个class文件  
  52.        myClassLoader.findClass("Log");  
  53.        //加载Log这个class文件  
  54.        Class<?> Log = myClassLoader.loadClass("Log");    
  55.          
  56.        System.out.println("类加载器是:"+Log.getClassLoader());    
  57.          
  58.        //利用反射获取main方法  
  59.        Method method=Log.getDeclaredMethod("main", String[].class) ;    
  60.        Object object=Log.newInstance();  
  61.        String [] arg={"ad"};  
  62.        method.invoke(object, (Object)arg);  
  63.    }  
  64. }  


业界内比较著名的有阿里巴巴的AndFix,HotFix(内测)Dexposed,Qzone的超级补丁和tencent的Tinker(将开源)以及大众点评的Nuwa,腾讯Bugly,RocooFix

Dex的热修复总结

Dex的热修复目前来看基本上有四种方案:

  • 阿里系的从native层入手,见AndFix
  • QQ空间的方案,插桩,见安卓App热补丁动态修复技术介绍
  • 微信的方案,见微信Android热补丁实践演进之路,dexDiff和dexPatch,方法很牛逼,需要全量插入,但是这个全量插入的dex中需要删除一些过早加载的类,不然同样会报class is pre verified异常,还有一个缺点就是合成占内存和内置存储空间。微信读书的方式和微信类似,见Android Patch 方案与持续交付,不过微信读书是miniloader方式,启动时容易ANR,在我锤子手机上变现出来特别明显,长时间的卡图标现象。
  • 美团的方案,也就是instant run的方案,见Android热更新方案Robust

此外,微信的方案是多classloader,这种方式可以解决用multidex方式在部分机型上不生效patch的问题,同时还带来一个好处,这种多classloader的方式使用的是instant run的代码,如果存在native library的修复,也会带来极大的方便。

Native Library热修复总结

而native libraray的修复,目前来说,基本上有两种方案。。

  • 类似multidex的dex方式,插入目录到数组最前面,具体文章见Android热更新之so库的热更新,需要处理系统的兼容性问题,系统分隔线是Android 6.0
  • 第二种方式需要依赖多classloader,在构造BaseDexClassLoader的时候,获取原classloader的native library,通过环境变量分隔符(冒号),将patch的native library与原目录进行连接,patch目录在前,这样同样可以达到修复的目的,缺点是需要依赖dex的热修复,优点是应用native library时不需要处理兼容性问题,当然从patch中释放出来的时候也需要处理兼容性问题。

上述方案从原理上可以简单划分为3类:

原理方案Native hook方案AndFixQQ空间提出的Classloader替换类的方案Nuwa, HotFix, RocooFixInstant Run的冷插拔原理的Dex替换Tinker
优缺点分析


测试模块AndFixClassloader方案Tinker类替换noyesyes资源替换nonoyes是否需要重启noyesyes兼容稳定性不稳定最好稳定



 

下面,我们就分别介绍QQ空间超级热补丁技术和微信Tinker以及阿里百川的HotFix技术。


一、Qzone超级补丁技术

超级补丁技术基于DEX分包方案,使用了多DEX加载的原理,大致的过程就是:把BUG方法修复以后,放到一个单独的DEX里,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。

 

当patch.dex中包含Test.class时就会优先加载,在后续的DEX中遇到Test.class的话就会直接返回而不去加载,这样就达到了修复的目的。

 

但是有一个问题是,当两个调用关系的类不在同一个DEX时,就会产生异常报错。我们知道,在APK安装时,虚拟机需要将classes.dex优化成odex文件,然后才会执行。在这个过程中,会进行类的verify操作,如果调用关系的类都在同一个DEX中的话就会被打上`CLASS_ISPREVERIFIED`的标志,然后才会写入odex文件。

 

所以,为了可以正常地进行打补丁修复,必须避免类被打上`CLASS_ISPREVERIFIED`标志,具体的做法就是单独放一个类在另外DEX中,让其他类调用。

 

我们来逆向手机QQ空间APK看一下具体的实现:

 

先进入程序入口`QZoneRealApplication`,在`attachBaseContext`中进行了两步操作:修复`CLASS_ISPREVERIFIED`标志导致的unexpected DEX problem异常、加载修复的DEX。

 

 

 1. 修复Unexpected DEX Problem异常

先看代码,

 

可以看到,这里是要加载一个libs目录下的dalvikhack.jar。在项目的assets/libs找到该文件,解压得到’classes.dex’文件,逆向打开该DEX文件,

 

 

通过不同的DEX加载进来,然后在每一个类的构造方法中引用其他DEX中的唯一类AnitLazyLoad,避免类被打上CLASS_ISPREVERIFIED标志。

 

在无修复的情况下,将DO_VERIFY_CLASSES设置为false,以提高性能。只有在需要修复的时候,才设置为true。

 

 

至于如何加载进来,与下面第二个步骤基本相同。

 

2. 加载修复的DEX

从loadPatchDex()方法进入,经过几次跳转,到达核心的代码段,`SystemClassLoaderInjector.c()`。由于进行了混淆和多次方法的跳转,于是将核心代码段做了如下整理:

 

修复的步骤为:

1. 可以看出是通过获取到当前应用的Classloader,即为BaseDexClassloader

2. 通过反射获取到他的DexPathList属性对象pathList

3. 通过反射调用pathList的dexElements方法把patch.dex转化为Element[]

4. 两个Element[]进行合并,把patch.dex放到最前面去

5. 加载Element[],达到修复目的

 

整体的流程图如下:

 

从流程图来看,可以很明显的找到这种方式的特点:

优势:

  1. 没有合成整包(和微信Tinker比起来),产物比较小,比较灵活
  2. 可以实现类替换,兼容性高。(某些三星手机不起作用)

不足:

1. 不支持即时生效,必须通过重启才能生效。

2. 为了实现修复这个过程,必须在应用中加入两个dex!dalvikhack.dex中只有一个类,对性能影响不大,但是对于patch.dex来说,修复的类到了一定数量,就需要花不少的时间加载。对手淘这种航母级应用来说,启动耗时增加2s以上是不能够接受的事。

3. 在ART模式下,如果类修改了结构,就会出现内存错乱的问题。为了解决这个问题,就必须把所有相关的调用类、父类子类等等全部加载到patch.dex中,导致补丁包异常的大,进一步增加应用启动加载的时候,耗时更加严重。


 

二、微信Tinker

微信针对QQ空间超级补丁技术的不足提出了一个提供DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同,区别在于不再将patch.dex增加到elements数组中,而是差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并,然后整体替换掉旧的DEX文件,以达到修复的目的。

 

我们来逆向微信的APK看一下具体的实现:

先找到应用入口`TinkerApplication`,在`onBaseContextAttached()`调用了`loadTinker()`,

 

进入TinkerLoader的tryLoad()方法中,

 

从方法名可以预见,在tryLoadPatchFilesInternal()中尝试加载本地的补丁,再经过跳转进入核心修复功能类SystemClassLoaderAdder.class中。

 

代码中可以看出,根据Android版本的不同,分别采取具体的修复操作,不过原理都是一样的。我们以V19为例,

 

从代码中可以看到,通过反射操作得到PathClassLoader的DexPatchList,反射调用patchlist的makeDexElements()方法吧本地的dex文件直接替换到Element[]数组中去,达到修复的目的。

 

对于如何进行patch.dex与classes.dex的合并操作,这里微信开启了一个新的进程,开启新进程的服务TinkerPatchService进行合并。

 

整体的流程如下:

 

从流程图来看,同样可以很明显的找到这种方式的特点:

优势:

  1. 合成整包,不用在构造函数插入代码,防止verify,verify和opt在编译期间就已经完成,不会在运行期间进行。
  2. 性能提高。兼容性和稳定性比较高。
  3. 开发者透明,不需要对包进行额外处理。

不足:

1. 与超级补丁技术一样,不支持即时生效,必须通过重启应用的方式才能生效。

2. 需要给应用开启新的进程才能进行合并,并且很容易因为内存消耗等原因合并失败。

3. 合并时占用额外磁盘空间,对于多DEX的应用来说,如果修改了多个DEX文件,就需要下发多个patch.dex与对应的classes.dex进行合并操作时这种情况会更严重,因此合并过程的失败率也会更高。

 


三、阿里百川HotFix

阿里百川推出的热修复HotFix服务,相对于QQ空间超级补丁技术和微信Tinker来说,定位于紧急BUG修复的场景下,能够最及时的修复BUG,下拉补丁立即生效无需等待。

 

1、AndFix实现原理

AndFix不同于QQ空间超级补丁技术和微信Tinker通过增加或替换整个DEX的方案,提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,对应用无性能消耗的目的。

原理图如下:

 

2、AndFix实现过程

 

对于实现方法的替换,需要在Native层操作,经过三个步骤:

 

接下来以Dalvik设备为例,来分析具体的实现过程:

 

1、setup()

 

 

对于Dalvik来说,遵循JIT即时编译机制,需要在运行时装载libdvm.so动态库,获取以下内部函数:

1) dvmThreadSelf( ):查询当前的线程;

2)dvmDecodeIndirectRef( ):根据当前线程获得ClassObject对象。

 

2、setFieldFlag

 

该操作的目的:把 private、protected的方法和字段都改为public,这样才可被动态库看见并识别,因为动态库会忽略非public属性的字段和方法。

 

3、replaceMethod

 

 

该步骤是方法替换的核心,替换的流程如下:

 

 

AndFix对ART设备同样支持,具体的过程与Dalvik相似,这里不再赘述。

 

从技术原理,不难看出阿里百川HotFix的几个特点:

 

优势:

  1. BUG修复的即时性
  2. 补丁包同样采用差量技术,生成的PATCH体积小
  3. 对应用无侵入,几乎无性能损耗

不足:

  1. 不支持新增字段,以及修改<init>方法,也不支持对资源的替换。
  2. 由于厂商的自定义ROM,对少数机型暂不支持。


综合分析如下:

 


  




热修复技术的坑与解

——————————————————————————————————————————————————————————————————————

我们可以看到,QQ空间超级补丁技术和微信Tinker的修复原理都基于类加载,在功能上已经支持类、资源的替换和新增,功能非常强大。既然已经有了这么强大的热修复技术,为什么阿里百川还要推出自己的热修复方案HotFix呢?

 

一、多DEX带来的性能影响

我们知道,多DEX方案原来是用于解决应用方法数65k的问题,现在google也官方支持了MultiDex的实现方案。超级补丁技术和Tinker却作为一种热修复的方案,平生给应用增加了多个DEX,而多DEX技术最大的问题在于性能上的坑,因此基于这种方案的补丁技术影响应用的性能是无疑的。

1. 启动加载时间过长

我们可以看到,超级补丁技术和Tinker都选择在Application的attachBaseContext()进行补丁dex的加载,即时这是加载dex的最佳时机,但是依然会带来很大的性能问题,首当其冲的就是启动时间太长。

对于补丁DEX来说,应用启动时虚拟机会进行dexopt操作,将patch.dex文件转换成odex文件,这个过程本身非常耗时。而这个过程又要求在主线程中,以同步的方式执行,否则无法成功进行修复。就DEX的加载时间,大概做了以下的时间测试。

 

通过上表可以看到,随着patch.dex的尺寸增加,在不做任何优化的情况下,启动时间也直线增长。对于一个应用来说,这简直是灾难性的。

 

2. 易造成应用的ANR和Crash

由于多DEX加载导致了启动时间变长,这样更容易引发应用的ANR。我们知道当应用在主线程等待超过5s以后,就会直接导致长时间无响应而退出。超级补丁技术为保证ART不出现地址错乱问题,需要将所有关联的类全部加入到补丁中,而微信Tinker采取一种差量包合并加载的方式,都会使要加载的DEX体积变得很大。这也很大程度上容易导致ANR情况的出现。

 

除了应用ANR以外,多DEX模式也同样很容易导致Crash情况的出现。在ART设备中为了保证不出现地址错乱,需要把修改类的所有相关类全部加入到补丁中,这里会出现一个问题,为了保证补丁包的体积最小,能否保证引入全部的关联类而不引入无关的类呢?一旦没有引入关联的类,就会出现以下的异常:

  • NoClassDefFoundError
  • Could Not Find Class
  • Could Not Find Method

出现这些异常,就会直接导致应用的Crash退出。

 

所以,不难看出如果我们需要修复一个不是Crash的BUG,但是因为未加入相关类而导致了更严重的Crash,就更加的得不偿失。

 

总的来说,热修复本质的目的是为了保证应用更加稳定,而不是为了更强大的功能引入更大的风险和不稳定性。

 

二、 热修复 or 插件化?

 

我们经常提到热修复和插件化,这都是当下热门的新兴技术。在讲述之前,需要对这两个概念进行一下解释。

 

  • 热修复:当线上应用出现紧急BUG,为了避免重新发版,并且保证修复的及时性而进行的一项在线推送补丁的修复方案。
  • 插件化:一个程序划分为不同的部分,以插件的形式加载到应用中去,本质上它使用的技术还是热修复技术,只是加入了更多工程实践,让它支持大规模的代码更新以及资源和SO包的更新。

 

显然,从概念上我们可以看到,插件化使用场景更多是功能上的,热修复强调微小的修复。从这个层面来说,插件化必然功能更加强大,能做的事情也更多。QQ空间超级补丁技术和微信Tinker从类、资源的替换和更新上来看,与其说是热修复,不如说是插件化技术的实践。

QQ空间超级补丁技术和微信Tinker提供了更加强大的功能,但是对应用的性能和稳定有较大的影响,就BUG修复的这个使用场景上还不够明确,并且显得过重。

 

针对应用的性能损耗,我们可以举例做一个对比:

某APP的启动载入时间为3s左右,本身就是基于多DEX模式的实现。

分别接入三种热修复服务,根据腾讯提供超级补丁技术和Tinker的数据,那么会变成以下的场景:

1. 阿里百川HotFix:启动时间几乎无增加,不增加运行期额外的磁盘消耗。

2. QQ空间超级补丁技术:如果应用有700个类,启动耗时增加超过2.5s,达到5.5s以上。

3. 微信Tinker:假设应用有5个DEX文件,分别修改了这5个DEX,产生5个patch.dex文件,就要进行5次的patch合并动作,假设每个补丁1M,那么就要多占用7.5M的磁盘空间。

显然对于修复紧急BUG这个场景,阿里百川HotFix的更为合适,它更加轻量,可以在不重启的情况下生效,且对性能几乎没有影响。


找了很多资料加上看各种文章源码写完这个文章,很多地方的了解不是那么深入,很多东西也是拾人牙慧,希望大家批评指正。

参考自:

  • 安卓App热补丁动态修复技术介绍 :https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a&scene=1&srcid=1106Imu9ZgwybID13e7y2nEi#wechat_redirect 

  • 鸿洋 –Android 热补丁动态修复框架小结  http://blog.csdn.net/lmj623565791/article/details/49883661 

  • 张涛 – Android 热修复,没你想的那么难 https://kymjs.com/code/2016/05/08/01/ 

  • Android热修复原理普及  http://blog.csdn.net/u012943767/article/details/52355214  

  •  Android 热修复其实很简单  原理及AndFix小例  http://blog.csdn.net/qq_31530015/article/details/51785228?locationNum=11

  • Android 热补丁技术——资源的热修复 http://blog.csdn.net/sbsujjbcy/article/details/52541803 

  • Android热修复技术选型——三大流派解析 http://www.cnblogs.com/alibaichuan/p/5863616.html 

  • 微信Android热补丁实践演进之路   http://blog.csdn.net/tencent_bugly/article/details/51821722 

  • AndFix: https://github.com/alibaba/AndFix  :AndFix对static的支持不太好

  • HotFix:https://github.com/dodola/HotFix

  • Nuva:https://github.com/jasonross/Nuwa


2 0