我的主页:chengang.plus/java
文章将会同步到我的微信公众号:Android部落格android
咱们开发的java代码经过编译生成.class文件,而后经过dx工具生成机器能够识别的dex文件。数组
Android中采用ClassLoader加载dex文件,加载完成以后能够经过反射调用其中的方法,适合那些不依赖文件等资源的业务,而打点刚好比较适合使用dex加载的方式。微信
Android中有PathClassLoader和DexClassLoader ,他们都继承自ClassLoader。他们的继承关系以下:markdown
看看三个类的源码:ide
dalvik/system/PathClassLoader.java函数
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } } 复制代码
dalvik/system/DexClassLoader.java工具
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, null, librarySearchPath, parent); } } 复制代码
public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { this(dexPath, optimizedDirectory, librarySearchPath, parent, false); } public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted) { super(parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); if (reporter != null) { reportClassLoaderChain(); } } public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) { // TODO We should support giving this a library search path maybe. super(parent); this.pathList = new DexPathList(this, dexFiles); } 复制代码
在BaseDexClassLoader中经过DexPathList类具体的处理Dex,他的构造函数以下:oop
private Element[] dexElements; DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) { this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted); } private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) { Element[] elements = new Element[files.size()]; int elementsPos = 0; for (File file : files) { if (file.isDirectory()) { elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); DexFile dex = null; dex = loadDexFile(file, optimizedDirectory, loader, elements); } } retrun elements; } private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException { if (optimizedDirectory == null) { return new DexFile(file, loader, elements); } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements); } } 复制代码
最终经过DexFile加载loadDex方法在native层实现对dex的加载和处理。ui
而分析PathClassLoader
和DexClassLoader
的构造函数能够看到Android 9.0中的DexClassLoader构造函数的optimizedDirectory参数默认是null。因此这里要针对版本的不一样作差别化处理。
咱们通常加载Dex的方式是:
classLoader = context.getClassLoader(); classLoader.loadClass("you class path"); 复制代码
这里作一下差别化:
public ClassLoader load(Context context,String dexName) { mLoaded = false; ClassLoader classLoader = null; File dexOutputDir = context.getDir("dex", 0); File dexFile = new File(dexOutputDir.getAbsolutePath(), dexName); Log.d(TAG, "load start"); if (!dexFile.exists()) { return null; } String dexPath = dexFile.getAbsolutePath(); Log.d(TAG, "dPath = " + dexPath); int version = android.os.Build.VERSION.SDK_INT; if (version >= 25) { BaseDexClassLoader parent = (BaseDexClassLoader) context.getClassLoader(); Class<BaseDexClassLoader> c = BaseDexClassLoader.class; Method method; try { method = c.getMethod("addDexPath", String.class); method.invoke(parent, dexPath); mLoaded = true; classLoader = parent; return classLoader; } catch (Exception e) { e.printStackTrace(); } } Log.d(TAG, "mLoaded1 = " + mLoaded); if (!mLoaded) { ArrayList<File> files = new ArrayList<File>(); files.add(dexFile); classLoader = context.getClassLoader(); classLoader.loadClass("you class path"); try { Field pathListField = findField(classLoader, "pathList"); Object dexPathList = pathListField.get(classLoader); ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); if (version < 19) { expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, files, null)); } else if (version < 23) { expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, files, null, suppressedExceptions)); } else { expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList, files, null, suppressedExceptions)); } } catch (Exception e1) { e1.printStackTrace(); } } Log.d(TAG, "mLoaded2 = " + mLoaded); if (mLoaded) { return classLoader; } return null; } 复制代码
当sdk_int大于等于25时,经过反射BaseDexClassLoader的addDexPath方法直接添加dex文件到DexPathList的Element[]数组中,然后续findClass方法的逻辑就是遍历这个数据找到对应的dex文件。
这种状况下,先反射获取ClassLoader的pathList对象,这里的ClassLoader实际是PathClassLoader,可是最终都会到BaseDexClassLoader的pathList。
获取到这个变量以后,先调用makeDexElements
方法将生成的dex对象放到一个数组中,接着在expandFieldArray
方法中将就的dex数组和新的dex数组合并:
private Object[] makeDexElements(Object dexPathList, ArrayList<File> files, File optimizedDirectory) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { Method makeDexElements = findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class); return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory); } 复制代码
这里的makeDexElements
方法对应的是DexPathList
的makeDexElements
方法,最终目的是将dex对象添加到Element[]
数组中,做为新的数组返回。
expandFieldArray
对应的是DexPathList
的addDexPath
方法,将新旧Element[]
数组合到一个数组中,旧的数组在前面。这样就致使了相同文件名的dex文件,最新修复了bug的dex不能当即生效。
private void expandFieldArray(Object instance, String fieldName, Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field jlrField = findField(instance, fieldName); Object[] original = (Object[]) jlrField.get(instance); Object[] combined = (Object[]) Array.newInstance(original.getClass() .getComponentType(), original.length + extraElements.length); System.arraycopy(original, 0, combined, 0, original.length); System.arraycopy(extraElements, 0, combined, original.length, extraElements.length); jlrField.set(instance, combined); mLoaded = true; Log.d(TAG, "expandFieldArray"); } 复制代码
对比看下DexPathList
中的addDexPath
方法:
public void addDexPath(String dexPath, File optimizedDirectory, boolean isTrusted) { final List<IOException> suppressedExceptionList = new ArrayList<IOException>(); final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptionList, definingContext, isTrusted); if (newElements != null && newElements.length > 0) { final Element[] oldElements = dexElements; dexElements = new Element[oldElements.length + newElements.length]; System.arraycopy( oldElements, 0, dexElements, 0, oldElements.length); System.arraycopy( newElements, 0, dexElements, oldElements.length, newElements.length); } } 复制代码
可见咱们本身的操做对应着DexPathList
的addDexPath
方法。
在咱们本身的expandFieldArray
方法最后经过执行jlrField.set(instance, combined);
,将合并后的Element[]
数组赋值给DexPathList
的Element[] dexElements
。
当上述操做完成以后,就要调用loadClass方法加载dex文件中的类了,这个方法在ClassLoader类中定义:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } if (c == null) { c = findClass(name); } return c; } 复制代码
parent
由Context.getClassLoader
所属的PathClassLoader传递,一直从BaseDexClassLoader
到ClassLoader
。
到这里要熟悉应用被建立初始化的流程了,这里先不引伸过去,只须要知道这个parent是BootClassLoader
类型。
@Override protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className); if (clazz == null) { clazz = findClass(className); } return clazz; } 复制代码
若是还找不到,就调用BaseDexClassLoader
的findClass
方法了。
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); return c; } 复制代码
到这里若是还找不到就抛异常出来了,ClassNotFoundException
。
上边的流程就是各大博客上面说的双亲委派机制,父类先从已经加载的类里面找,找不到的话,再从本身BaseDexClassLoader
的findClass
方法里面去找。
到这里,基本就将Dex加载的流程搞清楚了,可是这样的加载会致使新加载的Dex不能当即生效,必须从新启动应用以后才能生效。针对这种问题,能够将Dex热更新模块放到一个单独的进程中,当Dex加载完毕以后,调用killProcess方法自杀,而后由另外一个进程拉活重启。