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 的方式以及其他的考虑点了。

Reference

《Android N混合编译与对热补丁影响解析》

《Android类加载器ClassLoader》