Android热修复核心——ClassLoader

背景:

如今比较火的技术中热修复算是一种了。他能够帮助我们在不升级应用版本的情况下修复应用Bug。那么这种技术如何实现呢?这里我们先不急着讲如何去实现热修复,因为能力还达不到。我们先了解一下这个技术的核心:类加载。

一、概念了解:Dalvik和ART

(1)什么是Dalvik

上一章我们讲了Java虚拟机JVM。但实际上Java中JVM可以理解为一种虚拟机规范,现实中各家都根据这个规范开发了自己的虚拟机,就像我们今天要了解的Dalvik虚拟机也是一种Java虚拟机,它默认使用CMS垃圾回收器。它是Google设计的用于Android平台的Java虚拟机。但是与JVM运行 Class 字节码不同,Dalvik的特点是支持转换成.dex格式的Java应用程序的运行。解压过Android apk的同学可能会知道,我们的Android应用程序编译后会被转换成Classes.dex格式的文件,然后再被压缩进Apk安装包。

(2)什么是ART

Java是一种跨平台的解释型语言,它需要将我们的Class文件编译成字节码,然后再将字节码转换成机器码运行在各个平台上,但是在Android中如果每次运行程序都要进行这样去转换一次,那效率是不是超级慢,严重影响用户体验,于是ART(Android RunTime)出现了,它会在应用安装的时候经过AOT(Ahead-Of-Time)预编译字节码成机器码(所以我们会发现现在的应用安装时都比较慢,但是安装完成后运行时却效率很高,启动快),经过AOT预编译后.dex文件会转换成.oat机器码文件。

(3)ART和Dalvik的区别

在Dalvik虚拟机下,应用运行需要解释执行,执行频率高的代码通过即时编译(JIT:Jsut In Time)将字节码转换成机器码。这种情况下运行效率慢。而ART环境中,应用在安装时就将字节码预编译成了机器码,虽然安装慢但是运行效率却很高。看完这些描述是不是有同学和我一样蒙蔽,咋一下冒出来这么多专业词汇?下面我们通过图来解释上面的流程。

看完上面的流程图是不是感觉流程清晰了那么一丢丢。那么从.dex文件到.oat文件这个过程中Google做了很多的优

化,其中主要分为两种:

(1)Dexopt

在Dalvik环境下,对dex文件进行验证和优化为odex文件。

(2)dexAot

在ART环境下,安装时对dex文件执行dexopt优化之后再将odex进行AOT预编译操作,编译为.oat可执行文件(机器码)。

 有人说,你讲ClassLoader就讲ClassLoader好了,搞出这么多不懂的东西出来干嘛。因为要弄明白ClassLoader原理就需要了解一点上面这些虚拟机的知识点啊。

二、Android中的ClassLoader

为什么我们要说是Android中的ClassLoader呢。在Java中CLassLoader主要有两种SecureClassLoader和BaseDexClassLoader。而Android中用的就是后面这种BaseDexClassLoader.所以这里我们将根据这个BaseDexClassLoader来讲解ClassLoader的原理。

(1)、Android中的类加载器分类

Android中的类加载器主要有三类,他们都是BaseDexClassLoader的子类。

a、BootClassLoader:用于加载Android Framework层的Class文件

b、PathClassLoader:用于Android应用程序类加载器(也就是我们自己写的Class文件)。可以加载指定的dex、jar、zip以及apk中的dex文件。

c、DexClassLoader:用于加载指定dex、jar、zip以及apk中的dex文件。咦,为什么这里和上面的PathClassLoader一模一样?他们有区别吗?别急,下面我们会通过代码告诉大家区别。实际上在Android系统中这个DexClassLoader并没有使用到。个人感觉DexClassLoader就是让用户自定义ClassLoader来使用的。

/**
 * 参数一:指定要加载的dex文件路劲
 * 参数二:需要加载的so文件路径
 * 参数三:指定它的父加载器,这个下面说双亲委托机制会讲到
 */
