详解Android类加载ClassLoader

基本知识

Java的类加载设计了一套双亲代理的模式,使得用户没法替换系统的核心类,从而让应用更安全。所谓双亲代理就是指,当加载类的时候首先去Bootstrap中加载类,如果没有则去Extension中加载,如果再没有才去AppClassLoader中去加载。从而实现安全和稳定。

Java ClassLoader

BootstrapClassLoader

引导类加载器 ,用来加载Java的核心库。通过底层代码来实现的,基本上只要parent为null,那就表示引导类加载器。

比如:charsets.jar、deploy.jar、javaws.jar、jce.jar、jfr.jar、jfxswt.jar、jsse.jar、management-agent.jar、plugin.jar、resources.jar、rt.jar

ExtClassLoader

拓展类加载器 ,用来加载Java的拓展的类库, ${JAVA_HOME}/jre/lib/ext/ 目录中的所有jar。

比如:cldrdata.jar、dnsns.jar、jfxrt.jar、localedata.jar、nashorn.jar、sunec.jar、sunjce_provider.jar、sunpkcs11.jar、zipfs.jar等等

AppClassLoader

系统类加载器 (不要被名字给迷惑),用来加载Java应用中的类。一般来说自己写的类都是通过这个加载的。而Java中 ClassLoader.getSystemClassLoader() 返回的就是AppClassLoader。(Android中修改了ClassLoader的逻辑,返回的会是一个PathClassLoader)

自定义ClassLoader

用户如果想自定义ClassLoader的话,只需要继承自 java.lang.ClassLoader 即可。

ClassLoader中与加载类相关的方法:

  1. getParent() 返回该类加载器的父类加载器。
  2. loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
  3. findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
  4. findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
  5. defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。

也许你不太了解上面几个函数的区别,没关系,我们来看下源码是如何实现的。

//ClassLoader.java
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) {
      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
      }
    }
    return c;
}

所以优先级大概如下:

loadClass → findLoadedClass → parent.loadClass/findBootstrapClassOrNull → findClass → defineClass

Android ClassLoader

在Android中ClassLoader主要有两个直接子类,叫做 BaseDexClassLoader 和 SecureClassLoader 。而前者有两个直接子类是 PathClassLoader 和 DexClassLoader (Android O添加了 InMemoryDexClassLoader ,略)。

我们只讨论PathClassLoader和DexClassLoader

PathClassLoader

用来加载安装了的应用中的dex文件。它也是Android里面的一个最核心的ClassLoader了。相当于Java中的那个AppClassLoader。

public class PathClassLoader extends BaseDexClassLoader {
  /**
   * Creates a {@code PathClassLoader} that operates on a given list of files
   * and directories. This method is equivalent to calling
   * {@link #PathClassLoader(String, String, ClassLoader)} with a
   * {@code null} value for the second argument (see description there).
   *
   * @param dexPath the list of jar/apk files containing classes and
   * resources, delimited by {@code File.pathSeparator}, which
   * defaults to {@code ":"} on Android
   * @param parent the parent class loader
   */
  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).
   * </ul>
   *
   * 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 librarySearchPath 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 librarySearchPath, ClassLoader parent) {
    super(dexPath, null, librarySearchPath, parent);
  }
}

它的实例化是通过调用 ApplicationLoaders.getClassLoader 来实现的。

它是在ActivityThread启动时发送一个BIND_APPLICATION消息后在handleBindApplication中创建ContextImpl时调用LoadedApk里面的 getResources(ActivityThread mainThread) 最后回到ActivityThread中又调用LoadedApk的 getClassLoader 生成的,具体的在LoadedApk的 createOrUpdateClassLoaderLocked

那么问题来了,当Android加载class的时候,LoadedApk中的ClassLoader是怎么被调用到的呢?

其实Class里面,如果你不给ClassLoader的话,它默认会去拿Java虚拟机栈里面的 CallingClassLoader ,而这个就是LoadedApk里面的同一个ClassLoader。

//Class.java
public static Class<?> forName(String className)
      throws ClassNotFoundException {
  return forName(className, true, VMStack.getCallingClassLoader());
}

查看VMStack的源码发现 getCallingClassLoader 其实是一个native函数,Android通过底层实现了这个。

//dalvik.system.VMStack
/**
 * Returns the defining class loader of the caller's caller.
 *
 * @return the requested class loader, or {@code null} if this is the
 *     bootstrap class loader.
 */
@FastNative
native public static ClassLoader getCallingClassLoader();

底层想必最终也是拿到LoadedApk里面的ClassLoader。

DexClassLoader

它是一个可以用来加载包含dex文件的jar或者apk文件的,但是它可以用来加载非安装的apk。比如加载sdcard上面的,或者NetWork的。

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);
  }
}

比如现在很流行的插件化/热补丁,其实都是通过DexClassLoader来实现的。具体思路是: 创建一个DexClassLoader,通过反射将前者的DexPathList跟系统的PathClassLoader中的DexPathList合并,就可以实现优先加载我们自己的新类,从而替换旧类中的逻辑了。

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

