双亲委派机制

双亲委派机制

1. 什么是双亲委派机制

所谓的双亲委派机制,指的就是:当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。

虚拟机在加载类的过程中需要使用类加载器进行加载,**编写java程序过程中,有些是基础的系统类,有些是个人编写的类,如果个人的类与系统基础的类重复,就会破坏系统类整体正常使用 **,通过双亲委派机制,系统类使用系统的类加载器,个人编写的类使用应用或者个人级别的类加载器,从而系统类总是优先于个人类的加载,那么即使个人编写了与系统完全一致的类,加载过程中也会应为优先加载了系统类而导致个人的类不会加载

Java语言中支持的4种类加载器:

Java中提供的这四种类型的加载器,是有各自的职责的:

  • Bootstrap ClassLoader ,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等
  • Extention ClassLoader,主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件
  • Application ClassLoader ,主要负责加载当前应用的classpath下的所有类
  • User ClassLoader , 用户自定义的类加载器,可加载指定路径的class文件

2.双亲委派是如何实现的

这里需要明确一下,双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码的。

ClassLoader.java

java 复制代码
public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;
}

Classloader间的组合关系如下

实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中

java 复制代码
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                  // 先让父加载器去加载,由于BootstrapClassLoader是根加载器,所以
                  //parent = null
                    if (parent != null) {
                        c = parent.loadClass(name, false); 
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //父classloader没有加载成功,那么使用自身的findclass(name)方法
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

Tips

  • 责任链模式

  • defineClass() 将字节码转换为class对象

3. 认识Android中的classload

3.1 常见classloader

android对jvm做了调整,使用dvm虚拟机,类文件将被打包成dex文件。底层的虚拟机是不同的,所以它们的类加载器当然也会不同

常见的Android类加载器有如下四种:

  • BootClassLoader :加载Android Framework层中的class字节码文件(类似java的BootstrapClassLoader)

  • PathClassLoader :加载已经安装到系统中的Apk的 class 字节码文件(类似java的 AppClassLoader )

  • DexClassLoader :加载制定目录的class字节码文件(类似java中的 Custom ClassLoader )

  • BaseDexClassLoader : PathClassLoader 和 DexClassLoader 的父类

3.2dvm中 classloader组合关系如下:
3.3 常见classloader源码

DexClassLoader.java

java 复制代码
package dalvik.system;

import java.io.File;

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}

PathClassLoader.java

java 复制代码
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

BaseDexClassLoader.java

java 复制代码
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
//加载
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException { 
    Class clazz = pathList.findClass(name);
    if (clazz == null) { 
        throw new ClassNotFoundException(name); 
    } 
    return clazz;
 }
}

android中类的加载流程由DexPathList.java完成,DexPathListfindClass(name)

java 复制代码
//DexPathList的findClass,从自身的Element属性中查找,从前到后
public Class findClass(String name) { 
    for (Element element : dexElements) { 
        DexFile dex = element.dexFile;
        if (dex != null) { 
            Class clazz = dex.loadClassBinaryName(name, definingContext); 
          if (clazz != null) { 
              return clazz; 
          } 
        } 
    } 
    return null;
}
3.4 热修复做法

**应用通过PathClassLoader来加载用户编写的class,PathClassLoader加载class的由DexPathList完成 **,热修复便是通过反射修改DexPathListdexElements属性,将修复的文件插入dexElements集合最前面,使得findClass遍历时优先查找到修改后的class

要点:

  • 通过反射可以获取app的PathClassLoader实例,pathList属性实例和dexElements属性实例
  • DexPathList 中包含方法将apk文件中的dex文件 提取出来,转化为Element

实例:

