Android 类加载机制以及基于类加载机制的热修复

android 与 java 的类加载器

类别 加载文件 类加载器分类
java .class 文件 {{java类加载机制}}
android .dex 文件 {{android类加载机制}}

java 类加载机制

  1. BootStrapClassLoader:
    启动类加载器
    由 C/C++代码实现
    加载 .../jre/lib 下类库 / -Xbootclasspath 参数指定的路径中的类库
    顶级类加载器

  2. ExtClassLoader
    扩展类加载器
    sun.misc.Launcher 中的内部类(即 sun.misc.Launcher$ExtClassLoader)
    加载 .../jre/lib/ext 下的类库 / -Djava.ext.dirs 参数指定的路径中的类库
    parent: 无,此时可以看做 parent 就是 BootStrapClassLoader

  3. AppClassLoader
    应用程序类加载器
    sun.misc.Launcher 中的内部类 (即 sun.misc.Launcher$AppClassLoader)
    加载 classpath 所指定的类库
    parent: ExtClassLoader

  4. 自定义 classLoader
    继承 ClassLoader
    加载自行指定位置的类库
    parent: AppClassLoader

  • java 类加载机制采用双亲委派机制:
protected Class loadClass(String name, boolean resolve)throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        //1.检查请求加载类是否已经加载
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                //2.请求加载类还未加载,parent!=null,通过parent加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    //3.parent == null,尝试采用BootStrapClassLoader启动类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            //4.以上三步均未成功加载,通过本身加载
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
  • 自定义 classLoader
//继承ClassLoader
public class MyClassLoader extends ClassLoader {
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    //重写findClass(String name)方法
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = getByte(name);
            //调用defineClass(name, data, 0, data.length)返回class
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    //获取流
    private byte[] getByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(name);
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }
}

Android 类加载器机制

注: 除了 classLoader 与 BootClassLoader,可以通过 as 直接查看,其他均无法直接查看,源码取自互联网

  1. BootClassLoader
    java.lang.ClassLoader 中的内部类(即 java.lang.ClassLoader$BootClassLoader)
    预加载常用类

  2. PathClassLoader
    dalvik.system.PathClassLoader
    加载已经安装的 Apk(/data/app/package)的 Apk 文件
    只有构造方法

package dalvik.system;

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);
    }
}
  1. DexClassLoader
    dalvik.system.DexClassLoader
    加载任意位置的 dex/zip/apk/jar
    只有构造方法
package dalvik.system;
import java.io.File;
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}
  1. BaseDexClassLoader
    PathClassLoader/DexClassLoader 均继承于 BaseDexClassLoader
  • PathClassLoader 与 DexClassLoader 的区别
    只有构造函数所传参数不同
    DexClassLoader 比 PathClassLoader 多传 optimizedDirectory: dex 优化缓存路径
    optimizedDirectory : 用于存放:加载 jar/apk/zip 等压缩格式的程序文件时解压出其中的 dex 文件,如果本身只是 dex 文件,也会进行复制到此文件中
    必须是需要加载的程序目录:即 data/data/packname/xxx
  • android 中的 java.lang.ClassLoader 与 java 中的 java.lang.ClassLoader 并不相同 *
//可以看到android是无法通过defineClass来获取class类的
protected final Class defineClass(String name, byte[] b, int off, int len)throws ClassFormatError{
        throw new UnsupportedOperationException("can't load this type of class file");
}
  • DexClassLoader 类加载流程
  • BaseDexClassLoader
private final DexPathList pathList;

/**
 * 首先执行ClassLoader构造方法
 * 之后执行PathClassLoader/DexClassLoader的构造方法
 * 创建DexPathList对象
 */
public class BaseDexClassLoader extends ClassLoader {
    ...
    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent){
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    ...
}

//findclass是交由构造方法中所创建的DexPathList对象来执行的
@Override
protected Class findClass(String name) throws ClassNotFoundException {
    List suppressedExceptions = new ArrayList();
    // 实质是通过pathList的对象findClass()方法来获取class
    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;
}
  • DexPathList
