欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

详解Android类加载ClassLoader

程序员文章站 2023-11-17 18:52:16
基本知识 java的类加载设计了一套双亲代理的模式,使得用户没法替换系统的核心类,从而让应用更安全。所谓双亲代理就是指,当加载类的时候首先去bootstrap中加载类,如...

基本知识

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合并,就可以实现优先加载我们自己的新类,从而替换旧类中的逻辑了。

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