浅谈Android Classloader动态加载分析

ClassLoader概念

我们知道,Java源文件(.java)经过编译器编译之后,会转换成Java字节码(.class),然而程序是如何加载这些字节码文件到内存中呢?这就用到了ClassLoader,即类加载器。ClassLoader类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。从而只有class文件被载入到了内存之后,才能被其程序所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。

ClassLoader的分类

Android中的常用几种类加载器类型继承关系划分可以用一组关系图来表示

BootClassLoder

通过查看ClassLoader源码 我们得知,Android中在默认父加载器传入的情况下,默认父加载器为PathClassLoder,而PathClassLoader的父加载器正是BootClassLoader。BootClassLoader是ClassLoader的内部类,是包内可见,我们无法直接使用,也无法直接动态加载。

 /**
   * Encapsulates the set of parallel capable loader types.
   */
  private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");
    String librarySearchPath = System.getProperty("java.library.path", "");

    ...省略部分代码

    //默认父构造器为PathClassLoder
    return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
  }

URLClassLoader

URLClassLoader继承自SecureClassLoader,SecureClassLoader继承自ClassLoader。URLClassLoader的特点就是只能加载jar文件,但是dalvik不能直接识别jar。所以在Android中无法直接使用这个类加载器。

BaseDexClassLoader

BaseDexClassLoader直接继承自ClassLoader,下面是其构造函数

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
    ....
  }

下面解析下这四个参数

  1. dexPath:指目标类所在的apk、dex或者jar文件的路径(包括SD卡),然后加载器将从该路径中寻找到指定的目标类。当然了,这个路径可以是多个路径,这样可以寻找到多个目标类,多路径之间需要使用特定的分隔符,分隔符可以使用System.getProperty("path.separtor")获取。
  2. optimizedDirectory:由于dex文件被包含在apk或者jar文件中,需要先解压出来,而这个参数 就代表了被解压的路径。而且apk文件其实也是一个压缩包,解压的过程其实也是一个ODEX优化的过程,那么何为ODEX优化呢?其实就是把包里面的可执行程序提取出来变成ODEX文件,存放到optimizedDirectory目录下,因为提取出来的原因,应用第一次进行启动的时候,直接使用ODEX文件 启动速度自然是比解压再启动速度是要快的。为什么是应用第一次启动呢?因为dex版本只有第一次启动会解压执行程序到/data/dalvik-cache(针对PathClassLoader),或者optimizedDirectory文件目录下(针对DexClassLoader),之后就可以直接读取目录下的dex文件了。
  3. librarySearchPath:指的是目标类所使用的c、c++库存放的路径
  4. parent:是指该加载器的父加载器,一般为当前执行类的加载器。

PathClassLoader

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

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

通过源码我们可以知道,PathClassLoader继承于BaseDexClassLoader,并且构造器将optimizedDirectory置为null,也就是没有设置ODEX优化后的存储路径,前文有提到,如果没有设置optimizedDirectory目录,那么默认存储路径就是/data/dalvik-cache。因为这个原因,PathClassLoader被设定成只能加载Android系统类和已安装的android应用类。

DexClassLoader

public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
    super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
   ...
  }

DexClassLoader也是继承于BaseDexClassLoader,支持加载包含classes.dex文件的apk、jar,前文我们提到dalvik不支持直接加载jar文件,那么为什么到了DexClassLoader这里怎么就可以支持加载了呢?原因在于其父类BaseDexClassLoader对于“.jar”,“.apk”,".zip",".dex"后缀的文件都会进行对应的处理,最终提取成可执行的dex文件。然而URLClassLoader并未对此做类似的处理,因此我们一般会采用DexClassLoader做动态加载。

InMemoryDexClassLoader

 public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
    super(dexBuffers, parent);
  }

  public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
    this(new ByteBuffer[] { dexBuffer }, parent);
  }

InMemoryDexClassLoader继承于BaseDexClassLoader,是API26新增的类加载器。dexBuffers数组构造了一个DexPathList,可用于加载内存中的dex。

DelegateLastClassLoader

public DelegateLastClassLoader(String dexPath, ClassLoader parent) {
    super(dexPath, parent);
  }

DelegateLastClassLoader是API27新增的类加载器,继承自 PathClassLoader。DelegateLastClassLoader实行最后的查找策略。使用DelegateLastClassLoader来加载每个类和资源,使用的是以下顺序:

  1. 判断是否已经加载过该类
  2. 搜索此类的类加载器是否已经加载过该类
  3. 搜索与此类加载器相关联的dexPath文件列表,并委托给父加载器。