PathClassLoader pathClassLoader = new PathClassLoader("xxx/xxx/xx.dex",null,getClassLoader());
/**
 * 参数一:指定要加载的dex文件路劲
 * 参数二:还记得上面说的dexopt优化后会生成odex文件吗?这个参数就是用来指定将要保存odex文件的路径,且它只能是应用私有的路径(即data/data/packagename下面的路径)。
 *         这就是DexClassLoader与PathClassLoader的区别。PathClassLoader会指定一个默认保存odex的路劲,而DexClassLoader需要我们自己手动去指定路劲。
 * 参数三:需要加载的so文件路径
 * 参数四:指定它的父加载器,这个下面说双亲委托机制会讲到
 * 
 */
DexClassLoader dexClassLoader = new DexClassLoader("xxx/xxx/xx.dex",getCodeCacheDir().getAbsolutePath(),null,getClassLoader());

//创建完类加载器后,就可以加载dex文件下指定的类了。
try {
    Class<?> aClass = pathClassLoader.loadClass("xxx/xxx/xxx/MainActivity");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

try {
    Class<?> aClass = dexClassLoader.loadClass("xxx/xxx/xxx/MainActivity");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

//这里尝试用系统的ClassLoader来加载dex文件中的类,但是运行后会发现报ClassNotFoundException异常,
// 那是因为系统的类加载器没有加载我们的dex文件,所以它找不到dex文件下的类信息
ClassLoader classLoader = getClassLoader();
try {
    classLoader.loadClass("xxx/xxx/xxx/MainActivity");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

(2)一问三不知的双亲委托机制

有没有同学和我一样,每次面试的时候一被面试官问到类加载流程或者什么是双亲委托机制的时候就一脸懵逼,一问三不知,小心脏就怦怦跳了?其实双亲委托机制并没有想象的那么难,只是我们没有花时间去研究它而以。那么什么是双亲委托机制呢?

我们先来看一下ClassLoader源码的内部构造。

然后我们再来看一下它的loadClass方法

双亲委托的实质就是每次加载类时并不是立马由当前ClassLoader去加载,而是先委托给其父加载器进行加载(啃老族一样的),如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。这样实现的好处主要有以下几点:

a、各加载器分工明确,Framework层的Class文件由BootClassLoader加载,自定义类由PathClassLoader、DexClassLoader、自定义加载器加载。

b、避免多次加载,出现一个类有多份字节码的情况,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。提高程序运行效率。

c、安全性考虑,防止核心API库被随意篡改。我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中的定义类型,这样会存在非常大的安全隐患。

上面的截图里面我们可以看到最后一步才是通过自身的findClass函数加载类。那么这一步是如何实现从dex文件中查找类,并转换成我们需要的Class对象呢?我们来从源码中找答案。

可以看到BaseDexClassLoader的findClass方法狠简单,就是调用了一个内部变量的findClass函数。那么这个pathList是什么呢?

看到前缀Dex是否就感觉到了亲切呢?那我们继续跟踪DexPathList的源码找到findClass方法。

 DexPathList的findClass方法只有一个遍历的操作,那么它遍历的是什么呢?猜一下应该也能猜到肯定和Dex文件有关。这里的dexElements就是一个数组,它的成员变量Element就标志着一个个的DexFile文件。最后调用Element变量的findClass就是在遍历对应的Dex文件中的Class文件(只不过这里是通过调用native方法实现的,我们看不到。但不用想也知道底层是在操作文件IO啥的).最后同样是将找到的Class二进制流转换成我们的Class对象。

从以上类加载机制的源码中我们可以分析出,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找到类则返回,如果找不到继续从下一个dex文件查找。理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件中的类。其实大部分的热修复就是通过向dexElements数组头部中插入新的修复了bug的dex文件来实现的(如Qzone),当然了正真实现热修复的步骤绝对不像我说的这么简单,其中还涉及到很多的源码级的坑需要踩。毕竟是个黑科技啊。感兴趣的可以去看一下QZone的热修复原理。因为我自己也还在研究,所以就先不误人子弟的做过多说明了。

三、如何实现热修复功能

先立个Flag,等能力上来了再来补充这一块 内容,哈哈。

发布了29 篇原创文章 · 获赞 3 · 访问量 900

猜你喜欢

转载自blog.csdn.net/LVEfrist/article/details/94589236
今日推荐