AndroidClassLoader源码解析

来源:互联网 发布:益思留学 知乎 编辑:程序博客网 时间:2024/06/15 14:50

 在Android系统中,提供了DexClassLoader和PathClassLoader,这两者均继承BaseDexClassLoader。但这两者却有他们各自不同的特点,其中,DexClassLoader可用于加载apk文件中的.dex文件,也可用于加载没有安装到系统中的应用中的类。因此,这也是DexClassLoader与PathClassLoader的不同之处,正是因为这个特点,Android的主流开发技术,如插件化、热修复等,都是通过这一特性实现的,所以,DexClassLoader是实现动态插件化、热修复的前提条件。

一、DexClassLoader的特点

   DexClassLoader源代码如下,通过阅读源代码注释,可以清晰看出DexClassLoader的特性。


二、PathClassLoader的特点

   PathClassLoader也是通过继承BaseDexClassLoader实现的,它向外界提供了两个构造方法,重点看一下第二个构造方法,可以看出,PathClassLoader与DexClassLoader相比,它的第二个构造方法,少了optimizedDirectory这个内部文件拷贝路径参数。由此可见,PathClassLoader只能用于加载已经安装到系统中的apk文件中的.dex文件,这也是PathClassLoader与DexClassLoader在本质上的区别。也就是说,DexClassLoader可用于加载指定路径下的.dex文件,能支持动态插件化加载、热修复等;而PathClassLoader只可用于加载已经安装到系统中的apk中的.dex文件,不支持动态插件化加载、热修复等。关于PathClassLoader的源代码,如下所示。


    由于DexClassLoader和PathClassLoader,这两者均继承BaseDexClassLoader,而且具体的类加载过程,都是由BaseDexClassLoader实现的,因此,接下来将分析BaseDexClassLoader的源码,从而对类加载过程及原理有更深入的理解。

三、BaseDexClassLoader源码分析

    首先看BaseDexClassLoader的构造方法,可以看出,在这个类被创建时,初始化了pathList这个实例,而这个DexPathList的初始化,则是根据调用方提供的dexPath路径、optimizedDirectory拷贝目录、librarySerchPath和BaseDexClassLoader自身完成的DexPathList创建。


    在BaseDexClassLoader中,类的查找工作,是通过外部调用findClass这个方法完成的,该方法在查找到Class之后,则会将Class返回给调用方,关于findClass方法,源代码如下所示。


    通过查看findClass方法可以看出,这里并没有进行Class加载的具体真正实现,而具体实现,则是委托给了DexPathList这个类来完成,当DexPathList加载到Class后,则会进行检查工作,如果Class不存在,则会抛出异常信息。否则,就会将加载到的Class向调用层返回。

    继续查看BaseDexClassLoader的源码可以看出,不论是findClass方法还是其他方法,这里都没有进行具体的实现,这个类的设计思想都是通过DexPathList实现的委托操作,也就是将BaseDexClassLoader类向外部提供的方法,最终委托给DexPathList来完成。关于BaseDexClassLoader中的一些其他方法,源代码如下所示。


    回到findClass方法继续分析,在这个方法中,最关键的一行代码就是调用了DexPathList中的findClass方法。因此,接下来将继续深入到DexPathList的源码中进行分析。

