Android Classloader

Android N

最近的工作遇到了运用反射来加载类,需要用到这块的知识,因此有了这篇文章.

1. java类加载器

Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程。 具体过程参考<<深入理解java虚拟机>>.在加载阶段,java虚拟机需要完成以下3件事:

  • 通过一个类的全限定名来获取定义此类的二进制字节流。

  • 将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构。

  • 在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口,一个类不管new多少实例,只有一个对应的class对象.

这个加载过程是使用类加载器完成的.

在java里面默认的有三个类加载器:BootStrap,ExtClassLoader,AppClassLoader.类加载器也是需要加载的,BootStrap是用C++书写,直接在java虚拟机的内核里面,用于加载类加载器, 以及rt.jar包,ExtClassLoader是用来加载在Java/jre7/lib/ext/下面的jar包.

每个ClassLoader必须有一个父ClassLoader,在装载Class文件时,子ClassLoader会先请求父ClassLoader加载该Class文件,只有当其父ClassLoader找不到该Class文件时, 子ClassLoader才会继续装载该类,这是一种安全机制。叫类加载器的委托机制.

在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。Android中ClassLoader的defineClass方法具体是调用VMClassLoader的defineClass本地静态方法。 而这个本地方法除了抛出一个“UnsupportedOperationException”之外,什么都没做,甚至连返回值都为空.所以在android中继承ClassLoader定义自己的类加载器是行不通的. 为了在android中实现动态的加载类,Android从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader.

  • PathClassLoader是android中的默认加载器,只能加载/data/app中的apk,即安装在手机中的apk;
  • DexClassLoader可以加载任何路径的apk/dex/jar;

2. android的类加载器

2.1 DexClassLoader

加载器的代码在libcore/dalvik/src/main/java/dalvik/system/,DexClassLoader.java:

  1. package dalvik.system;
  2. /**
  3. * A class loader that loads classes from {@code .jar} and {@code .apk} files
  4. * containing a {@code classes.dex} entry. This can be used to execute code not
  5. * installed as part of an application.
  6. *
  7. * <p>This class loader requires an application-private, writable directory to
  8. * cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
  9. * such a directory: <pre> {@code
  10. * File dexOutputDir = context.getCodeCacheDir();
  11. * }</pre>
  12. *
  13. * <p><strong>Do not cache optimized classes on external storage.</strong>
  14. * External storage does not provide access controls necessary to protect your
  15. * application from code injection attacks.
  16. */
  17. public class DexClassLoader extends BaseDexClassLoader {
  18. /**
  19. * Creates a {@code DexClassLoader} that finds interpreted and native
  20. * code. Interpreted classes are found in a set of DEX files contained
  21. * in Jar or APK files.
  22. *
  23. * <p>The path lists are separated using the character specified by the
  24. * {@code path.separator} system property, which defaults to {@code :}.
  25. *
  26. * @param dexPath the list of jar/apk files containing classes and
  27. * resources, delimited by {@code File.pathSeparator}, which
  28. * defaults to {@code ":"} on Android
  29. * @param optimizedDirectory this parameter is deprecated and has no effect
  30. * @param librarySearchPath the list of directories containing native
  31. * libraries, delimited by {@code File.pathSeparator}; may be
  32. * {@code null}
  33. * @param parent the parent class loader
  34. */
  35. public DexClassLoader(String dexPath, String optimizedDirectory,
  36. String librarySearchPath, ClassLoader parent) {
  37. super(dexPath, null, librarySearchPath, parent);
  38. }
  39. }

DexClassLoader构造器,只有dexPath,optimizedDirectory,librarySearchPath,parent:

2.2 PathClassLoader

PathClassLoader的源码也在libcore/dalvik/src/main/java/dalvik/system/,PathClassLoader.java:

  1. public class PathClassLoader extends BaseDexClassLoader {
  2. /**
  3. * Creates a {@code PathClassLoader} that operates on a given list of files
  4. * and directories. This method is equivalent to calling
  5. * {@link #PathClassLoader(String, String, ClassLoader)} with a
  6. * {@code null} value for the second argument (see description there).
  7. *
  8. * @param dexPath the list of jar/apk files containing classes and
  9. * resources, delimited by {@code File.pathSeparator}, which
  10. * defaults to {@code ":"} on Android
  11. * @param parent the parent class loader
  12. */
  13. public PathClassLoader(String dexPath, ClassLoader parent) {
  14. super(dexPath, null, null, parent);
  15. }
  16. /**
  17. * Creates a {@code PathClassLoader} that operates on two given
  18. * lists of files and directories. The entries of the first list
  19. * should be one of the following:
  20. *
  21. * <ul>
  22. * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as
  23. * well as arbitrary resources.
  24. * <li>Raw ".dex" files (not inside a zip file).
  25. * </ul>
  26. *
  27. * The entries of the second list should be directories containing
  28. * native library files.
  29. *
  30. * @param dexPath the list of jar/apk files containing classes and
  31. * resources, delimited by {@code File.pathSeparator}, which
  32. * defaults to {@code ":"} on Android
  33. * @param librarySearchPath the list of directories containing native
  34. * libraries, delimited by {@code File.pathSeparator}; may be
  35. * {@code null}
  36. * @param parent the parent class loader
  37. */
  38. public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
  39. super(dexPath, null, librarySearchPath, parent);
  40. }
  41. }

