android中的类加载器

android的类加载机制跟Java类似,也是采用“双亲委托”机制。即用户在用自己定义的类加载器加载类时,会首先尝试调用该加载器的父加载器来进行加载,加载不成功再返回给子加载器来加载。同样,该父类加载器在加载时也会调用其父类加载器进行加载,也就是原加载器的爷爷加载器进行加载,不成功再往下返回。一层层往上调用,直到到达“引导加载器”,这个加载器是Dalvik实现的一部分,是用C而非用Java实现的,因为类加载器也是类,使用前也要进行加载,这就要求有一个加载器来实现类加载器的加载。这个流程可以从ClassLoader类的loadClass()方法看出来:

protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            try {
                clazz = parent.loadClass(className, false);
            } catch (ClassNotFoundException e) {
                // Don't want to see this.
            }

            if (clazz == null) {
                clazz = findClass(className);
            }
        }

        return clazz;
}

如上述代码所示,在调用loadClass加载类时,会首先判断该类是否已经被加载,如果该类已经被加载则返回此类,否则会调用父类加载器来尝试加载。如果父类加载器无法加载,则会返回给子加载器来加载(findClass)。为什么会出现加载器无法加载的情况呢,是因为这些加载器类(引导加载器、系统加载器等)都有自己的“命名空间”,加载器在进行类加载时会从固定目录中尝试类加载,如果在这些目录中无法找到想加载的类加载就会失败。下面通过一个动态加载类的例子来捋一下流程:

String path = Environment.getExternalStorageDirectory() + "/";
File file = getDir("dex", 0) ;
String filename = "TestB.apk";
classLoader = new DexClassLoader(path + filename, file.getAbsolutePath(),null, ClassLoader.getSystemClassLoader());
Application app = classLoader.loadClass(strClassName).newInstance();

上述classLoader是DexClassLoader对象,loadClass返回一个被加载的类的Class对象,我们来分析一下loadClass到底是如何加载一个类的。DexClassLoader继承自BaseClassLoader,在DexClassLoader中并没有loadClass的实现,因为DexClassLoader继承自BaseClassLoader,BaseClassLoader又继承自ClassLoader,所以在加载时是调用了ClassLoader的loadClass。从上面代码中可以知道,loadClass其实是调用findClass来实现类的加载。因为被加载的类不在父类加载器的“命名空间”内,所以最后还是要调用DexClassLoader的findClass来实现类的加载。下面我们就来看一下findClass是如何实现类加载的。

protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class c = pathList.findClass(name);
        if (c == null) {
            throw new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        }
        return c;
}

其中pathList为DexPathList对象,即调用DexPathList的findclass,所以我们需要继续跳转到DexPathList中来看findClass是如何实现的。(跟踪代码确实很麻烦)

public Class findClass(String name) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext);
                if (clazz != null) {
                    return clazz;
                }
            }
        }

        return null;
}

其中,dex是DexFile对象,在这里我们来看一下DexFile类长啥样,DexFile也是在类加载中比较底层的起重要作用的类。

public final class DexFile {
    private int mCookie;
    private final String mFileName;
    private final CloseGuard guard = CloseGuard.get();
    public DexFile(String fileName) throws IOException {
        mCookie = openDexFile(fileName, null, 0);
        mFileName = fileName;
        guard.open("close");
    }
}

在DexFile中有几个native方法,比较重要的两个是openDexFile、defineClass。openDexFile返回值类型为int(在DexFile的构造中将此返回值传给mCookie),源码中是这样注释的:A pointer to our internal data structure,即dex文件被加载之后的内存地址。而private static Class defineClass(String name, ClassLoader loader, int cookie)的注释是:Load a class from a DEX file,参数cookie就是openDexFile的返回值,而name是要加载的类。总的来说,可以这么理解:先用openDexFile将dex文件读到一个特殊的数据结构中(支持从硬盘或者内存中读取),然后用defineClass实现某一个类的加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值