双亲派遣机制
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
完结,DexPathList
的findClass(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
完结 **,热修复便是经过反射修正DexPathList
的 dexElements
特点,将修复的文件刺进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文件需求与原文件保持相同包名和类名