parent
							
								
									c2bf7f960d
								
							
						
					
					
						commit
						03ad51bf50
					
				| @ -0,0 +1,266 @@ | ||||
| --- | ||||
| 热修复 | ||||
| --- | ||||
| 
 | ||||
| #### 目录 | ||||
| 
 | ||||
| 1. 思维导图 | ||||
| 2. 概述 | ||||
| 3. 原理浅析 | ||||
|    - BaseDexClassLoader | ||||
|    - PathClassLoader | ||||
|    - DexClassLoader | ||||
|    - DexPathList | ||||
| 4. 实战 | ||||
| 5. 参考 | ||||
| 
 | ||||
| #### 思维导图 | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| #### 概述 | ||||
| 
 | ||||
| 一句话来说就是 dex 插桩。 | ||||
| 
 | ||||
| 在加载 dex 文件的时候,是放在一个 Element[] 数组里的,我们只要把修复好的 classes.dex 文件放在数组最前面被优先加载,那在加载有 bug 的 dex 文件里的 class 类的时候已经被加载过了,系统就不会加载,这时候带有 bug 的 class 就算被 “修复” 了。 | ||||
| 
 | ||||
| #### 原理浅析 | ||||
| 
 | ||||
| 热修复涉及的就是 dex 的加载流程。涉及的类有 BaseDexClassLoader、DexClassLoader、PathClassLoader、DexPathList 等等。 | ||||
| 
 | ||||
| ##### BaseDexClassLoader | ||||
| 
 | ||||
