双亲派遣机制

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

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;
}

Classloader间的组合联系如下

双亲委派机制

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

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

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

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

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)

//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

实例:

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 }
    }

运用:

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文件需求与原文件保持相同包名和类名