Android热修复基本原理
热修复的原理主要有两种技术,一是不需要启动APP就能实现修复,在 Native 层实现的。一种是需要启动 App,在JAVA层实现的。 本文会介绍 Android 热修复的最基本原理( Java 层实现的,需要重启 App),那就是通过 ClassLoader 的机制,通过反射先加载补丁包的类,从而替换掉存在 BUG 的类,达到修复的目的。
JVM与Dalvik
Android应用程序运行在 Dalvik/ART 虚拟机,并且每一个应用程序对应有一个单独的 Dalvik 虚拟机实例。Dalvik 虚拟机实则也算是一个 Java 虚拟机,只不过它执行的不是 class 文件,而是 dex 文件。class 文件有很多冗余信息,dex文件会做去冗余信息的优化。
Dalvik 虚拟机与 Java 虚拟机共享有差不多的特性,差别在于两者执行的指令集是不一样的,前者的指令集是基本寄存器的,而后者的指令集是基于堆栈的。
基于栈的虚拟机
对于基于栈的虚拟机来说,每一个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每有一次方法调用,栈中便会多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。
JVM是基于栈的指令会很紧凑,比如一个方法体的执行,需要经过一连串的指令来完成,JVM指令集是没有任何变量的,执行过程中,结合局部变量表,完成方法体指令的执行,过程中实际上需要和内存空间进行不断交互,这也是为什么一个 java 程序跑起来后,会占用很大的内存的原因。JVM使用的指令只占一个字节,因而称为字节码。
ICONST_1 : 将int类型常量1压入操作数栈;
ISTORE 0 : 将栈顶int类型值存入局部变量0;
IADD : 执行int类型的加法 ;
基于寄存器的虚拟机
Dalvik是基于寄存器的虚拟机,其指令集需要指定源地址和目标地址(理解为变量声明),因此需要更多的指令空间。Dalvik的某些指令需要占用两个字节。
基于栈和基于寄存器的指令集,各有优势,一般来说,执行同样的功能,基于栈需要更多的指令(主要是load和store),而基于寄存器需要更多的指令空间。
基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。
而且 Dalvik 版程序的指令数明显减少了,数据移动次数也明显减少了。基于寄存器的虚拟机虽然比基于堆栈的虚拟机在硬件通用性上要差一些,但是它的代码执行效率却更好。 显然,后者最大的好处在于可以根据硬件实现更大的优化,这更适合移动设备的特点。
ART与Dalvik
Dalvik虚拟机执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT即时编译(Just In Time),即在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。
而ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。
那么,ART虚拟机执行的本地机器码是从哪里来?
Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字节码翻译成本地机器码的最恰当AOT时机也就发生在应用安装的时候。ART 引入了预先编译机制(Ahead Of Time),在安装时,ART 使用设备自带的 dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码。
简单来说在应用程序安装的过程中,ART就已经将所有的字节码重新编译成了机器码。应用程序运行过程中无需进行实时的编译工作,只需要进行直接调用。因此,ART极大的提高了应用程序的运行效率,但是这样会导致应用的安装速度会变慢,所以谷歌在Android N引入了混编机制。
Android N 的运作方式
ART 使用预先 (AOT) 编译,并且从 Android N混合使用 AOT 编译,解释和 JIT 。
1、最初安装应用时不进行任何 AOT 编译(安装又快了),运行过程中解释执行,对经常执行的方法进行 JIT,经过 JIT 编译的方法将会记录到 Profile 配置文件中。
2、当设备闲置和充电时,编译守护进程会运行,根据 Profile 文件对常用代码进行 AOT 编译。待下次运行时直接使用编译好的机器码。
ClassLoader
先回顾下 Java 中的 ClassLoader
BootstrapClassLoader 负责加载 JVM 运行时的核心类,比如 JAVA_HOME/lib/rt.jar
等等
ExtensionClassLoader 负责加载 JVM 的扩展类,比如 JAVA_HOME/lib/ext
下面的 jar 包
AppClassLoader 负责加载 classpath 里的 jar 包和目录
双亲委派机制
某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。
1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子 ClassLoader 再加载一次。
2、安全性考虑,防止核心API库被随意篡改。
1public abstract class ClassLoader {
2
3 public Class<?> loadClass(String className) throws ClassNotFoundException {
4 return loadClass(className, false);
5 }
6
7 protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
8 // 判断当前类加载器是否已经加载过指定类,若已加载则直接返回
9 Class<?> clazz = findLoadedClass(className);
10
11 if (clazz == null) {
12 // 如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回
13 clazz = parent.loadClass(className, false);
14
15 if (clazz == null) {
16 // 还没加载,则调用当前类加载器来加载[见小节3.2]
17 clazz = findClass(className);
18 }
19 }
20 return clazz;
21 }
22}
Android 中的 ClassLoader:
可以看看各个类的ClassLoader是不是和上图一致:
1Log.d(TAG, "String: " + String.class.getClassLoader());
2Log.d(TAG, "Activity: " + Activity.class.getClassLoader());
3Log.d(TAG, "AppCompatActivity: " + AppCompatActivity.class.getClassLoader());
4Log.d(TAG, "getClassLoader: " + getClassLoader());
5
6Log.d(TAG, "AppCompatActivity getParent: " + AppCompatActivity.class.getClassLoader().getParent());
7Log.d(TAG, "Application: " + Application.class.getClassLoader());
8
9ClassLoader classLoader = ClassLoader.getSystemClassLoader();
10Log.d(TAG, "getSystemClassLoader: " + classLoader);
可以看到 BootClassLoader 加载了 AndroidFramework层以及 rt.jar 里面的类,Jar包和自己的代码类由 PathClassLoader 来完成加载。主要来看看 PathClassLoader,DexClassLoader,BaseDexClassLoader,BootClassLoader,ClassLoader 这5个类加载器:
PathClassLoader
1public class PathClassLoader extends BaseDexClassLoader {
2 public PathClassLoader(String dexPath, ClassLoader parent) {
3 super((String)null, (File)null, (String)null, (ClassLoader)null);
4 throw new RuntimeException("Stub!");
5 }
6
7 public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
8 super((String)null, (File)null, (String)null, (ClassLoader)null);
9 throw new RuntimeException("Stub!");
10 }
11}
PathClassLoader 比较简单, 继承于 BaseDexClassLoader.
DexClassLoader
1public class DexClassLoader extends BaseDexClassLoader {
2 public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
3 super((String)null, (File)null, (String)null, (ClassLoader)null);
4 throw new RuntimeException("Stub!");
5 }
6}
DexClassLoader也同样,只是简单地封装了BaseDexClassLoader对象
BaseDexClassLoader
1public class BaseDexClassLoader extends ClassLoader {
2 public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
3 throw new RuntimeException("Stub!");
4 }
5}
BaseDexClassLoader 构造函数,有一个非常重要的过程,那就是初始化 DexPathList 对象。
另外该构造函数的参数说明:
- dexPath: 包含目标类或资源的
apk/jar
列表;当有多个路径则采用:
分割; - optimizedDirectory: 优化后 dex 文件存在的目录,可以为 null;
- libraryPath: native库所在路径列表;当有多个路径则采用
:
分割; - ClassLoader:父类的类加载器;
ClassLoader
1public abstract class ClassLoader {
2 private ClassLoader parent; // 记录父类加载器
3
4 protected ClassLoader() {
5 this(getSystemClassLoader(), false); // 见下文
6 }
7
8 protected ClassLoader(ClassLoader parentLoader) {
9 this(parentLoader, false);
10 }
11
12 ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
13 if (parentLoader == null && !nullAllowed) {
14 // 父类的类加载器为空,则抛出异常
15 throw new NullPointerException("parentLoader == null && !nullAllowed");
16 }
17 parent = parentLoader;
18 }
19}
BootClassLoader
1class BootClassLoader extends ClassLoader {
2 private static BootClassLoader instance;
3
4 public static synchronized BootClassLoader getInstance() {
5 if (instance == null) {
6 instance = new BootClassLoader();
7 }
8
9 return instance;
10 }
11
12 public BootClassLoader() {
13 super(null, true);
14 }
15}
以上所有的 ClassLoader 都直接或间接着继承于抽象类 ClassLoader。
DexPathList
BaseDexClassLoader 里 DexPathList 属性主要就是记录了 dex 文件的列表:
1final class DexPathList {
2 private Element[] dexElements;
3 private final List<File> nativeLibraryDirectories;
4 private final List<File> systemNativeLibraryDirectories;
5
6 final class DexPathList {
7 public DexPathList(ClassLoader definingContext, String dexPath,
8 String libraryPath, File optimizedDirectory) {
9 ...
10 this.definingContext = definingContext;
11 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
12
13 // 记录所有的 dexFile 文件
14 this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
15
16 // app 目录的 native 库
17 this.nativeLibraryDirectories = splitPaths(libraryPath, false);
18 // 系统目录的 native 库
19 this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
20 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
21 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
22 // 记录所有的 native 动态库
23 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);
24 ...
25 }
26}
DexPathList初始化过程,主要功能是收集以下两个变量信息:
1、dexElements: 根据多路径的分隔符 :
将dexPath转换成File列表,记录所有的dexFile
2、nativeLibraryPathElements: 记录所有的 Native 动态库,包括app 目录的 native 库和系统目录的 native 库。
makeDexElements 方法用于 dexFile 转为 Element 数组
1private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
2 List<IOException> suppressedExceptions, ClassLoader loader) {
3 return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
4}
5
6private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
7 List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
8 Element[] elements = new Element[files.size()]; // 获取文件个数
9 int elementsPos = 0;
10 for (File file : files) {
11 if (file.isDirectory()) {
12 elements[elementsPos++] = new Element(file);
13 } else if (file.isFile()) {
14 String name = file.getName();
15 DexFile dex = null;
16 // 匹配以.dex为后缀的文件
17 if (name.endsWith(DEX_SUFFIX)) {
18 dex = loadDexFile(file, optimizedDirectory, loader, elements);
19 if (dex != null) {
20 elements[elementsPos++] = new Element(dex, null);
21 }
22 } else {
23 dex = loadDexFile(file, optimizedDirectory, loader, elements);
24 if (dex == null) {
25 elements[elementsPos++] = new Element(file);
26 } else {
27 elements[elementsPos++] = new Element(dex, file);
28 }
29 }
30 if (dex != null && isTrusted) {
31 dex.setTrusted();
32 }
33 } else {
34 System.logW("ClassLoader referenced unknown path: " + file);
35 }
36 }
37 if (elementsPos != elements.length) {
38 elements = Arrays.copyOf(elements, elementsPos);
39 }
40
41 return elements;
42}
DexPathList.findClass 方法就是热修复基本原理的核心逻辑了
1public Class findClass(String name, List<Throwable> suppressed) {
2 for (Element element : dexElements) {
3 DexFile dex = element.dexFile;
4 if (dex != null) {
5 // 找到目标类,则直接返回
6 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
7 if (clazz != null) {
8 return clazz;
9 }
10 }
11 }
12 return null;
13}
- PathClassLoader: 主要用于系统和 app 的类加载器,其中 optimizedDirectory 为 null ,采用默认目录
/data/dalvik-cache/
- DexClassLoader: 可以从包含 classes.dex 的 jar 或者 apk 中,加载类的类加载器,可用于执行动态加载,但必须是app私有可写目录来缓存 odex 文件。能够加载系统没有安装的 apk 或者 jar 文件,因此很多插件化方案都是采用 DexClassLoader;
- BaseDexClassLoader: 比较基础的类加载器,PathClassLoader 和 DexClassLoader 都只是在构造函数上对其简单封装而已;
- BootClassLoader: 作为父类的类构造器。
热修复核心逻辑:在DexPathList.findClass() 过程,一个 Classloader 可以包含多个 dex 文件,每个 dex 文件被封装到一个 Element 对象,这些 Element 对象排列成有序的数组 dexElements 。当查找某个类时,会遍历所有的 dex 文件,如果找到则直接返回,不再继续遍历 dexElements。也就是说当两个类不同的 dex 中出现,会优先处理排在前面的 dex 文件,这便是热修复的核心精髓,将需要修复的类所打包的 dex 文件插入到 dexElements 前面。通过反射就可以很方便的添加我们修复后的 dex 到 DexPathList 中,这样错误的类就不会被加载了!
热修复代码实现
假设现有 Activity onCreate 内代码崩溃:
1public class Test {
2 public static void hypothesisError(){
3 throw new UnsupportedOperationException("some error!");
4 }
5}
6
7...
8
9public class MainActivity extends AppCompatActivity {
10 @Override
11 protected void onCreate(Bundle savedInstanceState) {
12 super.onCreate(savedInstanceState);
13 setContentView(R.layout.activity_main);
14 Test.hypothesisError();
15 }
16}
HotFixApplication 作为自定义 Application,执行热修复
1public class HotFixApplication extends Application {
2 @Override
3 protected void attachBaseContext(Context base) {
4 super.attachBaseContext(base);
5 // 执行热修复,插入补丁 dex
6 Hotfix.installPatch(this, new File("/sdcard/patch.dex"));
7 }
8}
/sdcard/patch.dex
是提前准备好的 dex 文件,里面也只有 Test 类,但是去掉了 Exception。下面是生成 dex 文件的命令,当然也可以是用 jar 包生成:
1C:\Android\SDK\build-tools\30.0.3\dx --dex --output=patch.dex cn/tim/dalvik_test/Test.class
Hotfix 实现的热修复流程:
1package cn.tim.dalvik_test;
2
3import android.app.Application;
4import android.os.Build;
5import android.util.Log;
6
7import java.io.File;
8import java.io.IOException;
9import java.lang.reflect.Field;
10import java.lang.reflect.InvocationTargetException;
11import java.lang.reflect.Method;
12import java.util.ArrayList;
13import java.util.List;
14
15public class Hotfix {
16 private static final String TAG = "Hotfix";
17
18 public static void installPatch(Application application, File patch){
19 // 获取当前应用的 ClassLoader PathClassLoader
20 ClassLoader classLoader = application.getClassLoader();
21
22 Log.d(TAG, "installPatch: classLoader = " + classLoader);
23
24 List<File> files = new ArrayList<>();
25 if (patch.exists()) {
26 files.add(patch);
27 }
28
29 File dexOptDir = application.getCacheDir();
30 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
31 try {
32 NewClassLoaderInjector.inject(application, classLoader, files);
33 } catch (Throwable throwable) {
34 throwable.printStackTrace();
35 }
36 } else {
37 try {
38 //23 6.0及以上
39 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
40// V23.install(classLoader, files, dexOptDir);
41 install(classLoader, files, dexOptDir);
42 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
43 V19.install(classLoader, files, dexOptDir); //4.4以上
44 } else { // >= 14
45 V14.install(classLoader, files, dexOptDir);
46 }
47 } catch (Exception e) {
48 e.printStackTrace();
49 }
50 }
51 }
52
53 // V23
54 private static void install(ClassLoader loader, List<File> additionalClassPathEntities,
55 File optimizeDir) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException {
56 // 1、反射获取 DexPathList 属性对象 pathList
57 Field pathListField = ShareReflectUtil.findField(loader, "pathList");
58 Object dexPathList = pathListField.get(loader);
59
60 ArrayList<IOException> suppressedExceptions = new ArrayList<>();
61 // 2、反射修改 pathList 的 DexElements
62 Object[] patchElements = makePathElements(dexPathList, additionalClassPathEntities, optimizeDir, suppressedExceptions);
63
64 // 3、将原本的 dexElements 与 makePathElements生成的数组合并
65 ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", patchElements);
66
67 if (suppressedExceptions.size() > 0) {
68 for (IOException e : suppressedExceptions) {
69 Log.w(TAG, "Exception in makePathElement", e);
70 throw e;
71 }
72 }
73 }
74
75 /**
76 * V23 dexFile 转为 Element数组
77 */
78 private static Object[] makePathElements(Object dexPathList, List<File> files, File optimizeDir,
79 List<IOException> suppressedException)
80 throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
81 // android 6 7 8 9均存在 makePathElements 方法
82 Method makePathElementsM = ShareReflectUtil.findMethod(dexPathList, "makePathElements",
83 List.class, File.class, List.class);
84 return (Object[]) makePathElementsM.invoke(dexPathList, files, optimizeDir, suppressedException);
85 }
86
87
88 private static final class V19 {
89
90 private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
91 File optimizedDirectory)
92 throws IllegalArgumentException, IllegalAccessException,
93 NoSuchFieldException, InvocationTargetException, NoSuchMethodException,
94 IOException {
95 Field pathListField = ShareReflectUtil.findField(loader, "pathList");
96 Object dexPathList = pathListField.get(loader);
97 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
98 ShareReflectUtil.expandFieldArray(dexPathList, "dexElements",
99 makeDexElements(dexPathList,
100 new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
101 suppressedExceptions));
102 if (suppressedExceptions.size() > 0) {
103 for (IOException e : suppressedExceptions) {
104 Log.w(TAG, "Exception in makeDexElement", e);
105 throw e;
106 }
107 }
108 }
109
110 private static Object[] makeDexElements(
111 Object dexPathList, ArrayList<File> files, File optimizedDirectory,
112 ArrayList<IOException> suppressedExceptions)
113 throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
114 Method makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements",
115 ArrayList.class, File.class,
116 ArrayList.class);
117
118
119 return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory,
120 suppressedExceptions);
121 }
122 }
123
124 /**
125 * 14, 15, 16, 17, 18.
126 */
127 private static final class V14 {
128 private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
129 File optimizedDirectory)
130 throws IllegalArgumentException, IllegalAccessException,
131 NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
132
133 Field pathListField = ShareReflectUtil.findField(loader, "pathList");
134 Object dexPathList = pathListField.get(loader);
135
136 ShareReflectUtil.expandFieldArray(dexPathList, "dexElements",
137 makeDexElements(dexPathList,
138 new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
139 }
140
141 private static Object[] makeDexElements(
142 Object dexPathList, ArrayList<File> files, File optimizedDirectory)
143 throws IllegalAccessException, InvocationTargetException,
144 NoSuchMethodException {
145 Method makeDexElements =
146 ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class,
147 File.class);
148 return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
149 }
150 }
151}
需要注意的是,Android 各个版本实现代码细微不一致,这是由于 Android 不同版本的虚拟机导致的,但是核心逻辑都是一致的。如果是在 Android N,需要自定义 ClassLoader 通过反射替换系统创建的 PathClassLoader,这块可以参考 Tinker 的逻辑实现。
反射工具类 ShareReflectUtil:
1package cn.tim.dalvik_test;
2
3import java.lang.reflect.Array;
4import java.lang.reflect.Field;
5import java.lang.reflect.Method;
6import java.util.Arrays;
7
8public class ShareReflectUtil {
9
10
11 /**
12 * 从 instance 到其父类 找 name 属性
13 *
14 * @param instance
15 * @param name
16 * @return
17 * @throws NoSuchFieldException
18 */
19 public static Field findField(Object instance, String name) throws NoSuchFieldException {
20 for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
21 try {
22 //查找当前类的 属性(不包括父类)
23 Field field = clazz.getDeclaredField(name);
24
25 if (!field.isAccessible()) {
26 field.setAccessible(true);
27 }
28 return field;
29 } catch (NoSuchFieldException e) {
30 // ignore and search next
31 }
32 }
33 throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
34 }
35
36 /**
37 * 从 instance 到其父类 找 name 方法
38 *
39 * @param instance
40 * @param name
41 * @return
42 * @throws NoSuchFieldException
43 */
44 public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
45 throws NoSuchMethodException {
46 for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
47 try {
48 Method method = clazz.getDeclaredMethod(name, parameterTypes);
49
50 if (!method.isAccessible()) {
51 method.setAccessible(true);
52 }
53
54 return method;
55 } catch (NoSuchMethodException e) {
56 // ignore and search next
57 }
58 }
59 throw new NoSuchMethodException("Method "
60 + name
61 + " with parameters "
62 + Arrays.asList(parameterTypes)
63 + " not found in " + instance.getClass());
64 }
65
66
67 /**
68 * @param instance
69 * @param fieldName
70 * @param patchElements 补丁的Element数组
71 * @throws NoSuchFieldException
72 * @throws IllegalArgumentException
73 * @throws IllegalAccessException
74 */
75 public static void expandFieldArray(Object instance, String fieldName, Object[] patchElements)
76 throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
77 //拿到 classloader中的dexelements 数组
78 Field dexElementsField = findField(instance, fieldName);
79 //old Element[]
80 Object[] dexElements = (Object[]) dexElementsField.get(instance);
81
82
83 //合并后的数组
84 Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(),
85 dexElements.length + patchElements.length);
86
87 // 先拷贝新数组
88 System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
89 System.arraycopy(dexElements, 0, newElements, patchElements.length, dexElements.length);
90
91 //修改 classLoader中 pathList的 dexelements
92 dexElementsField.set(instance, newElements);
93 }
94
95
96}
NewClassLoaderInjector.java
1package cn.tim.dalvik_test;
2
3import android.app.Application;
4import android.content.Context;
5import android.content.res.Resources;
6import android.os.Build;
7
8import java.io.File;
9import java.lang.reflect.Field;
10import java.util.List;
11
12import dalvik.system.DexFile;
13import dalvik.system.PathClassLoader;
14
15public class NewClassLoaderInjector {
16 private static final class DispatchClassLoader extends ClassLoader {
17 private final String mApplicationClassName;
18 private final ClassLoader mOldClassLoader;
19
20 private ClassLoader mNewClassLoader;
21
22 private final ThreadLocal<Boolean> mCallFindClassOfLeafDirectly = new ThreadLocal<Boolean>() {
23 @Override
24 protected Boolean initialValue() {
25 return false;
26 }
27 };
28
29 DispatchClassLoader(String applicationClassName, ClassLoader oldClassLoader) {
30 super(ClassLoader.getSystemClassLoader());
31 mApplicationClassName = applicationClassName;
32 mOldClassLoader = oldClassLoader;
33 }
34
35 void setNewClassLoader(ClassLoader classLoader) {
36 mNewClassLoader = classLoader;
37 }
38
39 @Override
40 protected Class<?> findClass(String name) throws ClassNotFoundException {
41 System.out.println("find:" + name);
42 if (mCallFindClassOfLeafDirectly.get()) {
43 return null;
44 }
45 // 1、Application类不需要修复,使用原本的类加载器获得
46 if (name.equals(mApplicationClassName)) {
47 return findClass(mOldClassLoader, name);
48 }
49 // 2、加载热修复框架的类 因为不需要修复,就用原本的类加载器获得
50 if (name.startsWith("com.enjoy.patch.")) {
51 return findClass(mOldClassLoader, name);
52 }
53
54 try {
55 return findClass(mNewClassLoader, name);
56 } catch (ClassNotFoundException ignored) {
57 return findClass(mOldClassLoader, name);
58 }
59 }
60
61 private Class<?> findClass(ClassLoader classLoader, String name) throws ClassNotFoundException {
62 try {
63 //双亲委托,所以可能会stackoverflow死循环,防止这个情况
64 mCallFindClassOfLeafDirectly.set(true);
65 return classLoader.loadClass(name);
66 } finally {
67 mCallFindClassOfLeafDirectly.set(false);
68 }
69 }
70 }
71
72 public static ClassLoader inject(Application app, ClassLoader oldClassLoader,List<File> patchs) throws Throwable {
73 // 分发加载任务的加载器,作为我们自己的加载器的父加载器
74 DispatchClassLoader dispatchClassLoader
75 = new DispatchClassLoader(app.getClass().getName(), oldClassLoader);
76 //创建我们自己的加载器
77 ClassLoader newClassLoader
78 = createNewClassLoader(app, oldClassLoader, dispatchClassLoader,patchs);
79
80 dispatchClassLoader.setNewClassLoader(newClassLoader);
81
82 doInject(app, newClassLoader);
83 return newClassLoader;
84 }
85
86 private static ClassLoader createNewClassLoader(Context context, ClassLoader oldClassLoader,
87 ClassLoader dispatchClassLoader,List<File> patchs) throws Throwable {
88 //得到pathList
89 Field pathListField = ShareReflectUtil.findField(oldClassLoader, "pathList");
90 Object oldPathList = pathListField.get(oldClassLoader);
91
92 //dexElements
93 Field dexElementsField = ShareReflectUtil.findField(oldPathList, "dexElements");
94 Object[] oldDexElements = (Object[]) dexElementsField.get(oldPathList);
95
96 //从Element上得到 dexFile
97 Field dexFileField = ShareReflectUtil.findField(oldDexElements[0], "dexFile");
98
99 // 获得原始的dexPath用于构造classloader
100 StringBuilder dexPathBuilder = new StringBuilder();
101 String packageName = context.getPackageName();
102 boolean isFirstItem = true;
103 for (File patch : patchs) {
104 if (isFirstItem) {
105 isFirstItem = false;
106 } else {
107 dexPathBuilder.append(File.pathSeparator);
108 }
109 dexPathBuilder.append(patch.getAbsolutePath());
110 }
111 for (Object oldDexElement : oldDexElements) {
112 String dexPath = null;
113 DexFile dexFile = (DexFile) dexFileField.get(oldDexElement);
114 if (dexFile != null) {
115 dexPath = dexFile.getName();
116 }
117 if (dexPath == null || dexPath.isEmpty()) {
118 continue;
119 }
120 if (!dexPath.contains("/" + packageName)) {
121 continue;
122 }
123 if (isFirstItem) {
124 isFirstItem = false;
125 } else {
126 dexPathBuilder.append(File.pathSeparator);
127 }
128 dexPathBuilder.append(dexPath);
129 }
130 final String combinedDexPath = dexPathBuilder.toString();
131
132 // app的native库(so) 文件目录 用于构造classloader
133 Field nativeLibraryDirectoriesField = ShareReflectUtil.findField(oldPathList, "nativeLibraryDirectories");
134 List<File> oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);
135
136
137 StringBuilder libraryPathBuilder = new StringBuilder();
138 isFirstItem = true;
139 for (File libDir : oldNativeLibraryDirectories) {
140 if (libDir == null) {
141 continue;
142 }
143 if (isFirstItem) {
144 isFirstItem = false;
145 } else {
146 libraryPathBuilder.append(File.pathSeparator);
147 }
148 libraryPathBuilder.append(libDir.getAbsolutePath());
149 }
150
151 String combinedLibraryPath = libraryPathBuilder.toString();
152
153 //创建自己的类加载器
154 ClassLoader result = new PathClassLoader(combinedDexPath, combinedLibraryPath, dispatchClassLoader);
155 ShareReflectUtil.findField(oldPathList, "definingContext").set(oldPathList, result);
156 ShareReflectUtil.findField(result, "parent").set(result, dispatchClassLoader);
157 return result;
158 }
159
160
161 private static void doInject(Application app, ClassLoader classLoader) throws Throwable {
162 Thread.currentThread().setContextClassLoader(classLoader);
163
164 Context baseContext = (Context) ShareReflectUtil.findField(app, "mBase").get(app);
165 Object basePackageInfo = ShareReflectUtil.findField(baseContext, "mPackageInfo").get(baseContext);
166 ShareReflectUtil.findField(basePackageInfo, "mClassLoader").set(basePackageInfo, classLoader);
167
168 if (Build.VERSION.SDK_INT < 27) {
169 Resources res = app.getResources();
170 try {
171 ShareReflectUtil.findField(res, "mClassLoader").set(res, classLoader);
172
173 final Object drawableInflater = ShareReflectUtil.findField(res, "mDrawableInflater").get(res);
174 if (drawableInflater != null) {
175 ShareReflectUtil.findField(drawableInflater, "mClassLoader").set(drawableInflater, classLoader);
176 }
177 } catch (Throwable ignored) {
178 // Ignored.
179 }
180 }
181 }
182}
可以看到已经修复了崩溃!
最近简单热修复的基本原理就已经结束了,如果要实现真正的热修复框架,那么就需要Gradle开发:字节码插桩 + 自动生成Dex 的方式以及其他的考虑点了。