美文网首页
Android中的ClassLoader

Android中的ClassLoader

作者: Android_Gleam | 来源:发表于2020-09-01 01:07 被阅读0次
111112222.png

1.BootClassLoader

用于加载Android Framework层class文件。例如Activity、Application等

2.PathClassLoader

用于加载程序中我们自己写的类。例如MyApplication、MainActivity等

3.DexClassLoader

额外提供的动态类加载器。

与PathClassLoader只是构造方法不同(DexClassLoader多了一个optimizedDirectory参数,优化的dex存放的路径)。
二者都可以加载指定的dex,以及jar、zip、apk中的classes.dex

注:我们可以通过A.class.getClassLoader()来查看A类是由哪个classLoader进行加载的

4.类加载

我们首先看PathClassLoader中是否有loadClass方法,然后通过源码查看并没有,我们继续向父类查找,最后在ClassLoader中找到了loadClass方法。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded 找缓存
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

findLoadedClass寻找加载过的class,也就是在缓存中查找。如果找到了,则直接返回。
如果没有找到则通过parent去查找,这个parent又是什么呢?通过源码发现,这个parent也是一个ClassLoader,我们这里将其称为父加载器,这也就是所谓的双亲委托机制

什么是双亲委托机制?

如果没有加载过则交给父加载器进行加载,父类又会调用自己的父加载器加载,如果一直到最顶层的父加载器都没有加载到,那么再由自己去加载。

为什么需要这个机制?

1、避免重复加载,当父加载器已经加载了该类的时候,子ClassLoader就没必要再加载一次。

当应用启动的时候会先在ActivityThread的main方法中启动Application,实际开发中,我们都会实现自己的Application,这里我们假如叫MyApplication,上面我们说过,加载我们自己的类是需要PathClassLoader来加载,所以需要先创建PathClassLoader,我们先看下PathClassLoader的构造方法。

public class PathClassLoader extends BaseDexClassLoader {
  
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

两个构造方法中都有一个parent参数,这个parent参数就是BootClassLoader。

因为我们自己写的MyApplication继承系统的Application,所以当加载MyApplication时需要先加载Application,也就是需要先load系统Application。

然后回头看上面的代码,当parent不为null时,调用parent.loadClass,而这个parent就是BootClassLoader,然后会在BootClassLoader中的缓存找,然后返回,避免了重复加载。这还有一个好处就是,如果没有这个parent,也就是BootClassLoader,因为PathClassLoader是加载不了Application的,同事也保证了对应的类由对应的ClassLoader来加载

2、安全性考虑,防止核心API库被随意加载。

如果我们去掉上面的parent加载过程,代码改为下面这样

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded 找缓存
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                    c = findClass(name);
            }
            return c;
    }

这时我们自己创建一个与系统Application同包同名的Application,这时就会加载我们自己写的Application,这就会造成安全问题,同时代码运行过程中,如果调用了系统Application的方法也会造成方法找不到的问题。

5.查找类

上面我们说了类的加载过程,下面我们看看类的查找过程,也就是当我们程序中的类第一次加载时是怎么被发现的,入口也就是上面的findClass()方法。
PathClassLoader中是没有findClass方法的,我们继续向其父查找,最终在BaseClassLoadClass中找到了该方法。下面我们看下源码

public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
        // TODO We should support giving this a library search path maybe.
        super(parent);
        this.pathList = new DexPathList(this, dexFiles);
    }

    @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的findClass方法,pathList是DexPathList类型并且是在构造方法中创建出来的,所以我们进到DexPathList中查看findClass方法。

public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

我们发现这个方法中对dexElements进行了遍历,dexElements是一个数组,其中就包含了我们应用程序中所有的dex文件,每一个dex对应一个Element,我们可以看做当查找程序中类的时候,遍历我们所有的dex去进行查找。找到后进行加载,没有找到则抛出异常。

结尾

写的可能不是很准确或者全面,大家发现错误可以指出,感激不尽,技术小菜鸟一枚,勿喷。

Android中的dex插桩热修复(类文件)

相关文章

网友评论

      本文标题:Android中的ClassLoader

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