1 前言
Class类加载机制是了解Android高级技术的基础,现在热门的热修复,插件化,以及Dex分包等技术,都需要理解class类加载机制,了解了class类加载机制我们能更好的理解Android内部原理,从而使我们的技术提高一个层次
Android是基于java的,我们先来了解一下java的类加载机制
2 java Class类加载机制
java系统自带有三个类加载器,分别是Bootstrap ClassLoader, Extention ClassLoader,Appclass Loader
Bootstrap ClassLoader :是用C++语言实现的,最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录。
Extention ClassLoader :扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
Appclass Loader:也称为SystemAppClass 加载当前应用的classpath的所有类。
加载顺序
1. Bootstrap CLassloder
2. Extention ClassLoader
3. AppClassLoader
在java代码中我们可以使用如下代码进行测试:
ClassLoader classLoader = Main.class.getClassLoader();
//是APPClassLoader
System.out.println("Main.class ClassLoader:" + classLoader.toString());
URL[] urls = ((URLClassLoader)classLoader).getURLs();
printURL(urls);
ClassLoader parent1 = classLoader.getParent();
//是ExtClassLoader
System.out.println("Main.class parent ClassLoader:" + parent1.toString());
URL[] urls2 = ((URLClassLoader)parent1).getURLs();
printURL(urls2);
ClassLoader parent2 = parent1.getParent();
//是BootstrapLoader
System.out.println("ExtClassLoader parent ClassLoader:" + parent2.toString());
这三者的关系如下:
这三者的继承关系如下:
3 Android Class类加载机制
在Android中 有 2种类加载器:
PathClassLoader和DexClassLoader。分别位于如下
libcore\dalvik\src\main\java\dalvik\system\PathClassLoader.java
libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
定义如下
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
public class **PathClassLoader** extends BaseDexClassLoader {
......
}
/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
*
* <p>This class loader requires an application-private, writable directory to
* cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
* such a directory: <pre> {@code
* File dexOutputDir = context.getCodeCacheDir();
* }</pre>
*
* <p><strong>Do not cache optimized classes on external storage.</strong>
* External storage does not provide access controls necessary to protect your
* application from code injection attacks.
*/
public class **DexClassLoader** extends BaseDexClassLoader {
......
}
可以看到都继承了BaseDexClassLoader 这个基类,这个类又是继承ClassLoader的,这里先不去管它。我们来看注释。
对于PathClassLoader,从文档上的注释来看:Android是使用这个类作为其系统类和应用类的加载器。并且对于这个类呢,只能去加载已经安装到Android系统中的apk文件。
对于DexClassLoader,依然看下注释:可以看到可以加载从jar包中的,apk中的类。
Android要加载一个类 是通过ClassLoader的findClass方法 在dex中查找这个类 找到后加载到内存
我们来看下Android中类是如何加载的。
我们查看PathClassLoader与DexClassLoader均发现没有findClass()方法,因此我们在其父类BaseDexClassLoader中找一下
/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
...
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
....
}
可以看到,Android中类的加载是从DexPathList 的findClass()方法中加载的,我们再来看DexPathList 中查看
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
......
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private final Element[] dexElements;
......
/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*
* @param name of class to find
* @param suppressed exceptions encountered whilst finding the class
* @return the named class or {@code null} if the class is not
* found in any of the dex files
*/
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
}
可以看到查找类是在dexElements数组中依次遍历查找的。一个classloader可以包含多个dex,其中这个集合中的对象就是所有的dex文件,查找是从头开始遍历所有的dex 如果在dex中找到所需要的类,那么就直接返回。
在这个dex中查找相应名字的类,之后 defineClass把字节码交给虚拟机就完成了类的加载。