这个方案是基于Android的类加载机制 PathClassLoader负责加载Android的系统类和你自己定义的应用程序类; DexClassLoader负责加载任意目录下的dex和zip、jar、apk内的dex文件; PathClassLoader0和DexClassLoader都继承自BaseDexClassLoader,这两者类似,只是增加了调用父类构造函数的构造函数。所以功能不同只是因为定义,是设计人员要求这做,其实PathClassLoader能做的,DexClassLoader也能做
首先我们要了解类加载的流程,加载类的方法就是loadClass()
调用loadClass(),查看此类是否已经被加载过;是,直接返回结果;不是,跳到步骤2如果父类不为null,调用父类加载器的loadClass();如果父类为null,调用当前类加载器的findClass()加载,这是真正加载类的方法。最后返回结果这里的父类指的是类加载其中的属性ClassLoader parent;而不是继承关系。 BaseDexClassLoader继承了ClassLoader,重写了findClass()。我们看看这个方法干了什么。
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException( "Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; }重要的是这一行Class c = pathList.findClass(name, suppressedExceptions);,很显然,类加载的操作是在这里完成的。 pathList是BaseDexClassLoader的一个属性,pathList里面还有一个Element数组,你可以把每一个Elment看成是一个.dex文件。
//DexPathList.class public Class<?> findClass (String name, List < Throwable > suppressed){ for (Element element : dexElements) { Class<?> clazz = element.findClass(name, definingContext, suppressed); if (clazz != null) { return clazz; } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }pathList遍历了Element数组,依次调用Element.findClass(),这个源码就不看了,反正如果这个Element元素是我们要加载的类就会返回正确结果,否则继续下一个。
那我们现在就知道了,Android系统和应用程序类加载时,会去加载pathList内的一个个Element,如果找到对应的.dex就会返回。 那么我们就可以利用这一点,首先DexClassLoader跟PathClassLoader都继承自BaseDexClassLoader,所以我们可以利用DexClassLoader去加载我们需要替换的新类,然后通过反射获得DexClassLoader和PathClassLoader的pathList,再获得Element集合,最后合并两个集合,这里要注意,DexClassLoader的Element集合需要放在前面。 这就是代码替换的原理。