private final Element[] dexElements;

/**
 * 创建Element[]数组
 * dexPath是要加载的dex/zip/jar/apk的原始路径,支持多个以:分割
 * 遍历存入优化缓存路径当中
 * 包装成Element对象,以Element[]数组的形式返回
 */
public DexPathList(ClassLoader definingContext, String dexPath,
        String libraryPath, File optimizedDirectory) {
    ...
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
    ...
}

/**
 * 遍历Element[]数组
 * 找到name所对应的DexFile
 * 由DexFile加载dex文件,值得注意的是,加载成功一个就会直接返回,后面有相同name的也不会加载了
 * 通过类加载机制实现的热修复就是采用这个机制,将修复类的dex放在Element[]数组的最前方,则只会加载修复类的dex
 * 而不会去加载本身所具有的dex即具有bug的dex
 */
public Class findClass(String name, List suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

基于 android 类加载机制实现的热修复

从以上的分析可以得知:

  • DexClassLoader 创建
    构造方法传入希望加载的任意路径的 dex/zip/jar/apk 路径,以:分割
    构造方法传入优化缓存路径,需 data/data/packageName/xxx
  • BaseDexClassLoader
    构造方法创建 DexPathList 对象
    DexPathList 对象构造方法获取 Element[]数组
    获取 Element[]数组的过程是将 dex 文件包装成 Element 对象,并将任意位置的 dex/zip/jar/apk 存储到指定的优化缓存路径当中

  • DexClassLoader 继承于 BaseDexClassLoader
    DexClassLoader 并没有重写 BaseDexClassLoader 的 findClass()方法
    DexClassLoader 调用 findClass()
    BaseDexClassLoader 调用 findClass()
    DexPathList 调用 findClass()
    遍历 Element[]数组,根据所传入 name 来加载对应 dex 文件
    找到第一个之后,则直接加载并返回,后续的不再继续进行查找

  • 基于 android 类加载机制实现的热修复
    任意位置存入修复后的 dex/zip/jar/apk,设置优化缓存路径
    创建 DexClassLoader
    构造方法将任意位置存入的修复后的 dex/zip/jar/apk 存入设置的优化缓存路径当中,并包装成 Element[]数组
    此时,我们未进行 findClass(), 修复后的 dex 文件并没有被类加载器加载
    最理想的情况是,在第一次调用出现 bug 之前,findClass()进行修复后的 dex 文件加载替换掉有 bug 的 dex
    我们也不可能去判断哪个时候是第一次加载,然后在其之前调用 DexClassLoader 中的 findClass()来加载,因为当我们上一次发布,即具有 bug 的程序时,是根本不知道这个 bug 的存在的
    那么,换一个角度,当通过类加载器加载我们普通的 apk 中的类的时候,是通过 PathClassLoader 来完成的,那么,只需要修改 PathClassLoader 当中的 Element[]数组即可
    将通过 DexClassLoader 生成的 Element[]数组,放置于 PathClassLoader 的 Element[]之前
    那么,第一次加载 bug 的类的时候,系统通过 PathClassLoader 进行 findClass(),而此时在最前面的是我们在 DexClassLoader 中生成的 Element[]数组,这样,就不会再加载原有的具有 bug 的类了,也就是完成了修复

示例:
github示例

如何生成dex文件

  • 注意:

当已经通过类加载器加载了具有 bug 的类之后,修复类则不会加载了,所以我们将修复放在尽可能前一些的位置,这也是为什么我们常说的热修复需要重启的原因,可能在我们的修复包发送之前,客户已经启动过我们的应用了,也已经加载过 bug 类,他一直没有退过,则永远不会加载修复类

就像示例中所展示的,如果先点击了 add,则已经加载过 test 类,再点击就没有作用了
这时候,只需要把程序退出,然后重新启动,先点击修复,再点击 add,则可以正确执行

你可能感兴趣的:(Android 类加载机制以及基于类加载机制的热修复)