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

一、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文件继续查找。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容