Android ClassLoader源码浅析

从源码的角度(基于Android9.0,API 28)记录一下Android类加载的机制,Android中的ClassLoader主要分为:
BootClassLoaderBaseDexClassLoaderPathClassLoaderDexClassLoaderInMemoryDexClassLoader
特点:双亲委托机制(留在下一篇介绍,本篇只介绍几个ClassLoader及常见的方法)

1.BootClassLoader:用于加载Android Framework层class文件
 BootClassLoader继承自ClassLoader,是ClassLoader的内部类,实现方式是单例模式,由于其访问修饰符是默认的,所以仅可在同包下可使用。

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null);
    }
		
	...	
}	

2.BaseDexClassLoader: 继承自ClassLoader

当前列举了BaseDexClassLoader的各个构造方法,下面会介绍每一个构造方法分别会在什么场景下使用。
我们如何通过一个完整的类名称去找到这个类,通过findClass方法,而这个方法会调用DexPathList.findClass方法,而DexPathList会在构造方法中初始化,接着我们再去寻找DexPathList。 

public class BaseDexClassLoader extends ClassLoader{

	...
	
	//存放需要加载的dexList
	private final DexPathList pathList;
	
	/**
     *
     * @param dexPath   需要加载的dex文件所在的路径
     * @param optimizedDirectory  Android系统将dex文件进行优化后所生成的ODEX文件的存放路径,该路径必须是一个内部存储路径。
     * @param librarySearchPath   目标类所使用的c、c++库存放的路径
     * @param parent  该加载器的父加载器,一般为当前执行类的加载器
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }

	/**
     *
     * @param dexPath
     * @param optimizedDirectory
     * @param librarySearchPath
     * @param parent
     * @param isTrusted   是否已信任,关系到是否可调用隐藏API
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

        ...
    }
	
	 /**
     * 
     * @param dexFiles 字节缓存数组的dex文件
     * @param parent   该加载器的父加载器
     */
	public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
        // TODO We should support giving this a library search path maybe.
        super(parent);
        this.pathList = new DexPathList(this, dexFiles);
    }
	
	/**
     * 通过完整的类名寻找对应的类
     * @param name  传入一个完整的类名
     * @return
     * @throws ClassNotFoundException
     */
	@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
		//在pathList中寻找name对应的类
        Class c = pathList.findClass(name, suppressedExceptions);
		// 如果未找到此类,则抛出异常
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
}

3.DexPathList:
上面我们知道已经在BaseDexClassLoader的构造器中初始化了DexPathList的构造方法,通过不同参数来区分不同的Dex文件类型。此时会通过makeDexElements方法生成一个Element数组,紧接着当前类中的findClass方法又会调用DexPathList中的Element类的findClass方法。

/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String zipSeparator = "!/";

    private final ClassLoader definingContext;

	// dex/resource 存放dex的数组
    private Element[] dexElements;

	// 存放本地库文件的列表
    private final List<File> nativeLibraryDirectories;

	// 存放系统本地库文件的列表
    private final List<File> systemNativeLibraryDirectories;

	// 存放创建dexElement列表时引发异常的列表
    private IOException[] dexElementsSuppressedExceptions;
	
	DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
        ...

        this.definingContext = definingContext; //BaseDexClassLoader构造器中会传入其本身

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // 通过dexPath路径使用分隔符将其转换成dexElements列表
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);

       
        ...
    }
	
	 // 为本地库搜索路径生成一个directory/zip path元素数组
	 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
     
      for (File file : files) {
          if (file.isDirectory()) { //如果是文件夹,则直接添加至elements
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) { //如果是文件
              String name = file.getName();

              DexFile dex = null;
              if (name.endsWith(DEX_SUFFIX)) { // 如果文件名称是以 ".dex" 结尾
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else {
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);  // 生成dexFile对象
                  } catch (IOException suppressed) {
                     
                      suppressedExceptions.add(suppressed);
                  }

                  if (dex == null) { 
                      elements[elementsPos++] = new Element(file); // 生成file对应的Element对象
                  } else {
                      elements[elementsPos++] = new Element(dex, file);  // 生成dexFile对应的Element对象
                  }
              }
              if (dex != null && isTrusted) { //如果dex对象不为空且是允许信任状态
                dex.setTrusted(); // 将此dex对象设置为已信任,它可以访问平台的隐藏api
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }
	
	public Class<?> findClass(String name, List<Throwable> suppressed) {
		// 遍历dex列表
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
			//如果找到我们需要的name类,直接返回当前clazz
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
}

4.Element:    DexPathList类中的静态内部类

/*package*/ static class Element {
      
        private final File path;

        private final DexFile dexFile;

        private ClassPathURLStreamHandler urlHandler;
        private boolean initialized;

		...
		
        public Element(DexFile dexFile, File dexZipPath) {
            this.dexFile = dexFile;
            this.path = dexZipPath;
        }

        public Element(DexFile dexFile) {
            this.dexFile = dexFile;
            this.path = null;
        }

        public Element(File path) {
          this.path = path;
          this.dexFile = null;
        }
		
		...
		
        public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
			// 通过loadClassBinaryName方法寻找name类,找到即返回它,否则返回null 
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }

    }