| ```java | ||||
| public class BaseDexClassLoader extends ClassLoader { | ||||
| 
 | ||||
|     private final DexPathList pathList; | ||||
| 
 | ||||
|     /** | ||||
|      * dexPath: 要加载的程序文件,一般是 dex 文件 | ||||
|      * optimizedDirectory: dex 文件解压目录 | ||||
|      * librarySearchPath: 加载程序文件时需要用到的库路径 | ||||
|      * parent: 父加载器 | ||||
|      */ | ||||
|     public BaseDexClassLoader(String dexPath, File optimizedDirectory, | ||||
|             String librarySearchPath, ClassLoader parent) { | ||||
|         super(parent); | ||||
|         this.pathList = new DexPathList(this, dexPath, librarySearchPath, null); | ||||
| 
 | ||||
|         if (reporter != null) { | ||||
|             reporter.report(this.pathList.getDexPaths()); | ||||
|         } | ||||
|     } | ||||
|      | ||||
| 	//... | ||||
| 
 | ||||
|     @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; | ||||
|     } | ||||
| 	//... | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 其实 BaseDexClassLoader 的 findClass 是调用 DexPathList 的 findClass 方法,如果为 null 就抛出 ClassNotFoundException。 | ||||
| 
 | ||||
| ##### DexClassLoader | ||||
| 
 | ||||
| ##### 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); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ##### DexClassLoader | ||||
| 
 | ||||
| ```java | ||||
| public class DexClassLoader extends BaseDexClassLoader { | ||||
|     | ||||
|     public DexClassLoader(String dexPath, String optimizedDirectory, | ||||
|             String librarySearchPath, ClassLoader parent) { | ||||
|         super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 可以看到,其实 DexClassLoader 和 PathClassLoader 都没有做什么,都是调用父类方法,只不过 DexClassLoader 多传了一个optimizedDirectory,它们的使用场景不一样: | ||||
| 
 | ||||
| PathClassLoader:只能加载已经安装到 Android 系统中的 apk 文件,是 Android 默认使用的类加载器。 | ||||
| 
 | ||||
| DexClassLoader:可以加载任意目录下的 dex/jar/apk/zip 文件,比 PathClassLoader,是实现热修复的重点。 | ||||
| 
 | ||||
| ##### DexPathList | ||||
| 
 | ||||
| ```java | ||||
| final class DexPathList { | ||||
|     private static final String DEX_SUFFIX = ".dex"; | ||||
|     private static final String zipSeparator = "!/"; | ||||
| 
 | ||||
|     private final ClassLoader definingContext; | ||||
| 
 | ||||
|     /** | ||||
|      * dex 文件数组 | ||||
|      */ | ||||
|     private Element[] dexElements; | ||||
|          | ||||
|     public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) { | ||||
| 		//... | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * BaseDexClassLoader 中实例化的是这个构造方法 | ||||
|      */ | ||||
|     public DexPathList(ClassLoader definingContext, String dexPath, | ||||
|             String librarySearchPath, File optimizedDirectory) { | ||||
|          | ||||
| 		//... | ||||
| 
 | ||||
|         this.definingContext = definingContext; | ||||
| 
 | ||||
|         this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, | ||||
|                                            suppressedExceptions, definingContext); | ||||
| 		//... | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 生成 Element 集合,Element 里保存着 DexFile 和 文件路径 | ||||
|      * 遍历该 APP 私有目录所有文件,拿到以 .dex 结尾的文件 | ||||
|      */ | ||||
|     private static Element[] makeDexElements(List<File> files, File optimizedDirectory, | ||||
|             List<IOException> suppressedExceptions, ClassLoader loader) { | ||||
|       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(); | ||||
| 
 | ||||
|               if (name.endsWith(DEX_SUFFIX)) { | ||||
|                   // Raw dex file (not inside a zip/jar). | ||||
|                   try { | ||||
|                       DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements); | ||||
|                       if (dex != null) { | ||||
|                           elements[elementsPos++] = new Element(dex, null); | ||||
|                       } | ||||
|                   } catch (IOException suppressed) { | ||||
|                       System.logE("Unable to load dex file: " + file, suppressed); | ||||
|                       suppressedExceptions.add(suppressed); | ||||
|                   } | ||||
|               } else { | ||||
|                   DexFile dex = null; | ||||
|                   try { | ||||
|                       dex = loadDexFile(file, optimizedDirectory, loader, elements); | ||||
|                   } catch (IOException suppressed) { | ||||
|                       suppressedExceptions.add(suppressed); | ||||
|                   } | ||||
| 
 | ||||
|                   if (dex == null) { | ||||
|                       elements[elementsPos++] = new Element(file); | ||||
|                   } else { | ||||
|                       elements[elementsPos++] = new Element(dex, file); | ||||
|                   } | ||||
|               } | ||||
|           } else { | ||||
|               System.logW("ClassLoader referenced unknown path: " + file); | ||||
|           } | ||||
|       } | ||||
|       if (elementsPos != elements.length) { | ||||
|           elements = Arrays.copyOf(elements, elementsPos); | ||||
|       } | ||||
|       return elements; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 加载 dex 文件 | ||||
|      */ | ||||
|     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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      *  | ||||
|      */ | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * dex 文件路径集合 | ||||
|      */ | ||||
|     /*package*/ List<String> getDexPaths() { | ||||
|         List<String> dexPaths = new ArrayList<String>(); | ||||
|         for (Element e : dexElements) { | ||||
|             String dexPath = e.getDexPath(); | ||||
|             if (dexPath != null) { | ||||
|                 dexPaths.add(dexPath); | ||||
|             } | ||||
|         } | ||||
|         return dexPaths; | ||||
|     } | ||||
| 
 | ||||
|      static class Element { | ||||
|   | ||||
|         private final File path; | ||||
| 
 | ||||
|         private final DexFile dexFile; | ||||
| 
 | ||||
|    		//... | ||||
| 
 | ||||
|         public Class<?> findClass(String name, ClassLoader definingContext, | ||||
|                 List<Throwable> suppressed) { | ||||
|             return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) | ||||
|                     : null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### 实战 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #### 参考 | ||||
| 
 | ||||
| [热修复——深入浅出原理与实现](https://juejin.im/post/5a0ad2b551882531ba1077a2) | ||||
| 
 | ||||
| [https://github.com/GitLqr/HotFixDemo](https://github.com/GitLqr/HotFixDemo) | ||||
| 
 | ||||
| [网易云课堂]( | ||||
| https://study.163.com/course/courseLearn.htm?courseId=1208968811#/learn/live?lessonId=1278455246&courseId=1208968811) | ||||
| After Width: | Height: | Size: 78 KiB | 
| After Width: | Height: | Size: 53 KiB | 
					Loading…
					
					
				
		Reference in new issue