一步步手动实现热修复(二)-类的加载机制简要介绍

简介: *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 本节课程主要分为3块:1.一步步手动实现热修复(一)-dex文件的生成与加载2.一步步手动实现热修复(二)-类的加载机制简要介绍3.一步步手动实现热修复(三)-Class文件的替换本节示例所用到的任何资源都已开源,项目中包含工程中所用到代码、示例图片、说明文档。

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

本节课程主要分为3块:

本节示例所用到的任何资源都已开源,项目中包含工程中所用到代码、示例图片、说明文档。项目地址为:
https://code.csdn.net/u011064099/sahadevhotfix/tree/master


本节内容是为了给下节内容做知识铺垫,所以如果要需要了解热修复技术,本节内容的知识点必不可少。

一个类在被加载到内存之前要经过加载、验证、准备等过程。经过这些过程之后,虚拟机才会从方法区将代表类的运行时数据结构转换为内存中的Class。

我们这节内容的重点在于一个类是如何被加载的,所以我们从类的加载入口开始。

类的加载是由虚拟机触发的,类的加载入口位于ClassLoader的loadClassInternal()方法:

    // This method is invoked by the virtual machine to load a class.
    private Class<?> loadClassInternal(String name)
        throws ClassNotFoundException
    {
        // For backward compatibility, explicitly lock on 'this' when
        // the current class loader is not parallel capable.
        if (parallelLockMap == null) {
            synchronized (this) {
                 return loadClass(name);
            }
        } else {
            return loadClass(name);
        }
    }

这段方法还有段注释说明:这个方法由虚拟机调用用来加载一个类。我们看到这个类的内部最后调用了loadClass()方法。那我们进入loadClass()方法看看:

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

loadClass()方法方法内部调用了loadClass()的重载方法:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                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.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

loadClass()方法大概做了以下工作:

  • 首先查找该类是否已经被加载.
  • 如果该ClassLoader有父加载器,那么调用父加载器的loadClass()方法.
  • 如果没有父加载器,则调用findBootstrapClassOrNull()方法进行加载,该方法会使用引导类加载器进行加载。普通类是不会被该加载器加载到的,所以这里一般返回null.
  • 如果前面的步骤都没找到,那调用自身的findClass()方法进行查找。

好,ClassLoader的findClass()方法是个空方法,所以这个过程一般是由子加载器实现的。Java的加载器这么设计是有一定的渊源的,感兴趣的读者可以自行查找书籍了解。

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

在Android中,ClassLoader的直接子类是BaseDexClassLoader,我们看一下BaseDexClassLoader的findClass()实现:

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = pathList.findClass(name);

        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }

        return clazz;
    }

Tips: 有需要虚拟机以及类加载器全套代码的,请使用以下命令克隆:
git clone https://android.googlesource.com/platform/dalvik-snapshot
相关代码位于项目的ics-mr1分支上。

看到这里我们可以知道,Android中类的查找是通过这个pathList进行查找的,而pathList又是个什么鬼呢?

在BaseDexClassLoader中声明了以下变量:

    /** structured lists of path elements */
    private final DexPathList pathList;

所以我们可以看看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;
    }

这里通过遍历dexElements中的Element对象进行查找,最终走的是DexFile的loadClassBinaryName()方法:

    public Class loadClassBinaryName(String name, ClassLoader loader) {
        return defineClass(name, loader, mCookie);
    }

    private native static Class defineClass(String name, ClassLoader loader, int cookie);

到此为止,我们就将类的加载过程梳理完了。

下一节课我们将会介绍如何实现类的替换。

目录
相关文章
|
PHP 开发者
类的加载(概念和步骤)|学习笔记
快速学习类的加载(概念和步骤)
78 0
|
存储 Java 数据挖掘
AGP 实现方法插桩探究
AGP 实现方法插桩探究
166 0
AGP 实现方法插桩探究
|
设计模式 存储 安全
重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」
实现不了,有时候是功能复杂度较高难以实现,有时候是工期较短实现不完。而编码的行为又是一个不太好量化的过程,同样一个功能每个人的实现方式不一样,遇到开发问题解决问题的速度也不一样。除此之外还很不好给产品解释具体为什么要这个工期时间,这就像盖楼的图纸最终要多少水泥砂浆一样。那么这时研发会尽可能的去通过一些经验,制定流程规范、设计、开发、评审等,确定一个可以完成的时间范围,又避免风险的时间点后。再被压缩,往往会出一些矛盾点,能压缩要解释为什么之前要那么多时间,不能压缩又有各方不断施加的压力。因此有时候不一定是借口,是要考虑如何让整个团队健康的发展。
182 0
重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」
|
算法 Java 测试技术
官方文档:Android应用程序运行的性能设计
  Android应用程序运行的移动设备受限于其运算能力,存储空间,及电池续航。由此,它必须是高效的。电池续航可能是一个促使你优化程序的原因,即使他看起来已经运行的足够快了。由于续航对用户的重要性,当电量耗损陡增时,意味这用户迟早会发现是由于你的程序。
838 0
|
数据采集 存储 NoSQL
RedisSpider的调度队列实现过程及其源码
对于非分布式的scrapy爬虫而言,不能共享爬虫队列,不能实现分布式。RedisSpider是依赖Redis存储中介,来实现多台主机多爬虫之间的通信,RedisSpider是去重是内部的queue.py文件实现的,内部实现了队列、堆栈、优先级队列,在调度的统一协调下最终实现分布式协同工作。
1793 0
http://www.vxiaotou.com