一、什么是热修复:
相信很多人第一次听说热修复的时候也是一脸茫然,那么我就用自己的话来跟大家讲解一下。
当一个已经上线的APP突然发现一个严重的bug的时候,除了重新发版,让用户重新下载覆盖安装以外,还有没有别的方法来解决呢?如果可以有一个动态修复的方法就好了,那就是热补丁动态修复技术,也就是我们说的热修复。向用户下发Patch,在用户无感知的情况下,修复bug问题。这里用到的是android dex分包方案,后面会具体解释如何实现的。
这里我们先说一下Android的插件化,Android插件化分为三大块:热部署,动态加载资源,四大组件动态加载。
分别举例:
热部署 ------------------------------热修复
动态加载资源-------------------动态换肤
四大组件动态加载----------模块化开发
二、基于的原理——心急吃不了热豆腐
首先,就要从Android的ClassLoader体系来说起:
ClassLoader顾名思义,就是在Java中加载一个类需要用到的。
在Android中有三大ClassLoader,分别为URLClassLoader,PathClassLoader,DexClassLoader.其中:URLClassLoader:只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。
这里我们重点来说后面这两个加载类:
PathClassLoader:前面说过热修复是用到了dex分包方案(后面会说什么是dex文件),那么这个PathClassLoader就是用来去找dex文件的类。当apk安装后,PathClassLoader会去读取/data/dalvik-cache/dex文件,例如我们安装了一个包名为com.meijia.xxx的apk,那么安装后就会在/data/dalvik-cache目录下,生成一个data@app@com.meijia.xxx-1.apk@classes.dex的ODEX文件(后面会讲解什么是ODEX文件),而PathClassLoader找的就是这个ODEX文件,如果说找不到这个文件,就证明apk还没有安装,报错就是ClassNotFoundException。
DexClassLoader:顾名思义,DexClassLoader就是用来加载dex的加载类,就是从.jar和.apk类型的文件内部加载classes.dex文件。可以用来执行非安装的程序代码,也就指的是插件中的apk代码。
它的构造函数包含四个参数:
1.dexPath:就是目标类所在的APK或者jar文件的全路径。
2.dexOutputDir:就是dex在APK或者jar文件中解压后的dex文件存放路径
3.libPath:指目标类中所使用的C/C++库存放的路径
4.classLoad:是指该装载器的父装载器,一般为当前执行类的装载器
到这里,我们就知道了1.PathClassLoader在这里是作为类加载器,2.DexClassLoader是用来加载classes.dex文件的。继续。
PathClassLoader和DexClassLoader都是继承BaseDexClassLoader这个类,看源码如图:
这里我们通过注释可以看到1.其中DexPathList pathList就是多dex的结构列表。2.其中pathList里面包含一个Element [ ] dexElements数组就是dex列表,每一个element就是一个dex文件。
那好,当PathClassLoader加载类加载的时候会遍历Element [ ] dexElements,如果找到对应类则加载,找不到,就会继续从下一个dex文件中查找,那么我们就明白了,试想,如果我们将补丁插件dex文件插入到Element [ ] dexElements的最前面,理论上不同dex中有相同的类名存在时,会优先加载第一个类,找到了就返回,不会再继续查找下一个dex文件了,这就是我们说的基于dex分包的热修复的原理。
到这里,我们就知道了热修复就是在ClassLoader(类加载器)中插入一个dex文件。
那么什么是dex文件?什么又是ODEX文件呢?它们和class是什么关系?为什么Android只能识别dex文件,不能直接识别class文件呢?
先看一下class的结构,如图(摘自邓凡平老师博客)
我们可以看到一个class文件中包含很多版本信息,常量等信息。
dex文件就是将整个Android项目中的所有class文件合并成一个或者几个dex文件,当两个或几个class文件中有重复的字符串,那么dex就只存一份就可以了,换种说法就是dex是对class的压缩。
所以,在Android dalvik虚拟机中是无法识别一个class文件的,因为他们的结构不同,一个dex文件的结构如图
然后什么是ODEX呢,Android dalvik虚拟机也不是直接识别dex文件的,当APK被安装的时候,虚拟机会实现一次优化,就是将dex文件转换成odex文件,这次转换是为了对不同的手机硬件做对应的优化,而class转换成dex,是针对不同平台的优化,两者意义上是不同的。
然而,就在这优化过程中,在虚拟机启动的时候会有很多参数,其中有一项叫做verify的选项,当这个选项被开启之后,就会实行一个类的校验,具体校验内容就是,如果static方法、private方法、构造函数等,其中的直接引用(第一层关系)到的类都在同一个dex文件中,那么该类就会被打上CLASS_ISPREVERIFIED标志
校验过程如下:
一旦这个类CLASS_ISPREVERIFIED标志被打上,那么我们也就不能从别的dex文件中替换这个类了,那么我们之前说的在Element [ ]中插入dex的方法就无用了,知道了原因,办法就好想了。
不就是如果这个dex中的类没有引用其他dex文件中的类,就会被打上CLASS_ISPREVERIFIED
标志么?那么我们就让他引用就好了。
三、如何阻止CLASS_ISPREVERIFIED标记
到这里,试想,如果我们在所有的类的构造函数中,引用一个其他dex文件中的类的方法的话,是不是就可以防止被打上CLASS_ISPREVERIFIED标记了。
其中,hack.dex文件是要在最先加载进来的,不然如果当应用启动的时候,hack.dex没有先加载进来的话,这些引用它的类,就会报错,找不到hack.dex中的AntilazyLoad这个类。
那么问题来了?
如何去修改一个类?又如何在应用启动时最先加载hack.dex这个文件呢?
不同的修复方案的解决办法是不同的,这里就不一一介绍了,因为如果要说的话,要说的东西太多,我觉得自己还说不好,就搜罗网络上热门的方案大致以下几种开源框架。
https://github.com/dodola/HotFix
https://github.com/jasonross/Nuwa
https://github.com/bunnyblue/DroidFix
其中鸿洋大神的文章是用HotFix解说的:HotFix解说
涛哥的文章是用Nuwa解说的:Nuwa解说
总结:在学习一项新知识的时候,需要很大的耐心,对于基础不是很好的同学,会在学习的过程中发现越来越多自己不懂的地方,这时候请不要烦躁,请静下心来,反复对敲,不懂的地方更是要耐心的去找资料去学习,当你明白了一项新知识以后,你会发现,你学到的比你想象的还要多。以此共勉,加油~