kotlin 复制代码
private fun installPatch(application: Application, patch: File) {
        //原始dex文件
        val classLoader = application.classLoader
        Log.e("TAG", "classloader = ${classLoader::class.java.name}")
        val pathListField = getField(classLoader::class.java, "pathList") ?: return
        val pathList = pathListField.get(classLoader)
        val dexElementsFiled = getField(pathList::class.java, "dexElements") ?: return

        val oldElementArray = dexElementsFiled.get(pathList) as Array<*>

        Log.e(
            "TAG",
            "dexElements = ${oldElementArray::class.java.name} ,size = ${oldElementArray.size}"
        )
        val patchList = ArrayList<File>().apply {
            add(patch)
        }
        val patchElement: Array<*>? = when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
                val makePathElement = getMethod(
                    pathList::class.java,
                    "makePathElements",
                    List::class.java,
                    File::class.java,
                    List::class.java
                ) ?: return
                Log.e("TAG", "makePathElement != null")
                val ioExceptions: ArrayList<IOException> = ArrayList()
                makePathElement.invoke(
                    pathList,
                    patchList,
                    application.cacheDir,
                    ioExceptions
                ) as Array<*>

            }
            else -> {
                val makePathElement = getMethod(
                    pathList::class.java,
                    "makeDexElements",
                    ArrayList::class.java,
                    File::class.java,
                    ArrayList::class.java
                ) ?: return
                Log.e("TAG", "makeDexElements != null")
                val ioExceptions: ArrayList<IOException> = ArrayList()
                makePathElement.invoke(
                    pathList,
                    patchList,
                    application.cacheDir,
                    ioExceptions
                ) as Array<*>

            }
        }

        val gapSize = patchElement?.size ?: 0
        Log.e("TAG", "patchElement size = $gapSize , oldElementPathSize = ${oldElementArray.size}")
        val fixArray = oldElementArray::class.java.componentType?.let {
            java.lang.reflect.Array.newInstance(
                it,
                oldElementArray.size + gapSize
            )
        } as Array<Any?>

        fixArray.forEachIndexed { index: Int, _: Any? ->
            Log.e("TAG", "index = $index")
            if (index + 1 <= gapSize) {
                var patchElement1 = patchElement?.get(index)
                Log.e("TAG", "ELEMENT TYPE = ${patchElement1.toString()}")
                fixArray[index] = patchElement1
            } else {
                fixArray[index] = oldElementArray[index - gapSize]
            }
        }
        dexElementsFiled.set(pathList, fixArray)
    }


    private fun getField(clazz: Class<out Any>, filedName: String): Field? {
        var startClazz: Class<*>? = clazz
        var filed: Field? = try {
            startClazz?.getDeclaredField(filedName)
        } catch (e: Exception) {
            null
        }
        while (filed == null && startClazz != null) {
            startClazz = startClazz.superclass
            filed = try {
                startClazz?.getDeclaredField(filedName)
            } catch (e: Exception) {
                null
            }
        }
        return filed?.apply { isAccessible = true }
    }

    private fun getMethod(
        clazz: Class<out Any>,
        methodName: String,
        vararg argusClazz: Class<out Any>
    ): Method? {
        var startClazz: Class<*>? = clazz
        var method: Method? = try {
            startClazz?.getDeclaredMethod(methodName, *argusClazz)
        } catch (e: Exception) {
            null
        }
        while (method == null && startClazz != null) {
            startClazz = startClazz.superclass
            method = try {
                startClazz?.getDeclaredMethod(methodName, *argusClazz)
            } catch (e: Exception) {
                null
            }
        }
        return method?.apply { isAccessible = true }
    }

使用:

kotlin 复制代码
private val fixPath = "app-debug.apk"
val path = Environment.getExternalStorageDirectory().absolutePath + File.separator + fixPath
        val file = File(path)
        if (!file.exists()) {
            Log.e("TAG", "patch file is not exist , path = ${file.absoluteFile}")
        } else {
            Log.e("TAG", "patch file exist , path = ${file.absoluteFile}")
            installPatch(this, file)
            try {

            } catch (e: Exception) {
                Log.e("TAG", "message = ${e.message}")
            }
        }

Tips

  • 补丁包可以打包成apk,或者是多个dex文件的zip
  • fix文件需要与原文件保持相同包名和类名
相关推荐
liang_jy1 小时前
Android 事件分发机制(二)—— 点击事件透传
android·面试·源码
圆号本昊4 小时前
Flutter Android Live2D 2026 实战:模型加载 + 集成渲染 + 显示全流程 + 10 个核心坑( OpenGL )
android·flutter·live2d
冬奇Lab5 小时前
ANR实战分析:一次audioserver死锁引发的系统级故障排查
android·性能优化·debug
冬奇Lab5 小时前
Android车机卡顿案例剖析:从Binder耗尽到单例缺失的深度排查
android·性能优化·debug
ZHANG13HAO5 小时前
调用脚本实现 App 自动升级(无需无感、允许进程中断)
android
圆号本昊6 小时前
【2025最新】Flutter 加载显示 Live2D 角色,实战与踩坑全链路分享
android·flutter
小曹要微笑7 小时前
MySQL的TRIM函数
android·数据库·mysql
mrsyf8 小时前
Android Studio Otter 2(2025.2.2版本)安装和Gradle配置
android·ide·android studio
DB虚空行者8 小时前
MySQL恢复之Binlog格式详解
android·数据库·mysql