Dex热修复原理

市场上热修复有两种一种是基于multidex的更新修复(比如tinker),另外一种是native hook(比如dexposed),tinker这种是反射获取dexelements数组,修改dex加载顺序。今天我们主要介绍dex这种。
热修复包括两个部分

  1. 从远程端下载修复好bug的补丁包
  2. 客户端安装补丁包,加载补丁包的类。

使用android�类加载器,在类没被加载到模拟器前(一般在application热修复,如果类已加载,再去记载相同的类就无效了)然后先加载补丁dex,再去加载原来的app里面的dex,因为加载过的类
不会被加载第二次,从而做到热修复。

1. 类加载器

android的类加载器分为PathClassLoader和DexClassLoader.他们两者之间的区别就是

  1. PathClassLoader只能加载安装到手机里面的dex(比如data/app/包名里面的dex)
  2. DexClassLoader 可以加载任意目录下的dex/jar/apk/zip.

下面我们从源码看看他们到底有什么不同

public class PathClassLoader extends BaseDexClassLoader {
   
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
   }

   
   public PathClassLoader(String dexPath, String libraryPath,
           ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
}


//分割线
    public class DexClassLoader extends BaseDexClassLoader {
        public DexClassLoader(String dexPath, String optimizedDirectory,
          String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
   }
}

从代码可以看到两个classloader都继承了BaseDexClassLoader
调用了父类的构造方法,dexclassloader多传入了一个optimizedDirectory文件,然后从注解可以看到optimizedDirectory参数是dex输出的文件。接下来我们再看basedexclassloader里面做了什么骚操作。

public class BaseDexClassLoader extends ClassLoader {
     private final DexPathList pathList;
 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
       this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
   }

   @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        List suppressedExceptions = new ArrayList();
       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;
    }

    @Override
    protected URL findResource(String name) {
        return pathList.findResource(name);
    }

    @Override
    protected Enumeration findResources(String name) {
        return pathList.findResources(name);
   }

   @Override
    public String findLibrary(String name) {
       return pathList.findLibrary(name);
   }
   ....

BaseDexClassLoader的构造参数分别是

  1. 一系列的补丁包路径
  2. 补丁包输出路径
  3. 加载用到库的路径,比如c 那些native库
  4. 父加载器

其中DexPathList通过这四个参数完成初始化。最后通过findclass方法 里面的DexPathList.findClass来返回类。

2. DexPathList源码

接着看DexPathList源码里面做了什么
首先看构造函数

Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
                //省略参数的判断

this.definingContext = definingContext;
       ArrayList suppressedExceptions = new ArrayList();
       this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                         suppressedExceptions);
       if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
       this.nativeLibraryDirectories = splitLibraryPath(libraryPath);

}

从代码可以看到给classloader赋值,创建一个异常的list,给dexelements赋值,这里的dexelements是一个数组。来看看splitDexPath到底做什么了。
看到splitAndAdd方法会根据:来截取字符串,就是多个dexpath之间用:分割,然后变成file,被加进去List flies里面。最后看makeDexElements方法,
遍历files,首先判断是否文件夹,是的话全部加进去elements,最后装转成ELement数组。
最后在DexPathList的findclass方法,将Element的数组每个Element对象的dex加载成class。

所以我们在什么时候插入补丁包的dex呢,就在Dexpatchlist类的findclass方法里面。将补丁的Elements数组加上原来的Elements数组,一起循环获取dex返回class。我们可以通过反射去获取elements数组。然后合并数组。调用findclass方法。以上就是multidex热修复的原理。

你可能感兴趣的:(Dex热修复原理)