现在插件化技术十分热门,其核心原理之一是用到了ClassLoader类加载器。因此有必要来了解下Android中的ClassLoader加载原理。
动态加载dex/jar/apk文件的基础是类加载器ClassLoader,它的包路径是java.lang,由此可见其重要性,虚拟机就是通过类加载器加载其需要用的Class,这是Java程序运行的基础。
Java当中的类加载器分类
1.Bootstrp loader:加载jvm中指定路径的字节码文件。
2.AppClassLoader: 加载安装到系统的路径下的classs字节码文件。
3.CusomClassLoader:自定义加载字节码文件
Android中的类加载器分类
1.Baseclassloader:加载一些framework层的字节码文件。
2.DexClassLoader:加载指定路径下的字节码文件。
3.PathClassLoader:加载安装到系统中的app下制定目录的字节码文件。
在Android中classloader这个类是怎样加载字节码文件的呢?下面来分析一下Android中的classloader类如何加载Dex文件。
加载Dex文件主要用到的方法是ClassLoader的loadClass(string)方法。
loadClass(string)其实是执行了loadClass(string,false)。
第 2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行。
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
// Don't want to see this.
}
if (clazz == null) {
clazz = findClass(className);
}
}
return clazz;
}
首先去去找当前classloader的父classloader是否加载过这个文件,用父加载器去加载。
如果没有,在去缓存中找当前classsloader是否加载过对应的字节码文件。
ClassLoader在这里使用了双亲委托模式进行类加载。任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功后,才会由自己加载。
类加载器的双亲委派机制有什么好处呢?两个字,安全,假设当我们覆盖重写java.lang.String这个类的时候,因为类加载模式会先从bootstrapclassloader加载,于是就防止我们用自己定义的String类覆盖系统的String类。
具体点说就是因为ClassLoader使用以上双亲委派机制,所以当我们使用一个自定义的ClassLoader加载 java.lang.String时,会先判断这个ClassLoader的parent是否为null,当ClassLoader的parent为null 时,ClassLoader的parent就是bootstrap classloader,所以在ClassLoader的最顶层就是bootstrap classloader,因此最终委托到bootstrap classloader的时候,bootstrap classloader就会返回String的Class。
额,回过头来,然后再接着原来的代码往下看,找到findClass方法。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new RuntimeException("Stub!");
}
我们看到这里是空实现,应该是想由子类继承实现。
在android当中,classloader的继承类主要有baseclassloader,DexClassLoader,以及PathClassLoader这三个。
首先来研究一下DexClassLoader,这个类的源码在android studio里面是不能看的。要到源码网站上去看,具体地址:
http://androidxref.com/4.4.2_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
6public class DexClassLoader extends BaseDexClassLoader {
37 /**
38 * Creates a {@code DexClassLoader} that finds interpreted and native
39 * code. Interpreted classes are found in a set of DEX files contained
40 * in Jar or APK files.
41 *
42 * <p>The path lists are separated using the character specified by the
43 * {@code path.separator} system property, which defaults to {@code :}.
44 *
45 * @param dexPath the list of jar/apk files containing classes and
46 * resources, delimited by {@code File.pathSeparator}, which
47 * defaults to {@code ":"} on Android
48 * @param optimizedDirectory directory where optimized dex files
49 * should be written; must not be {@code null}
50 * @param libraryPath the list of directories containing native
51 * libraries, delimited by {@code File.pathSeparator}; may be
52 * {@code null}
53 * @param parent the parent class loader
54 */
55 public DexClassLoader(String dexPath, String optimizedDirectory,
56 String libraryPath, ClassLoader parent) {
57 super(dexPath, new File(optimizedDirectory), libraryPath, parent);
58 }
59}
然后再来看下pathclassloader
这个类里面代码页比较少,但是就是看下构造函数
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
我们把这个构造函数跟上面这个dexclaasloader的构造函数比较一下。
发现少了一个optimizedDirecotry,即pathclassloader只能加载系统中的dex文件;而DexClassLoader则可以加载指定目录下面的dex文件。
他们两个类都继承了BaseDexClassLoader。
这个BaseDexClassLoader的源码如下:
25/**
26 * Base class for common functionality between various dex-based
27 * {@link ClassLoader} implementations.
28 */
29public class BaseDexClassLoader extends ClassLoader {
30 private final DexPathList pathList;
31
32 /**
33 * Constructs an instance.
34 *
35 * @param dexPath the list of jar/apk files containing classes and
36 * resources, delimited by {@code File.pathSeparator}, which
37 * defaults to {@code ":"} on Android
38 * @param optimizedDirectory directory where optimized dex files
39 * should be written; may be {@code null}
40 * @param libraryPath the list of directories containing native
41 * libraries, delimited by {@code File.pathSeparator}; may be
42 * {@code null}
43 * @param parent the parent class loader
44 */
45 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
46 String libraryPath, ClassLoader parent) {
47 super(parent);
48 this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
49 }
50
51 @Override
52 protected Class<?> findClass(String name) throws ClassNotFoundException {
53 List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
54 Class c = pathList.findClass(name, suppressedExceptions);
55 if (c == null) {
56 ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
57 for (Throwable t : suppressedExceptions) {
58 cnfe.addSuppressed(t);
59 }
60 throw cnfe;
61 }
62 return c;
63 }
64
65 @Override
66 protected URL findResource(String name) {
67 return pathList.findResource(name);
68 }
主要看下54行findClass方法,通过成员变量pathlist真正的完成了class文件的查找。
这个pathlist成员变量在构造函数里面,我们发现,声明的 类型是DexPathList,第一个参数this表示classloader本身,第二个参数dexPath表示要传入的dex文件的路径。第四个参数 optimizedDirectory表示要复制到的内部文件夹中的位置,可以为空,空的话相当于使用到了PathClassLoader了。
DexPathList类源码:
48/*package*/ final class DexPathList {
49 private static final String DEX_SUFFIX = ".dex";
50 private static final String JAR_SUFFIX = ".jar";
51 private static final String ZIP_SUFFIX = ".zip";
52 private static final String APK_SUFFIX = ".apk";
53
54 /** class definition context */
55 private final ClassLoader definingContext;
56
57 /**
58 * List of dex/resource (class path) elements.
59 * Should be called pathElements, but the Facebook app uses reflection
60 * to modify 'dexElements' (http://b/7726934).
61 */
62 private final Element[] dexElements;
63
64 /** List of native library directories. */
65 private final File[] nativeLibraryDirectories;
66
67 /**
68 * Exceptions thrown during creation of the dexElements list.
69 */
70 private final IOException[] dexElementsSuppressedExceptions;
71
72 /**
73 * Constructs an instance.
74 *
75 * @param definingContext the context in which any as-yet unresolved
76 * classes should be defined
77 * @param dexPath list of dex/resource path elements, separated by
78 * {@code File.pathSeparator}
79 * @param libraryPath list of native library directory path elements,
80 * separated by {@code File.pathSeparator}
81 * @param optimizedDirectory directory where optimized {@code .dex} files
82 * should be found and written to, or {@code null} to use the default
83 * system directory for same
84 */
85 public DexPathList(ClassLoader definingContext, String dexPath,
86 String libraryPath, File optimizedDirectory) {
87 if (definingContext == null) {
88 throw new NullPointerException("definingContext == null");
89 }
90
91 if (dexPath == null) {
92 throw new NullPointerException("dexPath == null");
93 }
94
95 if (optimizedDirectory != null) {
96 if (!optimizedDirectory.exists()) {
97 throw new IllegalArgumentException(
98 "optimizedDirectory doesn't exist: "
99 + optimizedDirectory);
100 }
101
102 if (!(optimizedDirectory.canRead()
103 && optimizedDirectory.canWrite())) {
104 throw new IllegalArgumentException(
105 "optimizedDirectory not readable/writable: "
106 + optimizedDirectory);
107 }
108 }
109
110 this.definingContext = definingContext;
111 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
112 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
113 suppressedExceptions);
114 if (suppressedExceptions.size() > 0) {
115 this.dexElementsSuppressedExceptions =
116 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
117 } else {
118 dexElementsSuppressedExceptions = null;
119 }
120 this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
121 }
122
123 @Override public String toString() {
124 return "DexPathList[" + Arrays.toString(dexElements) +
125 ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]";
126 }
127
成员变量 DEX_SUFFIX = “.dex”表示我们要加载的是dex文件。DexPathList 有一个非常重要的成员变量private final Element[] dexElements;
这个Element其实就是它的内部类,而内部类Element有个成员变量叫dexfile,表示的正是dex文件在android内存中表现形式。
我们来看一下这个类的构造函数,关键看到112行,这里对dexElements进行初始化,调用的方法是makeDexElements方法。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
207 ArrayList<IOException> suppressedExceptions) {
208 ArrayList<Element> elements = new ArrayList<Element>();
209 /*
210 * Open all files and load the (direct or contained) dex files
211 * up front.
212 */
213 for (File file : files) {
214 File zip = null;
215 DexFile dex = null;
216 String name = file.getName();
217
218 if (name.endsWith(DEX_SUFFIX)) {
219 // Raw dex file (not inside a zip/jar).
220 try {
221 dex = loadDexFile(file, optimizedDirectory);
222 } catch (IOException ex) {
223 System.logE("Unable to load dex file: " + file, ex);
224 }
225 } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
226 || name.endsWith(ZIP_SUFFIX)) {
227 zip = file;
228
229 try {
230 dex = loadDexFile(file, optimizedDirectory);
231 } catch (IOException suppressed) {
232 /*
233 * IOException might get thrown "legitimately" by the DexFile constructor if the
234 * zip file turns out to be resource-only (that is, no classes.dex file in it).
235 * Let dex == null and hang on to the exception to add to the tea-leaves for
236 * when findClass returns null.
237 */
238 suppressedExceptions.add(suppressed);
239 }
240 } else if (file.isDirectory()) {
241 // We support directories for looking up resources.
242 // This is only useful for running libcore tests.
243 elements.add(new Element(file, true, null, null));
244 } else {
245 System.logW("Unknown file type for: " + file);
246 }
247
248 if ((zip != null) || (dex != null)) {
249 elements.add(new Element(file, false, zip, dex));
250 }
251 }
252
253 return elements.toArray(new Element[elements.size()]);
254 }
所以咯这个类的主要作用就是从制定文件夹路径中的dex加载到dexfile里面,并且存入到elements当中。那么存到elements数组中有什么作用呢?作用就是findclass方法。
我们来看一下
public Class findClass(String name, List<Throwable> suppressed) {
318 for (Element element : dexElements) {
319 DexFile dex = element.dexFile;
320
321 if (dex != null) {
322 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
323 if (clazz != null) {
324 return clazz;
325 }
326 }
327 }
328 if (dexElementsSuppressedExceptions != null) {
329 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
330 }
331 return null;
332 }
findClass 方法主要是遍历成员变量dexElements。
如果每个Element的dexfile对象为空,则进行class类的查找。
我们最终发现,通过 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);这句话完成了类的查找。那么这个Dexfile里面的loadClassBinaryName又干了些什么事情呢。我们找到Dexfile源码
。
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
215 return defineClass(name, loader, mCookie, suppressed);
216 }
217
218 private static Class defineClass(String name, ClassLoader loader, int cookie,
219 List<Throwable> suppressed) {
220 Class result = null;
221 try {
222 result = defineClassNative(name, loader, cookie);
223 } catch (NoClassDefFoundError e) {
224 if (suppressed != null) {
225 suppressed.add(e);
226 }
227 } catch (ClassNotFoundException e) {
228 if (suppressed != null) {
229 suppressed.add(e);
230 }
231 }
232 return result;
233 }
private static native Class defineClassNative(String name, ClassLoader loader, int cookie)
236 throws ClassNotFoundException, NoClassDefFoundError;
loadClassBinaryName最终调用defineClassNative方法。
我们可以想象一下defineClassNative的大致作用,通过c
或者c++去查找制定name的Class的相关信息,返回一个Class类型的数据,这个数据就是class文件字节码。
因为源码是在c层,所以这里是看不了。
好了,这样就把ClassLoader如何加载dex文件的全部流程都走了一遍。