• 反射与类加载之ClassLoader与类加载器(二)


    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
    本篇文章将从以下几个内容来阐述反射与类加载:

    • [动态代理模式]
    • [Android 中的Dalvik和ART]
    • [ PathClassLoader 和 DexClassLoader]
    • [双亲委派机制]

    一、动态代理模式

    动态代理

    所谓静态和动态指的是,接口方法的调用方式.静态代理,是真实对象显式地方法调用,而动态代理则是通过反射的方式调用真实对象的方法.

    public class DynamicProxy implements InvocationHandler {
        // 这个就是我们要代理的真实对象
        private Object subject;
     
        // 构造方法,给我们要代理的真实对象赋初值
        public DynamicProxy(Object subject) {
            this.subject = subject;
        }
     
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(subject, args);
        }
    }
     
    public class ProxyClient {
        public static void main(String[] args) {
            Subject realSubject = new RealSubject();
     
            InvocationHandler handler = new design.pattern.structure.proxy.demo.DynamicProxy(realSubject);
            Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
            subject.operate1();
            subject.operate2("hello proxy");
        }
    }
    

    三大角色:接口类,真实对象,InvocationHandler的实现类

    通过Java的Proxy.newProxyInstance的方法调用,给Subject生成了一个实例对象;接口方法的调用,会传递给handler中,handler通过invoke的执行,间接地调用了真实对象的方法.

    二、Android 中的Dalvik和ART

    2.1 什么是Dalvik?

    Dalvik是Google公司自己设计用于Android平台的虚拟机。
    Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。
    它可以支持已转换为** .dex格式**的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。
    Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
    很长时间以来,Dalvik虚拟机一直被用户指责为拖慢安卓系统运行速度不如IOS的根源。
    2014年6月25日,Android L 正式亮相于召开的谷歌I/O大会,Android L 改动幅度较大,谷歌将直接删除Dalvik,代替它的是传闻已久的ART。

    2.2 Dalvik和JVM有啥关系?

    主要区别:
    Dalvik是基于寄存器的,而JVM是基于栈的。
    Dalvik运行dex文件,而JVM运行java字节码
    自Android 2.2开始,Dalvik支持JIT(just-in-time,即时编译技术)。
    优化后的Dalvik较其他标准虚拟机存在一些不同特性: 
    1.占用更少空间 
    2.为简化翻译,常量池只使用32位索引  
    3.标准Java字节码实行8位堆栈指令,Dalvik使用16位指令集直接作用于局部变量。局部变量通常来自4位的“虚拟寄存器”区。这样减少了Dalvik的指令计数,提高了翻译速度。 
     当Android启动时,Dalvik VM 监视所有的程序(APK),并且创建依存关系树,为每个程序优化代码并存储在Dalvik缓存中。Dalvik第一次加载后会生成Cache文件,以提供下次快速加载,所以第一次会很慢。
     Dalvik解释器采用预先算好的Goto地址,每个指令对内存的访问都在64字节边界上对齐。这样可以节省一个指令后进行查表的时间。为了强化功能, Dalvik还提供了快速翻译器(Fast Interpreter)。

    一般来说,基于堆栈的机器必须使用指令才能从堆栈上的加载和操作数据,因此,相对基于寄存器的机器,它们需要更多的指令才能实现相同的性能。但是基于寄存器机器上的指令必须经过编码,因此,它们的指令往往更大。

    Dalvik虚拟机既不支持Java SE 也不支持Java ME类库(如:Java类,AWT和Swing都不支持)。 相反,它使用自己建立的类库(Apache Harmony Java的一个子集)。

    2.3 什么是ART?

    即Android Runtime
    ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。

    2.4 ART有什么优缺点呢?

    优点:
    1、系统性能的显著提升。
    2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。
    3、更长的电池续航能力。
    4、支持更低的硬件。
    缺点:
    1.机器码占用的存储空间更大,字节码变为机器码之后,可能会增加10%-20%(不过在应用包中,可执行的代码常常只是一部分。比如最新的 Google+ APK 是 28.3 MB,但是代码只有 6.9 MB。)
    2.应用的安装时间会变长。

    tips:现在智能手机大部分都可以让用户选择使用Dalvik还是ART模式。当然默认还是使用Dalvik模式。
    用法:设置-辅助功能-开发者选项(开发人员工具)-选择运行环境(不同的手机设置的步骤可能不一样)。

    三、ClassLoader 的构造函数

     
    19956127-82e2b33bc744d513.png
     

    3.1 BaseDexClassLoader 构造函数

    PathClassLoader 和 DexClassLoader 都是继承了 BaseDexClassLoader,这里先看一下。 BaseDexClassLoader 的构造函数。

    public class BaseDexClassLoader extends ClassLoader {
        private final DexPathList pathList;
    
        /**
         * Constructs an instance.
         *
         * @param dexPath the list of jar/apk files containing classes and
         * resources, delimited by {@code File.pathSeparator}, which
         * defaults to {@code ":"} on Android
         * @param optimizedDirectory directory where optimized dex files
         * should be written; may be {@code null}
         * @param libraryPath the list of directories containing native
         * libraries, delimited by {@code File.pathSeparator}; may be
         * {@code null}
         * @param parent the parent class loader
         */
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
        }
    }
    

    BaseDexClassLoader 构造函数有四个参数,含义如下:

    • dexPath: 需要加载的文件列表,文件可以是包含了 classes.dex 的 JAR/APK/ZIP,也可以直接使用 classes.dex 文件,多个文件用 “:” 分割
    • optimizedDirectory: 存放优化后的 dex,可以为空
    • libraryPath: 存放需要加载的 native 库的目录
    • parent: 父 ClassLoader

    通过构造函数我们大概可以了解到 BaseDexClassLoader 的运行方式,传入 dex 文件,然后进行优化,保存优化后的 dex 文件到 optimizedDirectory 目录。

    3.2 PathClassLoader 构造函数

    接着我们再看 PathClassLoader 的构造函数。

    /**
     * Provides a simple {@link ClassLoader} implementation that operates on a list
     * of files and directories in the local file system, but does not attempt to
     * load classes from the network. Android uses this class for its system class
     * loader and for its application class loader(s).
     */
    public class PathClassLoader extends BaseDexClassLoader {
        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, null, parent);
        }
    
        /**
         * Creates a {@code PathClassLoader} that operates on two given
         * lists of files and directories. The entries of the first list
         * should be one of the following:
         *
         * <ul>
         * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
         * well as arbitrary resources.
         * <li>Raw ".dex" files (not inside a zip file).
         * </ulyanzheng>
         *
         * The entries of the second list should be directories containing
         * native library files.
         *
         * @param dexPath the list of jar/apk files containing classes and
         * resources, delimited by {@code File.pathSeparator}, which
         * defaults to {@code ":"} on Android
         * @param libraryPath the list of directories containing native
         * libraries, delimited by {@code File.pathSeparator}; may be
         * {@code null}
         * @param parent the parent class loader
         */
        public PathClassLoader(String dexPath, String libraryPath,
                ClassLoader parent) {
            super(dexPath, null, libraryPath, parent);
        }
    }
    

    关于 PathClassLoader 有一点稍微注意一下,代码注释中对 PathClassLoader 的介绍是,用来操作文件系统上的一系列文件和目录 的 ClassLoader 实现。其中并没有提到只能加载安装后的 apk 文件。
    PathClassLoader 有两个构造函数,区别在于传给 BaseDexClassLoader 的 libraryPath 是否为空。最终调用 BaseDexClassLoader 构造函数时,传入的 optimizedDirectory 为空。

    3.3 DexClassLoader 构造函数

    再来看看 DexClassLoader 的构造函数。和 BaseDexClassLoader 构造函数的参数是一样的。

    public class DexClassLoader extends BaseDexClassLoader {
        /**
         * Creates a {@code DexClassLoader} that finds interpreted and native
         * code.  Interpreted classes are found in a set of DEX files contained
         * in Jar or APK files.
         *
         * <p>The path lists are separated using the character specified by the
         * {@code path.separator} system property, which defaults to {@code :}.
         *
         * @param dexPath the list of jar/apk files containing classes and
         *     resources, delimited by {@code File.pathSeparator}, which
         *     defaults to {@code ":"} on Android
         * @param optimizedDirectory directory where optimized dex files
         *     should be written; must not be {@code null}
         * @param librarySearchPath the list of directories containing native
         *     libraries, delimited by {@code File.pathSeparator}; may be
         *     {@code null}
         * @param parent the parent class loader
         */
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
        }
    }
    

    通过上面对构造函数的分析,我们可以明白,PathClassLoader 和 DexClassLoader 关键不同点,在 optimizedDirectory 参数上,PathClassLoader 传入的是 null,而 DexClassLoader 传入的是用户指定的目录。

    四、双亲委派机制

    4.1 模型说明

     
    944365-cdd4548d762686c2.png
    image

    4.2 工作流程讲解

    • 双亲委派模型的工作流程代码实现在java.lang.ClassLoader的loadClass()
    • 具体如下
    @Override 
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException { 
        Class<?> c = findLoadedClass(name); 
    
      // 检查需要加载的类是否已经被加载过
        if (c == null) { 
            try { 
                 // 若没有加载,则调用父加载器的loadClass()方法
                if (parent != null) { 
                    c = parent.loadClass(name, false); 
                }else{ 
                    // 若父类加载器为空,则默认使用启动类加载器作为父加载器
                    c=findBootstrapClassOrNull(name); 
                } 
            } catch (ClassNotFoundException e) { 
                // 若父类加载器加载失败会抛出ClassNotFoundException, 
                //说明父类加载器无法完成加载请求 
            } 
            if(c==null){ 
                // 在父类加载器无法加载时 
                // 再调用本身的findClass方法进行类加载 
                c=findClass(name); 
            } 
        } 
        if(resolve){ 
            resolveClass(c); 
        } 
        return c; 
    }
    
    

    步骤总结:若一个类加载器收到了类加载请求

    1. 把 该类加载请求 委派给 父类加载器去完成,而不会自己去加载该类

    每层的类加载器都是如此,因此所有的加载请求最终都应传送到顶层的启动类加载器中

    1. 只有当 父类加载器 反馈 自己无法完成该加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会自己去加载

    4.3 优点

    Java类随着它的类加载器一起具备了一种带优先级的层次关系

    1. 如:类 java.lang.Object(存放在rt.jar中)在加载过程中,无论哪一个类加载器要加载这个类,最终需委派给模型顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
    2. 若没有使用双亲委派模型(即由各个类加载器自行去加载)、用户编写了一个java.lang.Object的类(放在ClassPath中),那系统中将出现多个不同的Object类,Java体系中最基础的行为就无法保证

    在讲完系统的类加载器后,下面我将讲解如何根据需求自定义类加载器。
    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680

    推荐阅读:转自“腾讯高级工程师写给全国移动互联网工作者的一封公开信”

    参考:https://www.jianshu.com/p/8743d8062bb6
    https://blog.csdn.net/m_sdn/article/details/84856273
    https://www.jianshu.com/p/58f817d176b7
    https://segmentfault.com/a/1190000020254261

  • 相关阅读:
    java.lang.NoClassDefFoundError: org/jaxen/JaxenException解决方法
    解决Axis2在webservice中遇到特殊字符的无法传输的缺陷(<CDATA>数据类型)
    java使用POST发送soap报文请求webservice返回500错误解析
    HTTP直接请求webService
    Java占位符替换工具类
    sonarLint 插件配置sonarQube Server
    设计模式之十一:抽象工厂模式(Abstract Factory)
    oracle 序列重置
    windows快捷键之打开网络连接
    android中依据不同分辨率dp和px的相互转算
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11949024.html
Copyright © 2020-2023  润新知