PathClassLoader同样是继承BaseDexClassLoader类,有两个构造方法,最终都是调用BaseDexClassLoader的构造方法进行构造.但是与DexClassLoader相比, 没有了optimizedDirectory参数,其他都一样.原因是PathClassLoader加载的是android安装的apk的类,这些class的dex有一个固定的目录/data/dalvik-cache:

  1. Android:/data/dalvik-cache # cd arm/
  2. system@app@ApplicationsProvider@ApplicationsProvider.apk@classes.dex
  3. system@app@ApplicationsProvider@ApplicationsProvider.apk@classes.vdex
  4. system@app@AtciService@AtciService.apk@classes.art
  5. system@app@AtciService@AtciService.apk@classes.dex
  6. system@app@AtciService@AtciService.apk@classes.vdex
  7. system@app@BasicDreams@BasicDreams.apk@classes.art
  8. system@app@BasicDreams@BasicDreams.apk@classes.dex
  9. system@app@BasicDreams@BasicDreams.apk@classes.vdex
  10. system@app@BatteryWarning@BatteryWarning.apk@classes.art
  11. system@app@BatteryWarning@BatteryWarning.apk@classes.dex
  12. system@app@BatteryWarning@BatteryWarning.apk@classes.vdex
  13. system@app@Bluetooth@Bluetooth.apk@classes.art
  14. system@app@Bluetooth@Bluetooth.apk@classes.dex
  15. system@app@Bluetooth@Bluetooth.apk@classes.vdex
  16. system@app@BluetoothMidiService@BluetoothMidiService.apk@classes.art
  17. system@app@BluetoothMidiService@BluetoothMidiService.apk@classes.dex
  18. system@app@BluetoothMidiService@BluetoothMidiService.apk@classes.vdex
  19. system@app@BookmarkProvider@BookmarkProvider.apk@classes.art
  20. system@app@BookmarkProvider@BookmarkProvider.apk@classes.dex
  21. system@app@BookmarkProvider@BookmarkProvider.apk@classes.vdex
  22. system@app@BuiltInPrintService@BuiltInPrintService.apk@classes.art
  23. system@app@BuiltInPrintService@BuiltInPrintService.apk@classes.dex
  24. system@app@BuiltInPrintService@BuiltInPrintService.apk@classes.vdex
  25. ......

2.3 BaseDexClassLoader

DexClassLoader和PathClassLoader的构造方法都是调用了BaseDexClassLoader,在BaseDexClassLoader.java里面:

  1. public class BaseDexClassLoader extends ClassLoader{
  2. .....
  3. public BaseDexClassLoader(String dexPath, File optimizedDirectory,
  4. String librarySearchPath, ClassLoader parent) {
  5. super(parent);
  6. this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
  7. if (reporter != null) {
  8. reporter.report(this.pathList.getDexPaths());
  9. }
  10. }
  11. ......
  12. }

说明这最后都是从ClassLoader来的,ClassLoader才是祖师爷呀.在BaseDexClassLoader的构造方法里创建了DexPathList,也就干了这么一件事. BaseDexClassLoader创建时的四个参数的意义:

  • dexPath: 是加载apk/dex/jar的路径,dexPath,当有多个路径则采用:分割;
  • optimizedDirectory: 是dex的输出路径(因为加载apk/jar的时候会解压除dex文件,这个路径就是保存dex文件的),优化后的dex文件存在的目录;
  • librarySearchPath: 是加载的时候需要用到的lib库;
  • parent: 给DexClassLoader指定父加载器;

2.3.1 DexPathList