四、DexPathList源码分析

    由于BaseDexClassLoader在实例化时,创建了pathList,因此,DexPathList将会被创建。所以,接下来将首先从DexPathList的构造方法进行分析。

    在DexPathList类中,声明了dex文件的后缀名、外部传入的ClassLoader、Element数组等,代码如下所示。


    其中,dexElements用于存储dex文件,而nativeLibraryPathElements则用于存储nativeFramework中的所有library文件。由于这两个数组中存储的类型为Element,所以,关于dex文件,在Element中也会有处理。代码如下所示,可以看出,当Element创建时,记录了DexFile。

    继续回到DexPathList的构造方法中进行分析,在第126行代码中,这里初始化了dexElements数组,是通过调用makeDexElements方法实现的。因此,该方法是.dex文件加载的核心方法,makeDexElements也是获取.dex文件的核心操作。


    在执行makeDexElements方法过程中,首先会通过splitDexPath方法,通过阅读该方法的注释就可以看出,这个方法主要是将dexPath文件进行拆分,这个方法就是将指定路径中的文件,依次拆分成文件元素,并放入到List中。splitDexPath方法会依次遍历指定路径中的所有文件及文件夹目录,如果当前不是文件,则跳过,否则会将该文件添加到List中并返回。关于splitDexPath方法的源代码,如下所示。


    接下来makeDexElements方法会根据splitDexPath方法拆分出的文件List,将其放入到Element数组中,并根据Element数组对每个元素进行遍历。在遍历过程中,将会得到每个文件元素的路径和文件名称,代码如下所示。


    在遍历过程中得到文件路径和文件名称后,接下来则会检查当前被遍历的元素是文件夹还是文件。如果是文件夹或zip压缩文件,则更新elements数组,使其能继续进行递归遍历,从而确保文件夹目录中的dex文件能够被加载成功。相反,如果这时检查的元素不是文件夹,则根据文件名称检查当前元素是否为.dex文件,如果是.dex文件,根据文件file、ClassLoader、拷贝路径optimizedDirectory以及elements数组,执行loadDexFile这个方法进行.dex文件加载。关于makeDexElements方法中对每个元素的检查,代码如下所示。


    上面说到了如果此时是.dex文件或是文件目录,则会继续进行递归检索,将指定路径中的所有.dex文件,以及指定路径中所有目录下的.dex文件加载到elements数组中。因此,在loadDexFile方法中,如果optimizedDirectory目录为空,则表示当前的文件就是.dex文件,这是则创建了一个DexFile并返回。相反,如果不是,则通过DexFile这个类执行loadDex操作,将所有的.dex文件获取返回给makeDexElements方法。


    在makeDexElements方法的遍历末尾,有这样一个判断,检查zip或dex文件是否存在。也就是说,在makeDexElements的循环遍历中,通过loadDexFile方法加载到dex文件后,在该元素遍历的末尾,如果这个dex文件存在,则会被记录到elements数组中。这样一来,指定路径下的所有.dex文件,都将会被DexPathList进行记录,代码如下所示。


    分析到这里已经可以看出,当BaseDexClassLoader被创建时,将会首先实例化DexPathList,而DexPathList被实例化时,将会通过makeDexElements方法遍历指定路径下的所有.dex文件,并记录到elements数组中。这样一来,当加载某个类时,就可以通过elements数组来查找对应的.dex文件,然后再根据这个.dex文件,来查找对应的Class类了。

    因此,当BaseDexClassLoader执行findClass时,将会通过DexPathList执行findClass方法。在DexPathList的findClass方法中,将会遍历dexElements数组来获取每一个DexFile文件,接下来则检查dex文件是否存在,如果存在,则通过DexFile的loadClassBinaryName方法来加载这个dex文件中的类,代码如下所示。


   loadClassBinaryName方法接收外部传递的参数,name为要加载获取的Class类名,而definingContext则是具体的ClassLoader,最终将会通过此方法,加载到dex文件中对应的Class字节码文件。

    由此可见,在DexClassLoader和PathClassLoader中,加载Class字节码文件时,都是通过BaseDexClassLoader中的findClass方法进行的,而在BaseDexClassLoader的这个方法中,又是通过DexPathList获取到DexFile,然后通过DexFile中的loadClassBinaryName方法,并根据class名称以及当前的ClassLoader进行的Class字节码加载。关于loadClassBinaryName方法,代码如下所示。


    分析到这里可以看出,最终进行Class字节码的加载操作,是通过底层的native方法来完成的。

原创粉丝点击