美文网首页android实用技术Android技术知识Android进阶之路
MultiDex与热修复实现原理(一)ClassLoader原理

MultiDex与热修复实现原理(一)ClassLoader原理

作者: 枫羽望空 | 来源:发表于2017-01-12 13:05 被阅读138次

    一、Android的ClassLoader体系

    这里写图片描述

    DexClassLoader的构造函数

    public class DexClassLoader extends BaseDexClassLoader {
        // dexPath:是加载apk/dex/jar的路径
        // optimizedDirectory:是dex的输出路径(因为加载apk/jar的时候会解压除dex文件,这个路径就是保存dex文件的)
        // libraryPath:是加载的时候需要用到的lib库,这个一般不用
        // parent:给DexClassLoader指定父加载器
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    }
    

    可以看出,它调用的是父类的构造函数,所以直接来看BaseDexClassLoader的构造函数。

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

    可以看到,它创建了一个DexPathList实例,下面来看看构造函数。

    private final Element[] dexElements;
     
    // definingContext对应的就是当前classLoader
    // dexPath对应的就是上面传进来的apk/dex/jar的路径
    // libraryPath就是上面传进来的加载的时候需要用到的lib库的目录,这个一般不用
    // optimizedDirectory就是上面传进来的dex的输出路径
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        ArrayList<ioexception> suppressedExceptions = new ArrayList<ioexception>();
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions);
    }
    

    可以看到它调用的是makeDexElements方法,这个方法就是得到一个装有dex文件的数组Element[],每个Element对象里面包含一个DexFile对象成员,它对应的就是dex文件。

    static class Element {
        private final File file; 
        private final boolean isDirectory; 
        private final File zip;
        private final DexFile dexFile;
        ......
    }
    

    具体了解一下makeDexElements方法。

    // files是一个ArrayList<file>列表,它对应的就是apk/dex/jar文件,因为我们可以指定多个文件。
    // optimizedDirectory是前面传入dex的输出路径
    // suppressedExceptions为一个异常列表
    private static Element[] makeDexElements(ArrayList<file> files, File optimizedDirectory,
                                             ArrayList<ioexception> suppressedExceptions) {
        ArrayList<element> elements = new ArrayList<element>();
        /*
         * Open all files and load the (direct or contained) dex files
         * up front.
         */
        for (File file : files) {
            File zip = null;
            DexFile dex = null;
            String name = file.getName();
     
            // 如果是一个dex文件
            if (name.endsWith(DEX_SUFFIX)) {
                // Raw dex file (not inside a zip/jar).
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException ex) {
                    System.logE("Unable to load dex file: " + file, ex);
                }
            // 如果是一个apk或者jar或者zip文件
            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                    || name.endsWith(ZIP_SUFFIX)) {
                zip = file;
     
                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    /*
                     * IOException might get thrown "legitimately" by the DexFile constructor if the
                     * zip file turns out to be resource-only (that is, no classes.dex file in it).
                     * Let dex == null and hang on to the exception to add to the tea-leaves for
                     * when findClass returns null.
                     */
                    suppressedExceptions.add(suppressed);
                }
            } else if (file.isDirectory()) {
                // We support directories for looking up resources.
                // This is only useful for running libcore tests.
                elements.add(new Element(file, true, null, null));
            } else {
                System.logW("Unknown file type for: " + file);
            }
     
            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, false, zip, dex));
            }
        }
     
        return elements.toArray(new Element[elements.size()]);
    }
    

    Element,它里面具体包含哪些元素,现在从上面代码我们就可以知道了。

    static class Element {
        private final File file;  // 它对应的就是需要加载的apk/dex/jar文件
        private final boolean isDirectory; // 第一个参数file是否为一个目录,一般为false,因为我们传入的是要加载的文件
        private final File zip;  // 如果加载的是一个apk或者jar或者zip文件,该对象对应的就是该apk或者jar或者zip文件
        private final DexFile dexFile; // 它是得到的dex文件
        ......
    }
    

    主要调用loadDexFile方法。

    // file为需要加载的apk/dex/jar文件
    // optimizedDirectorydex的输出路径
    private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }
    

    如果我们没有指定dex输出目录的话,就直接创建一个DexFile对象,如果我们指定了dex输出目录,我们就需要构造dex输出路径。

    optimizedPathFor方法用来得到输出文件dex路径,就是optimizedDirectory/filename.dex,optimizedDirectory是前面指定的输出目录,filename就是加载的文件名,后缀为.dex,最终构造得到一个输出dex文件路径.

    下面我们重点看看DexFile.loadDex方法。

    static public DexFile loadDex(String sourcePathName, String outputPathName,
        int flags) throws IOException {
        return new DexFile(sourcePathName, outputPathName, flags);
    }
    

    总结一下

    1. 在DexClassLoader我们指定了加载的apk/dex/jar文件和dex输出路径optimizedDirectory,它最终会被解析得到DexFile文件。
    2. 将DexFile文件对象放在Element对象里面,它对应的就是Element对象的dexFile成员变量。
    3. 将这个Element对象放在一个Element[]数组中,然后将这个数组返回给DexPathList的dexElements成员变量。
    4. DexPathList是BaseDexClassLoader的一个成员变量。

    最终得到一个装有dex文件的数组Element[],每个Element对象里面包含一个DexFile对象成员,它对应的就是dex文件。

    这里写图片描述

    调用dexClassLoader的loadClass,得到加载的dex里面的指定的Class.

    clazz = dexClassLoader.loadClass("com.example.apkplugin.PluginTest");
    

    分析一下loadClass方法。因为DexClassLoader和BaseDexClassLoader都没有实现loadClass方法,所以最终调用的是ClassLoader的loadClass方法。

    public Class<!--?--> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, false);
    }
     
    protected Class<!--?--> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        Class<!--?--> clazz = findLoadedClass(className);
     
        if (clazz == null) {
            ClassNotFoundException suppressed = null;
            try {
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                suppressed = e;
            }
     
            if (clazz == null) {
                try {
                    clazz = findClass(className);
                } catch (ClassNotFoundException e) {
                    e.addSuppressed(suppressed);
                    throw e;
                }
            }
        }
     
        return clazz;
    }
    

    它调用的是findClass方法,由于DexClassLoader没有实现这个方法,所以我们看BaseDexClassLoader的findClass

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

    pathList就是前面创建的DexPathList对象,从上面我们知道,我们加载的dex文件都存放在它的exElements成员变量上面,dexElements就是Element[]数组,所以可以看到BaseDexClassLoader的findClass方法调用的是pathList的findClass方法。

    可以看到BaseDexClassLoader的findClass方法调用的是DexPathList的findClass方法。

    public Class findClass(String name, List<throwable> 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;
    }
    

    可以看到它就是遍历dexElements数组,从每个Element对象中拿到DexFile类型的dex文件,然后就是从dex去加载所需要的class文件,直到找到为止。

    总结:一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。

    相关文章

      网友评论

        本文标题:MultiDex与热修复实现原理(一)ClassLoader原理

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