美文网首页
Android ClassLoader

Android ClassLoader

作者: 杨旭_ | 来源:发表于2020-11-19 10:03 被阅读0次

    本文参考《Android进阶解密》作者刘望舒,吃水不忘挖井人。

    按照惯例先给大家来个段子。 哈哈哈嗝

    正文开始

    目录
    1,javaClassLoader
    2,AndroidClassLoader
    3,知识点总结
    image.png
    1,Java中的ClassLoader
    简介
    类名 编写语言 加载目录
    bootstrapCLassloader cpp JAVA_Home\jre\lib
    ExtensionsClassloader java java_home\jre\lib\ext
    applicationClassLoader java classpach 路径下

    双亲委派机制

    双亲委派机制的优点
    1,避免重复加载相同的类,也就是一个类,无论从自己到父类只能有一个Loader进行加载,并且缓存
    2,保证核心类的安全性,例如,自定义String想要去替换系统核心的类。
    代码
      protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException{
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);//步骤1
                if (c == null) {
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);//步骤2
                        } else {
                            c = findBootstrapClassOrNull(name);//步骤3
                        }
                    } 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.
                        c = findClass(name);
                    }
                }
                return c;// 步骤5
        }
    
    理论

    当需要加载一个类的时候
    1,首先判断是否加载过,如果有直接返回。
    2,如果没有加载过,先让自己的父类去加载,父类再调用自己的父类加载,一直到父类为null的时候。
    3,调用native方法进行加载也就是bootStrapClassloader进行加载。
    4,如果父类能加载直接返回,如果没有加载到就自己去加载
    5,返回加载的类。

    2,Android中的ClassLoader

    Android中的classLoader 和java 有所不同,我们都知道Java加载的是jar包文件,Android 加载的是dex文件

    CLassLoader
    名称 继承 作用
    BootClassLoader classLoader Android中所有ClassLoader的parent
    BaseDexClassLoader classLoader 加载dex文件,主要逻辑都在这里边
    SecureClassLoader classLoader 增加安全性
    URLClassLoader SecureClassLoader URL路径从jar文件中加载类和资源
    PathClassLoader BaseDexClassLoader Android默认加载器,只能加载已经安装的apk文件/data/app目录)
    DexClassLoader BaseDexClassLoader \color{red}{实现热修复的重点,加载任意目录下的dex文件。 }
    InMemoryDexClassLoader BaseDexClassLoader 加载内存中的类文件

    BaseDexClassLoader

    构造函数
    
     public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                                  String librarySearchPath, ClassLoader parent, boolean isTrusted) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);//1
            ...
        }
    
    

    dexPath:包含类和资源的 apk文件列表,由 File.pathSeparator分隔,在Android上默认为:,(默认为冒号)
    optimizedDirectory:由于dex文件被包含在APK,因此在装载目标类之前需要先从APK文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径。这也是对apk中dex根据平台进行ODEX优化的过程。自API26开始无效。
    librarySearchPath:指目标类中所使用的C/C++库存放的路径,可以为null。
    parent:父ClassLoader引用。

    看一下BaseDexClassLoader 的findClass方法
     protected Class<?> findClass(String name) throws ClassNotFoundException {
            List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    
    //这里是重点
            Class c = pathList.findClass(name, suppressedExceptions);//1
            if (c == null) {
                ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
                for (Throwable t : suppressedExceptions) {
                    cnfe.addSuppressed(t);
                }
                throw cnfe;//2
            }
            return c;
        }
    

    注意这里的pathList,在构造函数中创建,这是一个对象并不是一个集合,1处实际调用了pathList对象的findClass方法,第一个参数是全类名,第二个参数是异常集合,把方法内部抛出的异常,添加进去,这种类似C++的指针传了进去,在2处的代码抛出。

    DexPathList 源码
     final class DexPathList {
    
    
        private final Element[] dexElements;//数组,这里其实就是把dex文件封装起来了
        
        public Class findClass(String name, List<Throwable> suppressed) {
            for (Element element : dexElements) {
                DexFile dex = element.dexFile;
    
                if (dex != null) {
    //核心 调用DexFile 元素的loadClassBinaryName方法去加载
                    Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                    if (clazz != null) {
                        return clazz;
                    }
                }
            }
            if (dexElementsSuppressedExceptions != null) {
                suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
            }
            return null;
        }
    }
    

    DexPathList 的findClass 方法会遍历当前数组,每一个对象Element拿到它封装的DexFile 对象调用 loadClassBinaryName去加载类。

    Element 源码
     /*package*/ static class Element {
           private final File dir;
            private final boolean isDirectory;
            private final File zip;
            private final DexFile dexFile;
            private ZipFile zipFile;
            private boolean initialized;
    
    DexFile 源码
    public final class DexFile {
    
      //方法1
        public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
            return defineClass(name, loader, mCookie, suppressed);
        }
    //方法2
      private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                         List<Throwable> suppressed) {
            Class result = null;
            try {
    //核心,最后调用native去进行加载
                result = defineClassNative(name, loader, cookie);
            } catch (NoClassDefFoundError e) {
                if (suppressed != null) {
                    suppressed.add(e);
                }
            } catch (ClassNotFoundException e) {
                if (suppressed != null) {
                    suppressed.add(e);
                }
            }
            return result;
        }
    
    }
    

    知识点总结

    1,双亲委派机制原理
    2,java 和Android classLoader的不同
    3,Android 加载类的流程
    BaseDexClassLoader 持有一个pathList 对象,pathList对象内部持有一个Element对象数组,每个Element对象封装了一个DexFile对象
    DexFile对象调用loadClassBinaryName 方法,最后调用native去进行加载。
    为什么要介绍BaseDexClassLoader ,因为Android 加载dex文件的两个classloader PathClassLoader DexClassLoader都是他的子类,而且没有什么拓展只是控制了传入参数的不同。

    PathClassLoader
    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);
        }
    }
    

    所有的构造函数,调用super的时候第二个参数都传入了null,这个参数是dex解压目录,所以我们不能操作PathClassLoader 加载的目录,这是Android 默认加载的类。

    DexClassLoader
    public class DexClassLoader extends BaseDexClassLoader {
        /**
         * Creates a {@code DexClassLoader} that finds interpreted and native
         * code.  Interpreted classes are found in a set of DEX files contained
         * in Jar or APK files.
         *
         * <p>The path lists are separated using the character specified by the
         * {@code path.separator} system property, which defaults to {@code :}.
         *
         * @param dexPath the list of jar/apk files containing classes and
         *     resources, delimited by {@code File.pathSeparator}, which
         *     defaults to {@code ":"} on Android
         * @param optimizedDirectory directory where optimized dex files
         *     should be written; must not be {@code null}
         * @param libraryPath the list of directories containing native
         *     libraries, delimited by {@code File.pathSeparator}; may be
         *     {@code null}
         * @param parent the parent class loader
         */
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }
    

    构造函数,第二个参数我们可以自己定义,所以我们可以操作DexClassLoader 加载的目录,这就是其中一种热修复的方案核心所在。

    最后附上源码地址,Android源码

    \color{#ff00ff}{哲学 。}

    \color{#ff00ff}{许多赛跑的失败,都是失败在最后的几步。跑应跑的路已经不容易,“跑到尽头”当然更困难 —— 苏格拉底。}

    相关文章

      网友评论

          本文标题:Android ClassLoader

          本文链接:https://www.haomeiwen.com/subject/wrmzbktx.html