写在最前的话,一直听说热修复,不错,最近修复风靡,不明白原理都不行,明白原理了不会用也不行,故打算拿出一些时间去深入了解一番
翻阅众多资料
在此之前先感谢前人的资料提供,
好了
大家和我一起学习吧;
* 首先明白几个类的加载器:classLoader—->顾名思义,就是用来动态装载class文件的。标准的Java SDK中有个ClassLoader类,借助此类可以装载需要的class文件,前提是ClassLoader类初始化必须制定class文件的路径。
URLClassLoader,
PathClassLoader,
DexLoader
三个加载器使用不同,首先URLClassLoader是不能使用在Android上
PathClassLoader是在APK文件生成之后,及在/data/dalvik/cache文件中存在的,假如文件不存在的话就会爆出ClassNoFindException*()
那最后一个DexClassLoader根据我翻阅资料来看,应该就是基于它了
好接下来我们了解一下名词解析
什么是dex : dex文件是Android平台上可执行文件的类型。这时候我百度的时候出现了一个面试题
这里我简单提及一下,了解的小伙伴可以跳过,不了解的可以点击看一下dex文件和jar文件的区别是什么
区别一:dvm执行的是.dex格式文件 jvm执行的是.class文件 android程序编译完之后生产.class文件,然后,dex工具会把.class文件处理成.dex文件,然后把资源文件和.dex文件等打包成.apk文件。apk就是android package的意思。 jvm执行的是.class文件。
区别二:dvm是基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机。寄存器存取速度比栈快的多,dvm可以根据硬件实现最大的优化,比较适合移动设备。
区别三:.class文件存在很多的冗余信息,dex工具会去除冗余信息,并把所有的.class文件整合到.dex文件中。减少了I/O操作,提高了类的查找速度
嘿,突然发现我居然看懂了,这不就是dex和jar的区别吗,不是同一个名词,但是意思大体一致,为啥,你看呀jar只是将.class文件集成
而我们dex是将.class和资源文件转化成.dex,有木有明白了这几个名词。
好,接下来我们介绍一下DexClassLoder();首先它有一个4个参数的构找方法
dexPath 是jar,dex文件所在的路径
optimizedDirectory 是得到的dex的文件解压后放置的位置
libraryPath 是文件的library的存放路径,may be null 可是是空啊
parent 父类构造器
讲到这儿其实我们应该有明白,我们其实就是要拿DexClassLoader去进行加载在dexElements遍历之前,去做我们的操作,此时我们还不懂怎么去工作,小伙伴,不要慌,这里我给出一个链接,看完试试能不能明白DexClassLoader的使用原理
dexElements:明白这是啥玩意不,看完再点击上边的链接哈,其中每个dex文件是一个Element,当需要加载类的时候会遍历 dexElements,如果找到类则加载,如果找不到从下一个 dex 文件继续查找。
File.separator 小知识补充(就相当与windows下的“/”Linux下的“\”)
看完回来了,源码真心看不懂,恶心,,我先去吐会,等会回来再说
没看懂,,再去看一会
好了,看得差不多了,其实主要是我们跟踪源码的走向一步一步明白到底去干了什么,不必要知道每一个方法的意义,只要知道他怎么去处理的数据,我在这里就不再赘述了,我们看一下链接中文章的作者对这个DexClassLoader加载的理解,其实静下心来能够看明白的
我们可以简要总结下整个的加载流程,首先是对文件名的修正,后缀名置为”.dex”作为输出文件,然后生个一个DexPathList对象函数直接返回一个DexPathList对象,
在DexPathList的构造函数中调用makeDexElements()函数,在makeDexElement()函数中调用loadDexFile()开始对.dex或者是.jar .zip .apk文件进行处理,
跟入loadDexFile()函数中,会发现里面做的工作很简单,调用optimizedPathFor()函数对optimizedDiretcory路径进行修正。
之后才真正通过DexFile.loadDex()开始加载文件中的数据,其中的加载也只是返回一个DexFile对象。
在DexFile类的构造函数中,重点便放在了其调用的openDexFile()函数,在openDexFile()中调用了openDexFileNative()真正进入native层,
在openDexFileNative()的真正实现中,对于后缀名为.dex的文件或者其他文件(.jar .apk .zip)分开进行处理:
.dex文件调用dvmRawDexFileOpen();其他文件调用dvmJarFileOpen()。
在dvmRawDexFileOpen()函数中,检验dex文件的标志,检验odex文件的缓存名称,之后将dex文件拷贝到odex文件中,并对odex进行优化
调用dvmDexFileOpenFromFd()对优化后的odex文件进行映射,通过mprotect置为"只读"属性并将映射的内存结构保存在DvmDex*结构中。
dvmJarFileOpen()先对文件进行映射,结构保存在ZipArchive中,然后再尝试以文件名作为dex文件名来“打开”文件,
如果失败,则调用dexZipFindEntry在ZipArchive的名称hash表中找名为"class.dex"的文件,然后创建odex文件,下面就和
dvmRawDexFileOpen()一样了,就是对dex文件进行优化和映射。
有没有很开心,最起码懂了一点什么,那我们继续我们的主线
有上边那些我们知道了dex是根据.class文件转化过来的,然后又经dex转化成了odex,而 dex -> odex 则是针对不同平台,不同手机的硬件配置做针对性的优化。就是在这一过程中,虚拟机在启动优化的时候,会有一个选项就是 verify 选项,当 verify 选项被打开的时候,就会执行一次校验,校验的目的是为了判断,这个类是否有引用其他 dex 中的类,如果没有,那么这个类会被打上一个 CLASS_ISPREVERIFIED 的标志。一旦被打上这个标志,就无法再从其他 dex 中替换这个类了。而这个选项开启,则是由虚拟机控制的。
下一步
知道大体意思了,但是不明白怎么去操作这东西,有没有力不从心的感觉,马丹,意味着我还要继续看下去
可是,我发现我看跑题了,看到了一个插件制作的小Demo,卧槽,我还是决定提出来,因为这个看完你能更明白DexClassLoader的工作原理
插件架构Demo
代码简单说明
热修复的详解
看了这么一圈终于知道原理了,当别人问起来你对HotFix的理解的时候,你可以潇洒的说
1,三个ClassLoader的区别,我们采用那个DexClassLoader加载器进行加载
2,这个DexClassLoader有一个四个参数的构造函数,
- 对应着jar,class文件所在的文件位置,
- 对应着要指定获取到的dex文件放置的位置,
- 对应本地文件C,C++ 等本地文件的放置位置,一般设定为null
- 获取到父类加载器,等等我们是看过源码的淫,这里可以来点文章
3,对dex和jar的区别来点事,dex是将dvm将.class文件和资源文件加载成.dex,而那jar是jvm将.class 文件转为.jar.
4,DexClassLoader的工作原理是根据传入的路径获取到dex,如果不是dex的话源码中处理方式,比如讲不是.dex的结尾文件装华为.dex,是的就直接返回进行loadDex();
5,上边扯了这么大牛逼,我们回归正题,如何进行热修复,DexClassLoader是将获取到一个DexEleMents[]的数组,进行遍历,这样就好办了不是,数组是可以添加删除的,嘿嘿,在他遍历之前我们对数组添加,这不就解决了吗,至于真么添加,根据宿主App,拿到App中的所有类,这里我们对类进行过滤,拿到需要实现的类,通过ClassReader,ClassWriter,ClassVIstor,去操作这个字节码文件
6,这里会引出一个问题,就是我们的App都是经过混淆之后发布的,所以这里我们呢,就可以在发布BUG版本保存的,在 Gradle 插件编译过程中,有一个proguardTask,看名字应该就知道他是负责 proguard 任务的,我们可以保存首次执行时的混淆规则(也就是线上出BUG的包),这个混淆规则保存在工程目录中的一个mapping文件,当我们需要执行热修复补丁生成的时候,将线上包的mapping规则拿出来应用到本次编译中,就可以生成混淆后的类跟线上混淆后的类相同的类名的补丁了。