双亲委托机制

Android类加载器通过loadClass加载目标类,下面是加载的源码

 protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
  {
      // 首先检查当前目标类是否已经被加载过,有则直接返回
      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) {
          //如果仍未找到,则通过寻找子ClassLoader的目标类(如果子ClassLoader重写了findClass)
          c = findClass(name);
        }
      }
      return c;
  }

由上述源码,我们可以总结:

  1. 当前类加载器首先检查目标类是否已经被加载过,有则直接返回
  2. 当前类加载器会先委托父类加载器加载目标类,如果未设置父加载器,则检查辅助加载器是否支持查询加载目标类
  3. 只有上述加载器找不到目标类的时候,才会调用当前类加载器(Child) 查询路径寻找目标类。

以上这么做的好处是:一方面防止目标类的重复加载,另外一方面 主要考虑安全因素,防止有人重写原生类,比如说java.lang.String这样的数据类型,替换原生的String类,加载到JVM中,造成严重的安全问题。

双亲委托机制 在Android热修复领域中也有着广泛的应用。每个ClassLoader可以有多个dex文件,每个dex文件是一个Element,多个dex文件组成一个dexElements,类加载器寻找类的时候,会遍历dexElements中的dex文件,再通过dex文件遍历目标类。由于双亲委托机制的存在,寻找到目标类后就直接返回,不再寻找其他dex文件下该目标类,热修复的原理就是hook住ClassLoader,使其先加载修复后的目标类,而存在的BUG的目标类不会被加载。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解Android类加载ClassLoader

    基本知识 Java的类加载设计了一套双亲代理的模式,使得用户没法替换系统的核心类,从而让应用更安全.所谓双亲代理就是指,当加载类的时候首先去Bootstrap中加载类,如果没有则去Extension中加载,如果再没有才去AppClassLoader中去加载.从而实现安全和稳定. Java ClassLoader BootstrapClassLoader 引导类加载器 ,用来加载Java的核心库.通过底层代码来实现的,基本上只要parent为null,那就表示引导类加载器. 比如:charsets

  • 关于Android中自定义ClassLoader耗时问题的追查

    前言 Android中类加载器有BootClassLoader,URLClassLoader, PathClassLoader,DexClassLoader,BaseDexClassLoader,等都最终继承自java.lang.ClassLoader 最近在优化西瓜视频客户端冷启动速度时,发现在关闭插件 ClassLoader 注入的情况下,启动速度提升了300ms左右,但是西瓜在启动阶段并没有使用到插件,那么这么大的耗时是怎么来的呢?下面话不多说了,来一起看看详细的介绍吧. 猜原因 首先看下

  • Android开发中类加载器DexClassLoader的简单使用讲解

    简介 "类装载器"(ClassLoader),顾名思义,就是用来动态装载class文件的.标准的Java SDK中有个ClassLoader类,借助此类可以装载需要的class文件,前提是ClassLoader类初始化必须制定class文件的路径. import关键字引用的类文件和ClassLoader动态加载类的区别: import引用类的两个特点: 1.必须存在于本地,当程序运行该类时,内部类装载器会自动装载该类. 2.编译时必须在现场,否则编译过程会因找不到引用文件而不能正常编译

  • 浅谈Android Classloader动态加载分析

    ClassLoader概念 我们知道,Java源文件(.java)经过编译器编译之后,会转换成Java字节码(.class),然而程序是如何加载这些字节码文件到内存中呢?这就用到了ClassLoader,即类加载器.ClassLoader类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例.从而只有class文件被载入到了内存之后,才能被其程序所引用.所以ClassLoader就是用来动态加载class文件到内存当中用的. ClassLoader的分类 An

  • 浅谈vue首屏加载优化

    本文介绍了浅谈vue首屏加载优化,分享给大家,具体如下: 库使用情况 vue vue-router axios muse-ui material-icons vue-baidu-map 未优化前 首先我们在正常情况下build 优化 1. 按需加载 当前流行的UI框架如iview,muse-ui,Element UI都支持按需加载,只需稍微改动一下代码. 修改前: import MuseUI from 'muse-ui' import 'muse-ui/dist/muse-ui.css' imp

  • 浅谈JVM之类的加载链接和初始化

    加载 JVM可以分为三大部分,五大空间和三大引擎,要讲起来也不是特别复杂,先看下面的总体的JVM架构图. 从上面的图中,我们可以看到JVM中有三大部分,分别是类加载系统,运行时数据区域和Execution Engine. 加载就是根据特定名称查找类或者接口的二进制表示,并根据此二进制表示来创建类和接口的过程. 运行时常量池 我们知道JVM中有一个方法区的区域,在JDK8中,方法区的实现叫做元空间.这个元空间是存放在本地内存中的. 方法区中存放着每个class对应的运行时常量池. 当类或者接口创建

  • 浅谈SpringBoot资源初始化加载的几种方式

    目录 一.问题 二.资源初始化 一.问题 在平时的业务模块开发过程中,难免会需要做一些全局的任务.缓存.线程等等的初始化工作,那么如何解决这个问题呢?方法有多种,但具体又要怎么选择呢? 二.资源初始化 1.既然要做资源的初始化,那么就需要了解一下springboot启动过程(这里大体说下启动过程,详细:https://www.jb51.net/article/133648.htm) 按照前面的分析,Spring-boot容器启动流程总体可划分为2部分: 执行注解:扫描指定范围下的bean.载入自

  • Android ViewPager动态加载问题

    今天做项目时,纠结了很久,动态添加view,刚开始按照其他的adapter处理,但是不会刷新view,来回翻几页,还会view覆盖,最后手动调用adapter的destroyItem和instantiateItem方法,还是不行,最后重写notifyDataSetChanged中removeAllViews和instantiateItem,有点效果,可是还是不理想.最后查询资料要重写PagerAdapter的方法 如下: public int getItemPosition(Object obj

  • Android编程动态加载布局实例详解【附demo源码】

    本文实例讲述了Android编程动态加载布局的方法.分享给大家供大家参考,具体如下: 由于前段时间项目需要,需要在一个页面上加载根据不同的按钮加载不同的布局页面,当时想到用 tabhot .不过美工提供的界面图完全用不上tabhot ,所以想到了动态加载的方法来解决这一需求.在这里我整理了一下,写了一个 DEMO 希望大家以后少走点弯路. 首先,我们先把界面的框架图画出来,示意图如下: 中间白色部门是一个线性布局文件,我喜欢在画图的时候用不同的颜色将一块布局标示出来,方便查看.布局文件代码如下:

  • 浅谈解决Hibernate懒加载的4种方式

    本文总结了我在学习hibernate的过程中,解决hibernate懒加载问题的四种方式. 所谓懒加载(lazy)就是延时加载,延迟加载. 什么时候用懒加载呢,我只能回答要用懒加载的时候就用懒加载. 至于为什么要用懒加载呢,就是当我们要访问的数据量过大时,明显用缓存不太合适,因为内存容量有限,为了减少并发量,减少系统资源的消耗,我们让数据在需要的时候才进行加载,这时我们就用到了懒加载. 例如,有一个对象是Employee,还有一个对象是Department.显然,对于Employee相对Depa

  • 浅谈MUI框架中加载外部网页或服务器数据的方法

    我们很多同学在实施使用MUI框架的时候,在打开新的页面的时候常使用的方式是:mui.openwindow的方法,然而遇到网页需要从服务器或者是要嵌套外部的网页的时候,由于网速的问题会遇到加载时出现白屏,等待时间过长,导致用户体验不好. 页面加载的时候使用plus.webview.create方法就很好的解决了这个问题. 废话不多说直接贴代码 首先我们需要在创建一个父页面,以下是父页面的JS // H5 plus事件处理 function plusReady(){ var nwaiting = p

  • 浅谈SpringBoot2.4 配置文件加载机制大变化

    前言 Spring Boot 2.4.0.M2刚刚发布,它对 application.properties 和 application.yml 文件的加载方式进行重构.如果应用程序仅使用单个 application.properties 或 application.yml 作为配置文件,那么可能感受不到任何区别.但是如果您的应用程序使用更复杂的配置(例如,Spring Cloud 配置中心等),则需要来了解更改的内容以及原因. 为什么要进行这些更改 随着最新版本 Spring Boot 发布,S

  • 浅谈hibernate中懒加载禁用操作

    浅谈hibernate中懒加载禁用操作 懒加载的概念:懒加载就是hibernate中的延迟加载,在hibernate中的一对多,多对多关系中通过对象导航来查询对象时一般默认的就是懒加载.就是当我们查询一个对象的时候,在默认情况下,返回的只是该对象的代理对象,当用户去使用该对象的属性是,才会向数据库中再一次发出查询语句.懒加载在某些情况下确实可以减少不必要的sql语句,但是有的情况下,还是会抛出异常. 下面我将介绍懒加载禁用的方式 方式一: 在需要禁用懒加载的实体对象的配置文件中配置lazy="f

随机推荐