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
//继承ClassLoaderpublic 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对象来执行的@Overrideprotected 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,则可以正确执行

更多相关文章

  1. 万能imageLoader加载图片的包装,直接用
  2. C# android base-64 字符数组的无效长度
  3. Android(安卓)中解析JSON形式的数据
  4. Android快速开发框架之xUtils---图片模块
  5. android 自定义ButtonTab , ActivityGroup 动态加载 activity
  6. [置顶] 我的Android进阶之旅------>android异步加载图片显示,并且
  7. 2010.12.28(3)——— android alertDialog 复选框问题
  8. Android入门(9)AudioRecord和AudioTrack类的使用http://blog.sina.
  9. Android(安卓)Studio中图片的格式转换

随机推荐

  1. Android小技术知识(多用于面试)
  2. Android(安卓)Handler内存泄漏解决方法
  3. Android(安卓)TabHost布局
  4. Android之TextView
  5. XML的pull解析
  6. 切换Activity时的动画overridePendingTra
  7. [Android]应用语言切换的三种方法
  8. Android金毛狮王之Service
  9. 如何同时启动两个Android模拟器
  10. Android开发秘籍学习笔记(十)