5.DexFile :加载DEX文件,该类用于内部使用(目前能看到的是已经在Android API 30中已经标注为“已弃用”状态)

@Deprecated
public final class DexFile {

	...
	
    @Deprecated
    public DexFile(File file) throws IOException {
        this(file.getPath());
    }
  
    DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
            throws IOException {
        this(file.getPath(), loader, elements);
    }

    @Deprecated
    public DexFile(String fileName) throws IOException {
        this(fileName, null, null);
    }
	
	...
	
	public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
		//调用静态方法defineClass加载name类
        return defineClass(name, loader, mCookie, this, suppressed);
    }
	
	private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
	
	// 已经到native层,有兴趣的可以继续查询相关文档
	private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
                                                  DexFile dexFile)
            throws ClassNotFoundException, NoClassDefFoundError;

	...
}

6.PathClassLoader: 继承自BaseDexClassLoader,用来加载已经安装到系统中的apk中的dex文件, 它的父类加载器getParent()是BootClassLoader(注意这里不是继承的关系)

public class PathClassLoader extends BaseDexClassLoader {
  
     /**
     *
     * @param dexPath   dex文件路径集合
     * @param parent    父加载器
     */
    public PathClassLoader(String dexPath, ClassLoader parent) {
        //调用父类BaseDexClassLoader 四参构造方法
        super(dexPath, null, null, parent);
    }

    /**
     *
     * @param dexPath   dex文件路径集合
     * @param librarySearchPath    包含 C/C++库的路径集合,多个路径用文件分隔符分隔分割,可以为null
     * @param parent    父加载器
     */
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        //调用父类BaseDexClassLoader 四参构造方法
        super(dexPath, null, librarySearchPath, parent);
    }
}

7.DexClassLoader:继承自BaseDexClassLoader,用来加载指定的目录中的dex文件(.apk,.zip), 它的父类加载器getParent()是BootClassLoader(注意这里不是继承的关系)

public class DexClassLoader extends BaseDexClassLoader {

	 /**
     *
     * @param dexPath      dex文件路径集合,多个路径用文件分隔符分隔,默认文件分隔符为":"
     * @param optimizedDirectory   解压的dex文件存储路径,这个路径必须是一个内部存储路径,一般情况下使用当前应用程序的私有路径
     * @param librarySearchPath   包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null
     * @param parent       父加载器
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
			
		// 调用父类BaseDexClassLoader 四参构造方法,在API26以上,librarySearchPath参数已弃用,使用此方法		
        super(dexPath, null, librarySearchPath, parent);
		
		// API26及以下使用此方法		
		// super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

8.InMemoryDexClassLoader:Android 8.1及以后版本出现的,继承自BaseDexClassLoader,dexBuffers数组构造了一个DexPathList,可用于加载内存中的dex。

public final class InMemoryDexClassLoader extends BaseDexClassLoader {
   
   /**
     *
     * @param dexBuffers DEX文件的字节缓冲数组
     * @param parent 父类加载器
     */
    public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
        // 调用BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) 两参构造函数
        super(dexBuffers, parent);
    }

    /**
     *
     * @param dexBuffer DEX文件的字节缓冲区
     * @param parent
     */
    public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
        // 调用当前类内部的构造方法,将字节缓冲区转化为字节缓冲数组
        this(new ByteBuffer[] { dexBuffer }, parent);
    }
	
}

以上就是我记录的在查看ClassLoader源码时的一些心得,希望对你有所帮助,如果上述有错误之处,欢迎指导~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值