一、Java中的类加载器 首先花点时间回顾一下Java中的三种类加载器: BootStrap ClassLoader 启动类加载器,它是实现自C/C++的类加载器,用于加载JDK的核心类库,例如java.lang、java.util等系统类
一、Java中的类加载器首先花点时间回顾一下Java中的三种类加载器:
总体的加载关系是:null->ExtClassloader->AppClassLoader,需要注意的是,BootStrap ClassLoader无法在Java中通过.class.getClassLoader()访问它的父加载器。 同时,这只代表加载关系,而不代表继承关系,ClassLoader的继承关系如下: 其中:
二、Android中的类加载器区别于标准JVM以Class文件为输入的字节码文件,Android虚拟机采用更为紧凑的Dex文件作为输入文件,无论是Dalvik VM还是ART VM。这样一来,类加载器自然也会有所改变。 Android中的类加载器类型被分为两类:
2.1 BootClassLoaderAndroid 系统启动时,会使用BootClassLoader来加载常用的类,与SDK中的BootStrap ClassLoader不同的是,它本身不是由C/C++实现的, 而是采用Java实现的,它是ClassLoader的内部类,继承自ClassLoader,本身是一个单例类。应用中无法直接访问到BootClassLoader。 BootClassLoader是在ZygoteInit的入口方法中,间接调用了preloadClasses方法中,进行创建的,Android在/framework/base/preloaded-classes中封装了一系列的预加载类的目录,一些常用类,例如:ContextImpl、Fragment、Dialog等等都在列。预加载之后,应用程序启动时,就不用额外去做加载了。 2.2 PathClassLoaderPathClassLoader它通常被系统用来加载Apk中自带的Dex文件,它的构造函数中少了一个参数:optimizedDirectory,这是因为PathClassLoader定义了默认的optimizedDirectory参数:/data/dalvik-cache/,因此,我们无法自定义Dex文件的解压路径,所以我们加载类时,一般都使用DexClassLoader。 PathClassLoader是Zygote进程在fork SystemServer进程时创建的,当Zygote进程在新创建SystemServer时,通过调用forkSystemServer方法时,会调用到handleSystemServerProcess(),然后调用createPathClassLoader()去创建PathClassLoader。 2.3 DexClassLoader可以在磁盘中加载.dex或者是.apk文件,但是本质上都是加载属于Android 的字节码文件:Dex文件。 它的构造方法有四个参数:
2.4 InMemoryDexClassLoaderAndroid8.0 中新增的用于加载内存中的类加载器。和PathClassLoader、DexClassLoader一样,都是BaseDexClassLoader的实现类。 三、Dex文件3.1 Android内存中的Dex文件BaseDexClassLoader有三个子类:DexClassLoader、PathClassLoader、InMemoryDexClassLoader,它们三个主要任务就是:加载外部的Dex文件,获取其中定义的类信息。同样,Android的类加载机制也遵循双亲委派机制。 BaseDexClassLoader有一个特殊的结构:DexPathList类型的pathList ,它内部维护了一个Element类型的数组,用来存储被加载的Dex文件信息:
每当我们要使用一个类时,类加载器就会先检索:DexPathList中的所有Dex文件,逐个遍历,看看其中是否含有所需要的类: Element内部有一个对象:DexFile,在调用Element#findClass时,会按照如下规则去查找: 而最终,loadClassBinaryName会调用Native代码在本地内存上创建一个指向Dex文件的对象,这样 ,我们知道Dex文件在内存中的引用是类加载下的DexPathList中的一个个Element。 这个Element数组可以作为一个热修复的接入点,我们知道,类加载只会被加载一次,如果此时我们有多个Dex文件,那么Dex文件的引用在Element中会按照加载的顺序排列,这样一来,排在前面的Dex类中的Class就会被优先加载,由此我们就可以将热修复后重新生成的Patch.dex或Patch.apk加载到用户手机存储空间当中,然后自行使用DexPathClassLoader进行加载,并通过反射,Hook掉PathClassLoader,将Patch.dex对应的Element反射插入到其DexPathList当中去。这样一来,加载时就会优先从Patch.dex中加载了,原理大致如下图。修复后加载原先出错的类ClassE将会从Patch.dex中优先加载,而出错的Class E由于类加载的特性,将不会被加载出来。 3.2 Dex文件的生成我们所编写的Java代码,使用Java自带的编译器编译完成之后默认的输出一定是.class文件,而在ART或者Dalvik虚拟机中需要输入Dex文件,那么在其中必然存在Class -> Dex文件的过程。 该过程是由d8工具完成的,在我们的SDK目录下:/Library/Android/sdk/build-tools/28.0.3,有非常多和我们Android 构建相关的一些工具,例如aapt2工具会负责将res.xml中的文件在R.java中生成对应的ID引用、负责将二进制资源、资源表resources.arsc、classes.dex以及assets集成打包进一个未签名的.apk文件内。 d8工具会将需要打包的.class文件、额外依赖的Jar文件一同参与编译。比如我们需要对appt2下的一个MainActivity.class文件进行编译,那么我们可以在一个新项目中,点击Android Studio的编译或者上面的绿色小锤子,然后在:MyApplication2/app/build/intermediates/javac/debug/classes/com/red/myapplication 下我看到MainActicity.class文件,然后在该目录下执行如下的指令,注意,将MainActivity的AppcompatActivity换成Activity,因为前者是属于AndroidX系列的的AAR包中的依赖,需要额外添加。
其中,--lib指定了一些额外的依赖,因为MainActivity中会依赖android.jar中的一些文件(比如Activity类),完成后,我们就得到了一个classes.dex文件,结构如下: 如上的步骤都在Android提供的Gradle套件中,帮我们完成了,Gradle插件依赖的本质,就是插件文件的下载(Gradle同步)和引用。 |
2022-04-23
2022-01-26
2021-11-15
2021-08-02
2019-12-15