接着分析DexPathList代码,DexPathList.java在libcore/dalvik/src/main/java/dalvik/system,

  1. /**
  2. * Constructs an instance.
  3. *
  4. * @param definingContext the context in which any as-yet unresolved
  5. * classes should be defined
  6. * @param dexPath list of dex/resource path elements, separated by
  7. * {@code File.pathSeparator}
  8. * @param librarySearchPath list of native library directory path elements,
  9. * separated by {@code File.pathSeparator}
  10. * @param optimizedDirectory directory where optimized {@code .dex} files
  11. * should be found and written to, or {@code null} to use the default
  12. * system directory for same
  13. */
  14. public DexPathList(ClassLoader definingContext, String dexPath,
  15. String librarySearchPath, File optimizedDirectory) {
  16. if (definingContext == null) {
  17. throw new NullPointerException("definingContext == null");
  18. }
  19. if (dexPath == null) {
  20. throw new NullPointerException("dexPath == null");
  21. }
  22. if (optimizedDirectory != null) {
  23. if (!optimizedDirectory.exists()) {
  24. throw new IllegalArgumentException(
  25. "optimizedDirectory doesn't exist: "
  26. + optimizedDirectory);
  27. }
  28. if (!(optimizedDirectory.canRead()
  29. && optimizedDirectory.canWrite())) {
  30. throw new IllegalArgumentException(
  31. "optimizedDirectory not readable/writable: "
  32. + optimizedDirectory);
  33. }
  34. }
  35. this.definingContext = definingContext;
  36. ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
  37. // save dexPath for BaseDexClassLoader
  38. // 记录所有的dexFile文件
  39. this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
  40. suppressedExceptions, definingContext);
  41. // Native libraries may exist in both the system and
  42. // application library paths, and we use this search order:
  43. //
  44. // 1. This class loader's library path for application libraries (librarySearchPath):
  45. // 1.1. Native library directories
  46. // 1.2. Path to libraries in apk-files
  47. // 2. The VM's library path from the system property for system libraries
  48. // also known as java.library.path
  49. //
  50. // This order was reversed prior to Gingerbread; see http://b/2933456.
  51. // app的native库
  52. this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
  53. // 系统的native库
  54. this.systemNativeLibraryDirectories =
  55. splitPaths(System.getProperty("java.library.path"), true);
  56. // 把app的native库和system的native库合并,汇总
  57. List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
  58. allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
  59. // 记录所有的native动态库
  60. this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
  61. if (suppressedExceptions.size() > 0) {
  62. this.dexElementsSuppressedExceptions =
  63. suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
  64. } else {
  65. dexElementsSuppressedExceptions = null;
  66. }
  67. }

总结的构造的过程中主要干了两件事:记录DexFile(dexElements)和记录native动态库(nativeLibraryPathElements).

2.4 ClassLoader

所有的类加载器最终都是在调用ClassLoader类,该类是类加载的核心,其他都只是进行封装,打包.ClassLoader.java在libcore/ojluni/src/main/java/java/lang/ClassLoader.java, 下面只是列出了主要代码部分:

  1. public abstract class ClassLoader {
  2. static private class SystemClassLoader {
  3. public static ClassLoader loader = ClassLoader.createSystemClassLoader();
  4. }
  5. /**
  6. * Encapsulates the set of parallel capable loader types.
  7. */
  8. private static ClassLoader createSystemClassLoader() {
  9. String classPath = System.getProperty("java.class.path", ".");
  10. String librarySearchPath = System.getProperty("java.library.path", "");
  11. // String[] paths = classPath.split(":");
  12. // URL[] urls = new URL[paths.length];
  13. // for (int i = 0; i < paths.length; i++) {
  14. // try {
  15. // urls[i] = new URL("file://" + paths[i]);
  16. // }
  17. // catch (Exception ex) {
  18. // ex.printStackTrace();
  19. // }
  20. // }
  21. //
  22. // return new java.net.URLClassLoader(urls, null);
  23. // TODO Make this a java.net.URLClassLoader once we have those?
  24. return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
  25. }
  26. public static ClassLoader getSystemClassLoader() {
  27. // 返回系统默认的加载器,appclassloader
  28. return SystemClassLoader.loader;
  29. }
  30. // 加载使用双亲委派模式
  31. protected Class<?> loadClass(String name, boolean resolve)
  32. throws ClassNotFoundException
  33. {
  34. // First, check if the class has already been loaded
  35. // 首先,检查请求的类是否已经加载过
  36. Class<?> c = findLoadedClass(name);
  37. if (c == null) {
  38. try {
  39. if (parent != null) {//委派父类加载器加载
  40. c = parent.loadClass(name, false);
  41. } else {//委派启动类加载器加载
  42. c = findBootstrapClassOrNull(name);
  43. }
  44. } catch (ClassNotFoundException e) {//父类加载器无法完成加载请求
  45. // ClassNotFoundException thrown if class not found
  46. // from the non-null parent class loader
  47. }
  48. if (c == null) {//本身加载器无法完成加载
  49. // If still not found, then invoke findClass in order
  50. // to find the class.
  51. c = findClass(name);
  52. }
  53. }
  54. return c;
  55. }
  56. /**
  57. * Returns the class with the given <a href="#name">binary name</a> if this
  58. * loader has been recorded by the Java virtual machine as an initiating
  59. * loader of a class with that <a href="#name">binary name</a>. Otherwise
  60. * <tt>null</tt> is returned.
  61. *
  62. * @param name
  63. * The <a href="#name">binary name</a> of the class
  64. *
  65. * @return The <tt>Class</tt> object, or <tt>null</tt> if the class has
  66. * not been loaded
  67. *
  68. * @since 1.1
  69. */
  70. protected final Class<?> findLoadedClass(String name) {
  71. ClassLoader loader;
  72. if (this == BootClassLoader.getInstance())
  73. loader = null;
  74. else
  75. loader = this;
  76. return VMClassLoader.findLoadedClass(loader, name);
  77. }
  78. // 自定义ClassLoader需要重写该方法
  79. protected Class<?> findClass(String name) throws ClassNotFoundException {
  80. throw new ClassNotFoundException(name);
  81. }