插件化讲解到实战1--类加载的解析和双亲委派机制

插件化讲解到实战1–类加载的解析和双亲委派机制
插件化讲解到实战2–调用插件中的类
插件化讲解到实战3–Hook的源码分析和启动插件的Activity
插件化讲解到实战4–启动Activity适配9.0,10.0等版本

一、类加载机制

一个类被加载到虚拟机内存中需要经历几个过程:加载、连接、初始化。其中连接分为三个步骤:验证、准备、解析。

可以参考下面文章。本篇主要分析第一步类加载的过程和双亲委派机制。
https://www.cnblogs.com/chafanbusi/p/10639757.html

其中加载过程见下:在这里插入图片描述

二、类加载的解析和双亲委派机制

在说类加载器和双亲委派模型之前,我们先来梳理下Class类文件的加载过程,JAVA虚拟机为了保证 实现语言的无关性,是将虚拟机只与“Class 文件”字节码 这种特定形式的二进制文件格式 相关联,而不是与实现语言绑定。

类加载阶段,虚拟机主要完成三件事:
1、通过一个类的全限定名来获取定义此类的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构。
3、在Java堆中生成一个代表这个类的Class对象,作为方法区域数据的访问入口。

拓:上面第三步时候的类的class对象,反射时候会用到,反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法和属性。

通过如下代码打印,

private void printClassLoader() {
    ClassLoader classLoader = getClassLoader();
    while (classLoader != null) {
        Log.e("leo", "printClassLoader: " + classLoader);
        classLoader = classLoader.getParent();
    }
    Log.e("leo", "Activity:printClassLoader: " + Activity.class.getClassLoader());// pa1  boot2
    Log.e("leo", "Activity:printClassLoader: " + AppCompatActivity.class.getClassLoader());// pa3 boot4
}

可以看出,MainActivty的类加载器是PathClassLoader,PathClassLoader的parent是BootClassLoader,父类是BaseDexClassLoader。

Activity的类加载器是BootClassLoader,AppCompatActivity的类加载器是PathClassLoader。应用的类加载器是PathClassLoader,sdk的加载器是BootClassLoader,AppCompatActivity是依赖进去的所以是应用的。

自定义一个类加载器:
DexClassLoader dexClassLoader=new DexClassLoader(dexPath,context.getCacheDir().getAbsolutePath,null,context.getClassLoader());
Class<?>class=dexClassLoader.loadClass(“com.enjoy.text.Test”);

关于PathClassLoader和DexClassLoader,我也写了一篇:

https://blog.csdn.net/u013750244/article/details/107745288

双亲委派机制

通过源码,梳理一下loadClass的过程,也就是双亲委派机制:
简要流程:
三个点:
1、首先查找这个类是否已经加载过了,加载过了就直接返回class对象
2、如果没加载过,就让parent加载
3、判断parent是否加载成功,没成功就自己加载
优点:
1、避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2、安全性考虑,防止核心API库被随意篡改。

拓:
加载一个自定义的类,能否替换系统的String类?
不可以,String类的加载会优先加载BootClassLoader里的,也体现了双亲委派的安全性。

先看一下,DexClassLoader的源码发现里面没有调用loadClass(上面的文章有详细介绍),再看DexClassLoader的父类BaseDexClassLoader的源码,通过Android社区看源码,http://androidos.net.cn/sourcecode ,发现BaseDexClassLoader也没有loadClass,那么再找BaseDexClassLoader的父类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;
    }

1、首先,通过findLoadedClass去拿到class对象,也就是一开始去查找这个类是否加载过,如果加载过,直接返回这个类的class对象;如果class对象是空的话,看一下parent是否为空,我们传入的parent不是空的,一般传入PathClassLoader,当然传其他的也可以。我们这里设定传入的是pathClassLoader,那么在c=null之后,会判断parent是否为空。
2、parent不为空的话,执行 c = parent.loadClass(name, false); ,那么就相当于调用PathClassLoader的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;
    }

其实一模一样,因为最终都是调用的ClassLoader中的loadClass方法,那么这次又调用parent.loadClass(name, false),这次PathClassLoader的parent是BootClassLoader,BootClassLoader的源码在ClassLoader里面,loadClass源码如下:

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

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

        return clazz;
    }

3、看源码发现,BootClassLoader的loadClass也是首先查找是否加载过这个类,如果类为空,BootClassLoader不会再去找他的parent,而是结束这个递归,直接 clazz = findClass(className),也就是加载这个类。

4、从BootClassLoader,return到PathClassLoader,可以看到之前贴出PathClassLoader在loadClass的源码中有下面代码

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

也就是BootClassLoader如果没加载成功,这里再加载。然后在PathClassLoader的return返回值给DexClassLoader时,DexClassLoader也会判断PathClassLoader是否加载类成功,不成功就加载。

拓:
为什么DexClassLoader的parent不传入BaseDexClassLoader?
跟加载流程有关,我们传入parent的目的是为了优化,让它递归查找,从而不重复加载;而系统根本就没有用到BaseDexClassLoader去加载过类,所以parent传BaseDexClassLoader和传null差不多。

类查找,findClass分析:

通过查找源码,DexClassLoader没有重写findClass方法,而是使用的父类BaseDexClassLoader中的,源码如下:

 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就是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;
    }

可以看到,for循环,Element类的findclass,我们找到Element(DexPathList的内部类)的findclass方法:

 public Class<?> findClass(String name, ClassLoader definingContext,
            List<Throwable> suppressed) {
        return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                : null;
    }

可以看到,为空就直接返回空,不为空就 dexFile.loadClassBinaryName加载二进制,dexFile其实就是dex文件,用for循环是因为dex文件可能不止一个。

findclass小结:
1、DexClassLoader没有重写findClass方法,而是使用的父类BaseDexClassLoader中的。
2、跟踪进去发现,最终是通过Element类中的方法返回的,而每一个Element就对应一个dex文件,因为dex文件可能有多个,所以使用的Element[]表示的。
3、BaseDexClassLoader.findclass–>DexPathList.findclass–>Element.findClass

参考文章:

https://juejin.cn/post/6935358323139018766#heading-0

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值