前言
这篇文章主要是讲解Android中的ClassLoader
Dalvik VM
Dalvik是Google公司自己设计用于Android平台的Java虚拟机。它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,可以减少整体文件尺寸,提高I/o操作的类查找速度所以适合内存和处理器速度有限的系统。
关于Dalvik更多内容参考这篇文章:Dalvik概述
说的直白一点就是针对Android而生的JVM的升级。他与JVM的不同:
第一点不用解释
第二点后面会详细讲解
第三点:JVM只存在一个,DVM 可以存在多个,某一个引用程序挂掉以后不会影响的其他程序,保证程序的稳定性。
第四点:基于栈 表示方法的调用时在栈中完成的 寄存器运行更快
ART与Dalvik的不同
ART模式英文全称为:Android runtime,谷歌Android 4.4系统新增的一种应用运行模式,与传统的Dalvik模式不同,ART模式可以实现更为流畅的安卓系统体验,对于大家来说,只要明白ART模式可让系统体验更加流畅,不过只有在安卓4.4以上系统中采用此功能。这里只做简单介绍。
现在市面的手机基本都是ART模式了。
Android中的ClassLoader
JVM的类加载器是将字节码文件通过读取后加载到JVM运行时数据区。而Android中的ClassLoader的作用是一样的,只不过是加载到Dalvik中。
- Android中的ClassLoader有哪些?
Android中的ClassLoader由一下4个类组成。
1:加载Framework层字节码文件
2:加载已经安装到系统中APK文件中的字节码文件(sdk中的文件)
3:加载指定目录中的字节码文件(如lib引入的jar中的文件等)
4:是2.3的父类
从上面我们知道一个应用的运行必须要使用1和2,2中类加载器。
- Android中的ClassLoader的特点以及作用?
前面提到了委派模式。在Android中叫双亲代理模式。2者的作用与思想是一样的 。
特点:如果字节码在整个加载器类树中被一个加载器加载过 那么在整个系统生命周期中中都不会在重新加载 提高效率
作用:类加载的共享功能与隔离功能都是基于双亲代理模式总结而来的。
共享功能:一些底层(如Framework层)的类被顶层类加载器加载过那么以后在任何地方用到就不用再加载。
隔离功能:不同继承路线类加载器中,加载的类都是一定是不相同的,避免用户写一些可见的类冒充核心的类库。如:Object.lang.String类在程序启动之前就被系统加载了。如果我们自己写的String会将系统的String类替换的话,将会出现严重的安全问题。
双亲代理模式:
Android中的classLoader当加载一个字节码文件的时候首先会询问当前加载器是否已经加载过此类 如果已经加载 那么直接返回不再重复加载。如果没有加载,他会查询当前加载器的Parent类是否已经加载过此字节码。如果加载过直接返回Parent类加载的字节码文件。如果(所有继承链都没有加载过)那么就由子加载器加载并返回。
同一个类指的是相同的类名,包名,已经是同一个类加载器加载的。
Android中的ClassLoader源码讲解
从上面我们知道一个应用的运行必须要使用BootClassLoader和PathClassLoader,2种类加载器。下面我们新建个Android项目来运行下。代码如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader classLoader = getClassLoader();
Log.e("ggxiaozhi", "classLoader: "+ classLoader);
if (classLoader.getParent()!=null){
classLoader=classLoader.getParent();
Log.e("ggxiaozhi", "classLoader-Parent: "+ classLoader);
}
}
}
打印结果:
01-12 06:25:26.880 1842-1842/? E/ggxiaozhi: classLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.ggxiaozhi.hotfix-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.ggxiaozhi.hotfix-1/lib/x86, /vendor/lib, /system/lib]]]
01-12 06:25:26.880 1842-1842/? E/ggxiaozhi: classLoader-Parent: java.lang.BootClassLoader@665444a
从打印结果也可以看到确实是这样的。下面我们进入源码分析下。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded (1)
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false); (2)
} 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); (3)
// this is the defining class loader; record the stats
}
}
return c;
}
源码也比较简单:
- (1)首先查找我们的ClassLoader是否加载过我们的当前的class文件。
- (2)如果没有找到就查找他的分类有没有加载过。
- (3)如果父类也没有找到过就去通过findClass(name);去加载这个类文件。
点进去findClass()这个方法发现,这是一个空实现说明他的子类实现了这个方法。而他的子类正上上面我们提到的4种类加载器。由于我们在实际中Framework层的加载器我们接触不到,所以重点分下其他三种类加载器。由于这几个方法我们都看不到所有我们通过源码网查去查询。
源码地址
使用教程文章
-
DexClassLoader
1/**
22 * A class loader that loads classes from {@code .jar} and {@code .apk} files
23 * containing a {@code classes.dex} entry. This can be used to execute code not
24 * installed as part of an application.
25
36 public class DexClassLoader extends BaseDexClassLoader {
55 public DexClassLoader(String dexPath, String optimizedDirectory,
56 String librarySearchPath, ClassLoader parent) {
57 super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
58 }
59}
可以看到它里面就一个构造方法,通过类的注释可以简单理解为它是加载来自.jar和.apk本身的class文件路径,也可以用来执行不作为应用程序的一部分安装的代码。(所以它也是我们后面要讲得动态更新加载的核心加载器)
这里面的参数含义分别为:
- dexPath:要加载的指定文件下的dex文件路径
- optimizedDirectory :这是一个copy路径。可以理解应用在安装时,先将dex文件copy到应用的内部路径,待需要加载dex文件时去应用内部路径找到dex文件去加载。(中间还会做一些优化)。这个路径是应用的内部路径,在DexClassLoader下这个参数一定不能为空。
- librarySearchPath 加载native相关dex文件。
- ClassLoader 父类加载器
-
PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
37 public PathClassLoader(String dexPath, ClassLoader parent) {
38 super(dexPath, null, null, parent);
39 }
40
63 public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
64 super(dexPath, null, librarySearchPath, parent);
65 }
66}
从类的注解上来看这个加载器是Android使用作为它的系统类加载器和它的应用程序类加载器。也就是加载Android项目工程中的类文件加载器。参数和上面一样,正是因为缺少optimizedDirectory参数所以它只能加载项目本省的dex文件中的类
-
BaseDexClassLoader
BaseDexClassLoader是上面2个类加载器的父类。由于上面2个类加载器都没有具体的逻辑方法。还记上面我们在查找ClassLoader时知道加载类的方法是findClass(name).由于这两个雷都没有实现这个方法,那么一定就是在他们的父类BaseDexClassLoader中实现的。下面看下他的源码:
29 public class BaseDexClassLoader extends ClassLoader {
30 private final DexPathList pathList;
31
45 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
46 String librarySearchPath, ClassLoader parent) {
47 super(parent);
48 this.pathList = new DexPathList(this, dexPath, librarySearchPath, 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 }
在这个类中找到了上面我们在ClassLoader中未实现的findClass方法。发现他是调用了DexPathList类中的方法,并且这个类是在构造方法中已经初始化并肩参数传入其中了。所以我们在这个类中寻找一下DexPathList#findClass():
final class DexPathList {
51 private static final String DEX_SUFFIX = ".dex";
52 private static final String zipSeparator = "!/";
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 Element[] dexElements;
63
64 /** List of native library path elements. */
65 private final Element[] nativeLibraryPathElements;
66
67 /** List of application native library directories. */
68 private final List<File> nativeLibraryDirectories;
69
70 /** List of system native library directories. */
71 private final List<File> systemNativeLibraryDirectories;
72
73 /**
74 * Exceptions thrown during creation of the dexElements list.
75 */
76 private IOException[] dexElementsSuppressedExceptions;
77
78
96 public DexPathList(ClassLoader definingContext, String dexPath,
97 String librarySearchPath, File optimizedDirectory) {
98 ...
122 this.definingContext = definingContext;
123
124 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
125 // save dexPath for BaseDexClassLoader
126 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
127 suppressedExceptions, definingContext);
128
129
140 this.systemNativeLibraryDirectories =
141 splitPaths(System.getProperty("java.library.path"), true);
142 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
143 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
144
145 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
146 suppressedExceptions,
147 definingContext);
148
149 if (suppressedExceptions.size() > 0) {
150 this.dexElementsSuppressedExceptions =
151 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
152 } else {
153 dexElementsSuppressedExceptions = null;
154 }
155 }
156
157
...
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
280 List<IOException> suppressedExceptions) {
281 return makeElements(files, optimizedDirectory, suppressedExceptions, false, null);
282 }
283
//这个方法的作用就是将指定路径class文件转化成dexfile(dex文件) 同时存在Element[]数组中 //最后在findClass文件中使用
284 private static Element[] makeElements(List<File> files, File optimizedDirectory,
285 List<IOException> suppressedExceptions,
286 boolean ignoreDexFiles,
287 ClassLoader loader) {
//创建Element数组
288 Element[] elements = new Element[files.size()];
289 int elementsPos = 0;
290 /*
291 * Open all files and load the (direct or contained) dex files
292 * up front.
293 */
294 for (File file : files) {//遍历dex文件集合
295 File zip = null;
296 File dir = new File("");
//dex文件对应的java类
297 DexFile dex = null;
//获取文件路径
298 String path = file.getPath();
//获取文件名
299 String name = file.getName();
300
//path是文件夹继续往下遍历
301 if (path.contains(zipSeparator)) {
302 String split[] = path.split(zipSeparator, 2);
303 zip = new File(split[0]);
304 dir = new File(split[1]);
305 } else if (file.isDirectory()) {
306 // We support directories for looking up resources and native libraries.
307 // Looking up resources in directories is useful for running libcore tests.
308 elements[elementsPos++] = new Element(file, true, null, null);
309 } else if (file.isFile()) {//如果是文件 最后都会调用loadDexFile()f方法创建dex文件
310 if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {//这个文件是不是以.dex文件为后缀的
311 // Raw dex file (not inside a zip/jar).
312 try {
//如果是就创建一个dex文件
313 dex = loadDexFile(file, optimizedDirectory, loader, elements);
314 } catch (IOException suppressed) {
315 System.logE("Unable to load dex file: " + file, suppressed);
316 suppressedExceptions.add(suppressed);
317 }
318 } else {//如果这个文件值zip格式
319 zip = file;
320
321 if (!ignoreDexFiles) {
322 try {
323 dex = loadDexFile(file, optimizedDirectory, loader, elements);
324 } catch (IOException suppressed) {
325 /*
326 * IOException might get thrown "legitimately" by the DexFile constructor if
327 * the zip file turns out to be resource-only (that is, no classes.dex file
328 * in it).
329 * Let dex == null and hang on to the exception to add to the tea-leaves for
330 * when findClass returns null.
331 */
332 suppressedExceptions.add(suppressed);
333 }
334 }
335 }
336 } else {
337 System.logW("ClassLoader referenced unknown path: " + file);
338 }
339
340 if ((zip != null) || (dex != null)) {
341 elements[elementsPos++] = new Element(dir, false, zip, dex);
342 }
343 }
344 if (elementsPos != elements.length) {
345 elements = Arrays.copyOf(elements, elementsPos);
346 }
347 return elements;
348 }
/**
351 * Constructs a {@code DexFile} instance, as appropriate depending on whether
352 * {@code optimizedDirectory} is {@code null}. An application image file may be associated with
353 * the {@code loader} if it is not null.
354 */
355 private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
356 Element[] elements)
357 throws IOException {
358 if (optimizedDirectory == null) {//这个路径是空就说明一个dex文件没有 我们就要创建一个dex文件
359 return new DexFile(file, loader, elements);
360 } else {//否自会通过解压等处理最后得到DexFile
361 String optimizedPath = optimizedPathFor(file, optimizedDirectory);
362 return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
363 }
364 }
413 public Class findClass(String name, List<Throwable> suppressed) {
414 for (Element element : dexElements) {
415 DexFile dex = element.dexFile;
416
417 if (dex != null) {
418 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
419 if (clazz != null) {
420 return clazz;
421 }
422 }
423 }
424 if (dexElementsSuppressedExceptions != null) {
425 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
426 }
427 return null;
428 }
429
....
448
489
490 /**
491 * Element of the dex/resource/native library path
492 */
493 /*package*/ static class Element {
494 private final File dir;
495 private final boolean isDirectory;
496 private final File zip;
497 private final DexFile dexFile;
498
499 private ClassPathURLStreamHandler urlHandler;
500 private boolean initialized;
501
502 public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
503 this.dir = dir;
504 this.isDirectory = isDirectory;
505 this.zip = zip;
506 this.dexFile = dexFile;
507 }
508
509 @Override public String toString() {
510 if (isDirectory) {
511 return "directory \"" + dir + "\"";
512 } else if (zip != null) {
513 return "zip file \"" + zip + "\"" +
514 (dir != null && !dir.getPath().isEmpty() ? ", dir \"" + dir + "\"" : "");
515 } else {
516 return "dex file \"" + dexFile + "\"";
517 }
518 }
566
567 ...
590 }
591}
592
这个类比较长,这里简单讲解下:首先定义一些常量来规定加载.dex文件格式,同时定义了Element属性。在构造方法中先对一些异常处理并初始化一些常量。下面只看我们上步跟踪的方法findClass。发现这个方法先遍历了Element这个数组,而这个数组是通过在构造方法中调用makeElements()方法初始化,然后调用DexFile#loadClassBinaryName()方法,说明这个类也不是最终加载类的地方。不过在继续跟踪之前我们先对Element有个理解。其实他就是DexPathList中的一个内部类,谁对dex文件的包装,将路径与最终加载的类DexFile封装在一起,并进行一些字符串的拼凑。接着我们在进入DexFile#loadClassBinaryName()方法:
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
289 return defineClass(name, loader, mCookie, this, suppressed);
290 }
291
292 private static Class defineClass(String name, ClassLoader loader, Object cookie,
293 DexFile dexFile, List<Throwable> suppressed) {
294 Class result = null;
295 try {
296 result = defineClassNative(name, loader, cookie, dexFile);
297 } catch (NoClassDefFoundError e) {
298 if (suppressed != null) {
299 suppressed.add(e);
300 }
301 } catch (ClassNotFoundException e) {
302 if (suppressed != null) {
303 suppressed.add(e);
304 }
305 }
306 return result;
307 }
387 private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
388 DexFile dexFile)
389 throws ClassNotFoundException, NoClassDefFoundError;
这里直接看loadClassBinaryName方法他调用了defineClass方法,最后调用defineClassNative方法。defineClassNative()这个方法是native,是用C/C++ 实现的,往后我们就无法查看了。不过经过前面分析,最后native方法是大概就是通过C/C++
根据指定传入类的name去查找dex文件中对应的class文件相关信息数据,然后将dex文件的中的运行数据区中的数据拼成一个class字节码返回。应用层使用。
总结:
注意dex可以理解成把所有的class文件压缩成了一个dex文件 dex对应转化的是jar不是class
我的理解dex文件包含各个路径的jar文件.zip文件 不管用没有用到 等用到了采用类加载器去根据类名去加载这个类 信息然后最后通过native层在dex文件查找返回这个类的信息并返回。(这个整个串联的流程我也好串联起来。待后期有更深的研究在完善这部分) 如果大家有相关的书籍推荐下。
Android中的类加载器流程。