(0)

相关推荐

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

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

  • 详解Android类加载ClassLoader

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

  • 详解Android ViewCompat的作用

    详解Android ViewCompat的作用 ViewCompat类主要是用来提供兼容性的, 比如我最近看的比较的多的canScrollVertically方法, 在ViewCompat里面针对几个版本有不同的实现, 原理上还是根据版本判断, 有时甚至还要判断传入参数的类型. 但是要注意的是, ViewCompat仅仅让你调用不崩溃, 并不保证你调用的结果在不同版本的机器上一致. 关于如何优雅的组织代码, ViewCompat类的结构非常适合我们参考. ViewCompat里面定义了一个接口,

  • 详解JAVA类加载机制

    1.一段简单的代码 首先来一段代码,这个是单例模式,可能有的人不知道什么是单例模式,我就简单说一下 单例模式是指一个类有且只有一种对象实例.这里用的是饿汉式,还有懒汉式,双检锁等等.... 写这个是为了给大家看一个现象 class SingleTon{ public static int count1; public static int count2=0; private static SingleTon instance=new SingleTon(); public SingleTon()

  • 详解Android Activity的启动流程

    前言 activity启动的流程分为两部分:一是在activity中通过startActivity(Intent intent)方法启动一个Activity:二是我们在桌面通过点击应用图标启动一个App然后显示Activity:第二种方式相较于第一种方式更加全面,所以本文会以第二种流程来分析. 简要 我们手机的桌面是一个叫做Launcher的Activity,它罗列了手机中的应用图标,图标中包含安装apk时解析的应用默认启动页等信息.在点击应用图标时,即将要启动的App和Launcher.AMS

  • 详解Android系统启动过程

    计算机是如何启动的 计算机的硬件包括:CPU,内存,硬盘,显卡,显示器,键盘鼠标等输入输出设备.所有的软件都是存放在硬盘中,程序执行时,需要将程序从硬盘上读取到内存中,然后加载到CPU中来运行.当按下开机键时,内存中什么都没有,因此需要借助某种方式,将操作系统加载到内存中,而完成这项任务的就是BIOS. 引导阶段 BIOS:BIOS是主板芯片上的一个程序,计算机通电后,第一件事情就是读取BIOS. BIOS首先进行硬件检测,检查计算机硬件能否满足运行的基本条件.如果硬件出现问题,主板发出不同的蜂

  • 详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,PhoneWindow包裹DecorView.接下来我们首先看一下三者分别是如何创建的. 二.Activity是如何创建的 首先看到入口类ActivityThread的performLaunchActivity方法: private Activity performLaunchActivity(Activi

  • 详解Android中的ActivityThread和APP启动过程

    ActiviryThread ActivityThread的初始化 ActivityThread即Android的主线程,也就是UI线程,ActivityThread的main方法是一个APP的真正入口,MainLooper在它的main方法中被创建. //ActivityThread的main方法 public static void main(String[] args) { ... Looper.prepareMainLooper(); ActivityThread thread = ne

  • 详解Android内存泄露及优化方案一

    目录 一.常见的内存泄露应用场景? 1.单例的不恰当使用 2.静态变量导致内存泄露 3.非静态内部类导致内存泄露 4.未取消注册或回调导致内存泄露 5.定时器Timer 和 TimerTask 导致内存泄露 6.集合中的对象未清理造成内存泄露 7.资源未关闭或释放导致内存泄露 8.动画造成内存泄露 9.WebView 造成内存泄露 总结 一.常见的内存泄露应用场景? 1.单例的不恰当使用 单例是我们开发中最常见和使用最频繁的设计模式之一,所以如果使用不当就会导致内存泄露.因为单例的静态特性使得它

  • 详解Android Ashmem匿名共享内存

    目录 1. 简述 2. 创建 MemoryFile 和 数据写入 3. 将文件描述符传递到其他进程 4. 在其他进程接收 FileDescriptor 并读取数据 1. 简述 Android 的 匿名共享内存(Ashmem) 基于 Linux 的共享内存,都是在临时文件系统(tmpfs)上创建虚拟文件,再映射到不同的进程.它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制.相对于 Linux 的共享内存,Ashmem 对内存的管理更加精细化,并且添加了互斥锁.Java 层

  • 详解Android内存泄露及优化方案

    目录 一.常见的内存泄露应用场景? 1.单例的不恰当使用 2.静态变量导致内存泄露 3.非静态内部类导致内存泄露 4.未取消注册或回调导致内存泄露 5.定时器Timer 和 TimerTask 导致内存泄露 6.集合中的对象未清理造成内存泄露 7.资源未关闭或释放导致内存泄露 8.动画造成内存泄露 9.WebView 造成内存泄露 总结 一.常见的内存泄露应用场景? 1.单例的不恰当使用 单例是我们开发中最常见和使用最频繁的设计模式之一,所以如果使用不当就会导致内存泄露.因为单例的静态特性使得它

随机推荐